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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
WorkManager Tutorial for Android: Getting Started
30 mins
- Understanding Background Work
- Types of Background Work
- Why WorkManager?
- Knowing When to Use WorkManager
- Getting Started
- Setting Up the WorkManager Library
- Understanding WorkManager Classes
- Creating Your First Background Task
- Writing the FilterWorker Code
- Instantiating and Configuring the Worker
- Checking the Results
- Chaining Tasks
- Writing the CompressWorker Code
- Chaining FilterWorker with CompressWorker
- Checking the Results
- Cleaning the Worker and Uploading the ZIP
- Checking the Results
- Starting Unique Work
- Replacing Existing Work of Picked Photos
- Observing Work
- Tagging a WorkRequest and Observing It
- Checking the Results
- Canceling Work
- Canceling the Picking Photos Work Sequence
- Understanding Constraints
- Adding a Network Connection Constraint
- Testing the Network Constraint
- Where to Go From Here?
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.
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.