WorkManager Tutorial for Android: Getting Started

In this WorkManager tutorial for Android, you’ll learn how to create background tasks, how to chain tasks, and how to add constraints to each task. By Fernando Sproviero.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Chaining FilterWorker with CompressWorker

Open MainActivity again and modify onActivityResult() as follows:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
  if (data != null
      && resultCode == Activity.RESULT_OK
      && requestCode == GALLERY_REQUEST_CODE) {

    val applySepiaFilter = buildSepiaFilterRequests(data)
    val zipFiles = OneTimeWorkRequest.Builder(CompressWorker::class.java).build()

    val workManager = WorkManager.getInstance()
    workManager.beginWith(applySepiaFilter)
        .then(zipFiles)
        .enqueue()
    }
}

You’ve combined the zip compression with the sepia filter code. After you created a CompressWorker zipFiles, you chained it with the sepia filter worker by calling .then(zipFiles) on the workManager instance. You can chain arbitrary amounts of workers this way, and it’s really simple to do so!

Also notice how you don’t need to pass any arguments to the CompressWorker, because of the inputData and outputData constructs. When the first worker finishes, whatever output it passed on gets propagated to the next worker in chain.

Checking the Results

Run the app again. Select one or more images and, after a few seconds, check in the app files with Device File Explorer. Now, you should also see the .zip file.
Zip file

Cleaning the Worker and Uploading the ZIP

Next up, you’ll clean the directory. Additionally, after generating the .zip file, you’ll upload it to a server.

First, create a CleanFilesWorker.kt file in the workers package:

private const val LOG_TAG = "CleanFilesWorker"

class CleanFilesWorker : Worker() {

  override fun doWork(): WorkerResult = try {
    // Sleep for debugging purposes
    Thread.sleep(3000)
    Log.d(LOG_TAG, "Cleaning files!")

    ImageUtils.cleanFiles(applicationContext)

    Log.d(LOG_TAG, "Success!")
    WorkerResult.SUCCESS
  } catch (e: Throwable) {
    Log.e(LOG_TAG, "Error executing work: ${e.message}", e)
    WorkerResult.FAILURE
  }
}

This worker is pretty straightforward; it simply calls ImageUtils to clean up the files.

To finish, create the UploadWorker.kt file inside workers with the following content:

private const val LOG_TAG = "UploadWorker"
private const val KEY_ZIP_PATH = "ZIP_PATH"

class UploadWorker : Worker() {

  override fun doWork(): WorkerResult = try {
    // Sleep for debugging purposes
    Thread.sleep(3000)
    Log.d(LOG_TAG, "Uploading file!")

    val zipPath = inputData.getString(KEY_ZIP_PATH, null)

    ImageUtils.uploadFile(Uri.parse(zipPath))

    Log.d(LOG_TAG, "Success!")
    WorkerResult.SUCCESS
  } catch (e: Throwable) {
    Log.e(LOG_TAG, "Error executing work: " + e.message, e)
    WorkerResult.FAILURE
  }
}

UploadWorker is simple, too; it parses the .zip file path and tells ImageUtils to upload the final product.

Now, create the new workers and add them to the chain them accordingly in MainActivity:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
  if (data != null
      && resultCode == Activity.RESULT_OK
      && requestCode == GALLERY_REQUEST_CODE) {

    val applySepiaFilter = buildSepiaFilterRequests(data)
    val zipFiles = OneTimeWorkRequest.Builder(CompressWorker::class.java).build()
    val uploadZip = OneTimeWorkRequest.Builder(UploadWorker::class.java).build()
    val cleanFiles = OneTimeWorkRequest.Builder(CleanFilesWorker::class.java).build()

    val workManager = WorkManager.getInstance()
    workManager.beginWith(cleanFiles)
        .then(applySepiaFilter)
        .then(zipFiles)
        .then(uploadZip)
        .enqueue()
    }
}

Note: Since the UploadWorker needs to upload the file somewhere, you need a file server to be running. You can do this in two different ways: run one locally or upload to a shared service.

Both options are ready for you. There’s a shared service running to which you can upload at any time. If, however, you prefer to upload locally, you can use the prepared local version of a file server.

To run the local version, you need to install NodeJS, and then navigate in the terminal to the server folder, found in the materials you downloaded. Execute the command node app.js, and everything should be up and running.

Depending on the chosen server option, open ImageUtils.kt and comment out one of the two SERVER_UPLOAD_PATH constants and uncomment the other one. The one containing 10.0.2.2:3000 is used for local server, while the other one is the shared service. Feel free to change the local server IP address to something else, if you’re using a real device.

Note: Since the UploadWorker needs to upload the file somewhere, you need a file server to be running. You can do this in two different ways: run one locally or upload to a shared service.

Both options are ready for you. There’s a shared service running to which you can upload at any time. If, however, you prefer to upload locally, you can use the prepared local version of a file server.

To run the local version, you need to install NodeJS, and then navigate in the terminal to the server folder, found in the materials you downloaded. Execute the command node app.js, and everything should be up and running.

Depending on the chosen server option, open ImageUtils.kt and comment out one of the two SERVER_UPLOAD_PATH constants and uncomment the other one. The one containing 10.0.2.2:3000 is used for local server, while the other one is the shared service. Feel free to change the local server IP address to something else, if you’re using a real device.

Checking the Results

Run the app, again. Next, select one or more images and, after a few seconds, open Logcat (View ▸ Tool Windows ▸ Logcat). You should see something like this, confirming that the file was correctly received by the server:
onResponse - Status: 200 Body: {"url":"/files/Your-UUID-string.zip"}

If you are running the server locally, you can open a browser and go to http://localhost:3000/files/Your-UUID-string.zip to download the file.

Starting Unique Work

Worker tasks can be started in two different ways. First, you can call the beginWith() method, as you’ve done so far. The second way is by calling beginUniqueWork() instead. As the name suggests, the beginUniqueWork() method starts work that can only have one unique instance. But you also have to provide something called an ExistingWorkPolicy. If you try to start another work instance, the previous work will proceed according to the chosen policy — replace, keep or append:

  • Replace the existing sequence with the new one.
  • Just ignore the new one and keep the existing sequence.
  • Append the new sequence to the existing one.

Replacing Existing Work of Picked Photos

For this tutorial, you’ll replace the existing work. To do that, open MainActivity, add a constant to the companion object and change onActivityResult() as follows:

class MainActivity : AppCompatActivity() {

  companion object {
    ...
    private const val UNIQUE_WORK_NAME = "UNIQUE_WORK_NAME"
  }

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    if (resultCode == Activity.RESULT_OK
        && requestCode == GALLERY_REQUEST_CODE) {

      val applySepiaFilter = buildSepiaFilterRequests(data)
      val zipFiles = OneTimeWorkRequest.Builder(CompressWorker::class.java).build()
      val uploadZip = OneTimeWorkRequest.Builder(UploadWorker::class.java).build()
      val cleanFiles = OneTimeWorkRequest.Builder(CleanFilesWorker::class.java).build()

      val workManager = WorkManager.getInstance()

      workManager.beginUniqueWork(UNIQUE_WORK_NAME, ExistingWorkPolicy.REPLACE, cleanFiles)
          .then(applySepiaFilter)
          .then(zipFiles)
          .then(uploadZip)
          .enqueue()
    }
  }
  ...
}

By calling beginUniqueWork(UNIQUE_WORK_NAME, ExistingWorkPolicy.REPLACE, cleanFiles), you’ve started unique work and decided to use the REPLACE policy. This means that, each time that you click the Pick Photos button and select one or more images, WorkManager will replace the existing sequence of WorkRequests and start a new one.