Table of Contents
- When to use WorkManager while working with background processing?
- Creating a background work with Android WorkManager
- Working with WorkRequests
- Constraints in Android WorkManager
- Input Data, Worker Results, and Output Data in WorkManager
- Observing Worker state via LiveData in WorkManager
- Closing Thoughts
Android WorkManager is a background processing library which is used to execute background tasks which should run in a guaranteed way but not necessarily immediately. With WorkManager we can enqueue our background processing even when the app is not running and the device is rebooted for some reason. WorkManager also lets us define constraints necessary to run the task e.g. network availability before starting the background task.
Android WorkManager is a part of Android Jetpack (a suite of libraries to guide developers to write quality apps) and is one of the Android Architecture Components (collection of components that help developers design robust, testable, and easily maintainable apps).
When to use WorkManager while working with background processing?
With the evolution of Android OS over the years, there are restrictions placed on background processing, in order to optimize battery consumption and make use of device resources in an optimal way. Each new Android release, starting from android marshmallow (API 23), has added some restrictions. You can read about the specific details on the same in the Android developer guide.
Thus, it is important to choose the best background processing approach for your app per your needs. Let’s quickly talk about cases when you would choose to use WorkManager.
Android WorkManager can be a perfect background processing library to use when your task –
1. Does not need to run at a specific time
2. Can be deferred to be executed
3. Is guaranteed to run even after the app is killed or device is restarted
4. Has to meet constraints like battery supply or network availability before execution
The simplest example for this can be when your app needs to upload a large chunk of user data to the server. This particular use case meets the criteria we mentioned above to choose WorkManager because:
1. Results need not be reflected immediately in your Android app
2. Data needs to be uploaded even when the upload begins and the user kills the app to work on some other app, and
3. The network needs to be available in order to upload data on the server.
This blog by Pietro Maggi, Android Developer Advocate @Google gives a detailed explanation about when and how to use WorkManager.
Creating a background work with Android WorkManager
To get started with implementing WorkManager, we first need to create a work which defines the task we need to run in the background using WorkManager. For brevity, we will consider uploading user data to server as our use case throughout this article. To define work, we will create a class which extends the Worker class. The task to be performed in background is written in the overridden method
The code below shows an example of how to write a Worker class.
Working with WorkRequests
After we define our work, we now need to define how and when we should start the work. One of the advantages of using WorkManager is that we can define whether our work is a one-time task or the work needs to be called periodically.
OneTimeWorkRequest is a concrete implementation of the WorkRequest class which is used to run WorkManager tasks that are to be executed once. The code below shows how to create OneTimeWorkRequest in its simplest form:
val uploadWorkRequest = OneTimeWorkRequestBuilder<UserDataUploadWorker>().build()
In order to run this work request, we need to call
enqueue() method on an instance of WorkManager and pass this WorkRequest as shown below:
enqueue() method enqueues one or more WorkRequests to be run in the background.
PeriodicWorkRequest, as the name suggests is used to run tasks that need to be called periodically until cancelled.
A few important points to keep in mind when working with PeriodicWorkRequest:
1. The work is run multiple times
2. The work keeps on executing periodically until it is cancelled
3. The first execution happens immediately after the mentioned constraints are met and the next execution occurs after the mentioned period of time
4. Recurring execution doesn’t begin if the constraints mentioned with that work request are not met
5. The minimum repeat interval is 15 minutes
6. The work cannot be chained with other work requests
7. The time interval between 2 periodic intervals can differ based on OS optimization restrictions we saw earlier
A PeriodicWorkRequest can be executed as shown below:
val periodicWorkRequest = PeriodicWorkRequestBuilder<UserDataUploadWorker>(24, TimeUnit.HOURS).build()
The code above creates a periodic work request which is executed every 24 hours.
In order to stop the execution of periodic work, it needs to be explicitly cancelled:
Constraints in Android WorkManager
WorkRequests can also be built with some constraints that need to be satisfied in order to execute the task. Constraints in WorkManager are specifications about the requirements that must be met before the work request is executed. For our use case, if we need to upload user data on our server, having network availability is a must. Thus, through the work request constraints, we can make sure that our upload task is executed only when the network is available as shown below:
There are many other constraints available with Android WorkManager including ‘requires charging’, ‘storage is not low’, ‘device is not idle’, etc. A list of all these constraints can be found below:
One work request can have multiple constraints defined on it. In case of multiple constraints, the work is executed only when all the constraints are met. If any constraint is not satisfied, the work is stopped and resumed only when that constraint is met.
Input Data, Worker Results, and Output Data in WorkManager
Running your background task often needs some data to work on. Like in our example, if we want to send user data to the server via a Worker, we need to pass that user data to the Worker task. In Android WorkManager, we can do so by using the Data class. The input data is nothing but a list of key-value pairs. The data can be passed to the WorkRequest as shown below:
As seen in the code above, the data can be constructed in 2 ways:
1. By using
workDataOf() method which is part of KTX and which converts list of pairs as Data
2. By using the
Data.Builder class which constructs a map from the passed key-value pairs and builds Data from this map
In order to access this input data inside the Worker,
getInputData() is called inside the
doWork() method to extract individual values from their keys like this:
Worker Results and Output Data
Result class is used to return the status of our worker task. doWork() method of the Worker class which is overridden to do the background tasks, expects an object of class Result as its return type. Work Result can be of three types:
Result.success() – This result indicates that the work was completed successfully
Result.failure() – This result indicates that the work was permanently terminated and that if there is any chain of workers following this worker, all the following workers will also be terminated. So we should return Result.failure() only when our chain of workers is dependent on this worker’s output
Result.retry() – This result indicates that the work is terminated for some reason and that it should be retried/ re-executed
Great, so what if we also want to pass some output data back from the worker once our background processing is over?
failure() of the Result class take in Data as an argument. This Data is same as the one used for Input Data and can constructed in the same manner, with a list of key-value pairs.
An example of passing output data from Worker is shown below:
But how do you access this output data from the Worker?
If you follow android developer’s official documentation, you might try to extract the output by using WorkInfo like this:
If you execute the code above, you will notice that the output data is never passed from the worker and the method getBoolean() on outputData will just resort to returning the default value i.e. false. We can double check if we have indeed passed output data from the worker but the behaviour still remains the same.
The reason behind not getting any output data is that the Worker executes all the tasks in background synchronously. When we attempt to fetch the output data from the WorkInfo, we try to fetch the output data before the worker is even executed. You can check this by running getStatus() method on the WorkInfo instance.
Calling the state immediately after enqueuing the WorkRequest will give the state as ENQUEUED and hence don’t get any output data.
To get the output data, we need to observe the WorkInfo via LiveData so that our app will attempt to retrieve the output data only when the worker is finished with its execution.
Observing Worker state via LiveData in WorkManager
Android WorkManager lets you observe the states of the Worker by returning LiveData for the requested WorkInfo and observing this LiveData for any changes. Thus, now to access the output data, we need to write an observer on the WorkInfo LiveData, check if its state is SUCCEEDED, and then extract the output data. Check the code below:
To summarise, we looked at:
- What Android WorkManager is
- When exactly to use WorkManager (for background processing)
- How to execute it and applying constraints
- Passing data to and from the WorkManager workers
- Observing WorkManager task for output data
With these basics in mind, I hope you will now be able to implement deferrable background tasks on modern day android devices running upgraded android OS after lollipop.