Android Background Processing

Sep 23 2022 · Kotlin 1.6, Android 12, Android Studio Chipmunk 2021.2.1

Part 2: Use WorkManager in Complex Apps

09. Implement WorkManager in Complex Apps

Episode complete

Play next episode

Next
About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 08. Set Up the Project Next episode: 10. Chain Work

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Notes: 09. Implement WorkManager in Complex Apps

The student materials have been reviewed and are updated as of SEPTEMBER 2022.

Transcript: 09. Implement WorkManager in Complex Apps

Now that you’ve set up the projects and everything you need to work through this course, you can implement the work manager! :] Let’s move to the project!

The WorkManager dependency is already within the build.gradle file, so you don’t have to manually add it. Now it’s time to create your first worker, just like you had it in the previous part of the course.

Create a package called worker, and add a new class called DownloadImageWorker.kt. Then add the next snippet of code to the class:

class DownloadImageWorker(context: Context, workerParameters: WorkerParameters) :
    Worker(context, workerParameters) {

  override fun doWork(): Result {

  }
}

Your class will extend the worker class, and override doWork(), like before. Then add the code to create a file, to which you’ll save the image:

class DownloadImageWorker(context: Context, workerParameters: WorkerParameters) :
    Worker(context, workerParameters) {

  override fun doWork(): Result {
    val imageDownloadPath = inputData.getString("image_path") ?: return Result.failure()
    val imageUrl = URL(imageDownloadPath)

    val connection = imageUrl.openConnection() as HttpURLConnection
    connection.doInput = true
    connection.connect()

    val imagePath = "${System.currentTimeMillis()}.jpg"
    val inputStream = connection.inputStream
    val file = File(applicationContext.externalMediaDirs.first(), imagePath)
  }
}

By using an HTTP connection, you’ll create an input stream and download the image to a local file. To finish the worker code, you need to add the output code, to store the bytes into the file:

class DownloadImageWorker(context: Context, workerParameters: WorkerParameters) :
    Worker(context, workerParameters) {

  override fun doWork(): Result {
    val imageDownloadPath = inputData.getString("image_path") ?: return Result.failure()
    val imageUrl = URL(imageDownloadPath)

    val connection = imageUrl.openConnection() as HttpURLConnection
    connection.doInput = true
    connection.connect()

    val imagePath = "${System.currentTimeMillis()}.jpg"
    val inputStream = connection.inputStream
    val file = File(applicationContext.externalMediaDirs.first(), imagePath)

    val outputStream = FileOutputStream(file)
    outputStream.use { output ->
      val buffer = ByteArray(4 * 1024)

      var byteCount = inputStream.read(buffer)
      while (byteCount > 0) {
        output.write(buffer, 0, byteCount)

        byteCount = inputStream.read(buffer)
      }

      output.flush()
    }

    val output = workDataOf("image_path" to file.absolutePath)
    return Result.success(output)
  }
}

In the end, you’re returning the image path as the output data, to be able to load the image. With that complete, head over to the ImagesFragment, and add the following code to the onImageDownload function:

    val constraints = Constraints.Builder()
        .setRequiredNetworkType(NetworkType.NOT_ROAMING)
        .setRequiresBatteryNotLow(true)
        .setRequiresStorageNotLow(true)
        .build()

    val downloadImageWorker = OneTimeWorkRequestBuilder<DownloadImageWorker>()
        .setInputData(workDataOf("image_path" to imageUrl))
        .setConstraints(constraints)
        .build()

You begin by creating the necessary constraints - which are enough storage & battery, and a network which isn’t roaming. That way you’re making sure the user is not wasting resources or data unless they can afford it!

Then you build a worker, and add the imageUrl as its input data. Once you’ve built the worker, you need to enqueue it and observe its info:

    val constraints = Constraints.Builder()
        .setRequiredNetworkType(NetworkType.NOT_ROAMING)
        .setRequiresBatteryNotLow(true)
        .setRequiresStorageNotLow(true)
        .build()

    val downloadImageWorker = OneTimeWorkRequestBuilder<DownloadImageWorker>()
        .setInputData(workDataOf("image_path" to imageUrl))
        .setConstraints(constraints)
        .build()

    val workManager = WorkManager.getInstance(requireActivity())
    workManager.enqueue(downloadImageWorker)

    workManager.getWorkInfoByIdLiveData(downloadImageWorker.id)
        .observe(this, Observer { workInfo ->
          if (workInfo?.state?.isFinished == true) {
            activity?.toast("Image downloaded!")
          }
        })

If the image was successfully downloaded, you will show a toast! Once you finish the upload worker, you’ll be able to download images you’ve uploaded to the backend, but for now, add a return statement after the hardcoded image in getAllImages(). Now run the app, and try to long click the image, to promt a dialog to download it!

Once you’re done, you’ll see the toast saying the download has been successful! Then open the device file explorer, and check if the image really is in the app folder. If it is, everything works fine! :]

Next, you’ll proceed to build a worker, to upload an image! See you in the next episode.