Use of WorkManager in Android App(Part-1)

Abhishek Srivastava
6 min readAug 5, 2020

--

What is WorkManager

WorkManager is part of Android Jetpack and an Architecture Component for background work that needs a combination of opportunistic and guaranteed execution. Opportunistic execution means that WorkManager will do your background work as soon as it can. Guaranteed execution means that WorkManager will take care of the logic to start your work under a variety of situations, even if you navigate away from your app.

WorkManager is an incredibly flexible library that has many additional benefits. These include:

  • Support for both asynchronous one-off and periodic tasks
  • Support for constraints such as network conditions, storage space, and charging status
  • Chaining of complex work requests, including running work in parallel
  • The output from one work request used as input for the next
  • Handles API level compatibility back to API level 14.
  • Works with or without Google Play services
  • Follows system health best practices
  • LiveData support to easily display work request state in UI

Note:

WorkManager sits on top of a few APIs such as JobScheduler and AlarmManager. WorkManager picks the right APIs to use, based on conditions like the user's device API level. To learn more, check out the WorkManager documentation.

When to use WorkManager

The WorkManager library is a good choice for tasks that are useful to complete, even if the user navigates away from the particular screen or your app.

Some examples of tasks that are a good use of WorkManager:

  • Uploading logs
  • Applying filters to images and saving the image
  • Periodically syncing local data with the network

WorkManager offers guaranteed execution, and not all tasks require that. As such, it is not a catch-all for running every task off of the main thread. For more details about when to use WorkManager, check out the Guide to background processing.

Add WorkManager to your app

WorkManager requires the Gradle dependency below. These have been already included in the build files:

app/build.gradle

dependencies {
// Other dependencies
implementation "androidx.work:work-runtime-ktx:$versions.work"
}

You should get the most current version of work-runtime-ktx from here and put the correct version in. At this moment the latest version is:

build.gradle

versions.work = "2.3.4"

If you update your version to a newer one, make sure to Sync Now to sync your project with the changed Gradle files.

WorkManager Basics

There are a few WorkManager classes you need to know about:

  • Worker: This is where you put the code for the actual work you want to perform in the background. You'll extend this class and override the doWork() method.
  • WorkRequest: This represents a request to do some work. You'll pass in your Worker as part of creating your WorkRequest. When making the WorkRequest you can also specify things like Constraints on when the Worker should run.
  • WorkManager: This class actually schedules your WorkRequest and makes it run. It schedules WorkRequests in a way that spreads out the load on system resources while honoring the constraints you specify.

How to use Work Manager in Code

Step 1 — Create a Data input object

Input and output are passed in and out via Data objects. Data objects are lightweight containers for key/value pairs. They are meant to store a small amount of data that might pass into and out from WorkRequests.

You’re going to pass in the URI for the user’s image into a bundle. That URI is stored in a variable called imageUri.

Create a private method called createInputDataForUri. This method should:

  1. Create a Data.Builder object.
  2. If imageUri is a non-null URI, then add it to the Data object using the putString method. This method takes a key and a value. You can use the String constant KEY_IMAGE_URI from the Constants class.
  3. Call build() on the Data.Builder object to make your Data object, and return it.

Below is the completed createInputDataForUri method:

BlurViewModel.kt

/**
* Creates the input data bundle which includes the Uri to operate on
* @return Data which contains the Image Uri as a String
*/

private fun createInputDataForUri(): Data {
val builder = Data.Builder()
imageUri?.let {
builder.putString(KEY_IMAGE_URI, imageUri.toString())
}
return builder.build()
}

Step 2 — Pass the Data object to WorkRequest

You’re going to want to change the applyBlur a method so that it:

  1. Creates a new OneTimeWorkRequest.Builder.
  2. Calls setInputData, passing in the result from createInputDataForUri.
  3. Builds the OneTimeWorkRequest.
  4. Enqueues that request using WorkManager.

Below is the completed applyBlur method:

BlurViewModel.kt

internal fun applyBlur(blurLevel: Int) {
val blurRequest = OneTimeWorkRequestBuilder<BlurWorker>()
.setInputData(createInputDataForUri())
.build()
workManager.enqueue(blurRequest)
}

Step 3 — Update BlurWorker’s doWork() to get the input

Now let’s update BlurWorker's doWork() method to get the URI we passed in from the Data object:

BlurWorker.kt

override fun doWork(): Result {
val appContext = applicationContext

// ADD THIS LINE
val resourceUri = inputData.getString(KEY_IMAGE_URI)

// ... rest of doWork()
}

Step 4 — Blur the given URI

With the URI, you can blur the image the user selected:

BlurWorker.kt

override fun doWork(): Result {
val appContext = applicationContext
val resourceUri = inputData.getString(KEY_IMAGE_URI) makeStatusNotification("Blurring image", appContext) return try {
// REMOVE THIS
// val picture = BitmapFactory.decodeResource(
// appContext.resources,
// R.drawable.test)
if (TextUtils.isEmpty(resourceUri)) {
Timber.e("Invalid input uri")
throw IllegalArgumentException("Invalid input uri")
}
val resolver = appContext.contentResolver val picture = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri)))
val output = blurBitmap(picture, appContext) // Write bitmap to a temp file
val outputUri = writeBitmapToFile(appContext, output)
Result.success()
} catch (throwable: Throwable) {
Timber.e(throwable)
Result.failure()
}
}

Step 5 — Output temporary URI

We’re done with this Worker and we can now return Result.success(). We're going to provide the OutputURI as an output Data to make this temporary image easily accessible to other workers for further operations. This will be useful in the next chapter when we're going to create a Chain of workers. To do this:

  1. Create a new Data, just as you did with the input and store outputUri as a String. Use the same key, KEY_IMAGE_URI.
  2. Return this to WorkManager using Result.success(Data outputData) method.

BlurWorker.kt

Modify the Result.success() line in doWork() to:

val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())Result.success(outputData)

Step 6 — Run your app

At this point, you should run your app. It should compile and have the same behavior.

How to cancel Work Manager

When you want to cancel work by a unique chain name because you want to cancel all work in the chain, not just a particular step.

Step 1 — Cancel the work by name

BlurViewModel.kt

internal fun cancelWork() {
workManager.cancelUniqueWork(IMAGE_MANIPULATION_WORK_NAME)
}

Work constraints

To create a Constraints object, you use a Constraints.Builder. Then you set the constraints you want and add it to the WorkRequest, as shown below:

BlurViewModel.kt

// Put this inside the applyBlur() function
// Create charging constraint
val constraints = Constraints.Builder()
.setRequiresCharging(true)
.build()
// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
.setConstraints(constraints)
.addTag(TAG_OUTPUT)
.build()
continuation = continuation.then(save)
// Actually start the work
continuation.enqueue()

Rest discuss briefly in part-2 where we know the complete flow of implementation in the Android App.

If you prefer you can clone the WorkManager’s from GitHub:

$ git clone https://github.com/googlecodelabs/android-workmanager

Thanks for Reading…

--

--

Abhishek Srivastava

Senior Software Engineer | Android | Java | Kotlin | Xamarin Native Android | Flutter | Go