Use of WorkManager in Android App(Part-1)
--
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
andAlarmManager
. 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 thedoWork()
method.WorkRequest
: This represents a request to do some work. You'll pass in yourWorker
as part of creating yourWorkRequest
. When making theWorkRequest
you can also specify things likeConstraints
on when theWorker
should run.WorkManager
: This class actually schedules yourWorkRequest
and makes it run. It schedulesWorkRequest
s 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 WorkRequest
s.
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:
- Create a
Data.Builder
object. - If
imageUri
is a non-nullURI
, then add it to theData
object using theputString
method. This method takes a key and a value. You can use the String constantKEY_IMAGE_URI
from theConstants
class. - Call
build()
on theData.Builder
object to make yourData
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:
- Creates a new
OneTimeWorkRequest.Builder
. - Calls
setInputData
, passing in the result fromcreateInputDataForUri
. - Builds the
OneTimeWorkRequest
. - 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:
- Create a new
Data
, just as you did with the input and storeoutputUri
as aString
. Use the same key,KEY_IMAGE_URI
. - 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…