WorkManager Finally Released!
Here’s how to use it.
After quite a bit of back and forth with its implementation, androidx.work.WorkManager
is finally in a state where we can expect no breaking changes which completely redefine the way we use it. For those who aren’t yet familiar with it, WorkManager
is used on Android as a bridge to things like JobScheduler
or AlarmManager
to schedule work that happens regardless if the app is running. It can survive app crashes, force close, even device reboots, and it is a very powerful tool in today’s power limiting Android. You can find the latest release notes here:
As such, let’s have a look at how to do some interesting things with WorkManager
and how things work now.
Step 1: Decide What Kind of Work You Want To Do
First things first, you need to think of what you expect WorkManager
to do. What is the purpose of the work you’re scheduling? Do you need it now or later? Will it ever repeat and how often?
The purpose of the work matters because it affects how you structure it, and whether you even need to use WorkManager
. The purpose of this tool is to create work that is guaranteed, but deferrable. This means that you want to make sure that whatever work you give it will happen, but you’re ok with it being delayed, sometimes for a long time due to stuff like Doze. For light workloads, for example, it might just make sense to use a separate thread to perform the work immediately. For a complex recurring workload, you might want to use a OneTimeWorkRequest
with an initial delay which triggers a PeriodicWorkRequest
if one is not active and accurate so that the work gets performed periodically at a set time.
The first main difference is whether you want a single task to run or a recurring task. If you want a single task you create a OneTimeWorkRequest
, whereas if you want a recurring task you create a PeriodicWorkRequest
and you define a repeatInterval
when building it. Please note that for a PeriodicWorkRequest
the minimum interval is 15 minutes. Please also note that the PeriodicWorkRequest
has a number of limitations for some reason, such as not allowing you to setInitialDelay
(fixed in the next version thanks to our complaining, probably 2.1.0-alpha02) and not allowing you to chain together WorkRequests
after the work is complete.
The second difference is whether you want the task to run immediately or not. If not, you can define a delay before it runs with setInitialDelay
.
The third difference is whether you want to send any data to the work, in which case you define a Data
object and add it to the WorkRequest
with setInputData
. Please note there is a 10KB limit for Data
objects, for some reason.
Finally, you can add a number of Constraints
to prevent the work from running unless certain conditions are met. You can create them with a Constraints.Builder()
and apply them to the work using setConstraints
. The possible constraints are:
getRequiredNetworkType()
to only run based onNetworkType
requiresBatteryNotLow()
to only run when battery is “not low”requiresCharging()
to only run when device is chargingrequiresDeviceIdle()
to only run when device is idlerequiresStorageNotLow()
to only run when storage is “not low”
To help manage the work you can also use addTag
to add a String tag to the work you schedule, which you can then use to check its status or cancel it.
Step 2: Let’s Build a Worker
To start, we need to define the work that will be performed. In order to do this, we create a class which extends Worker
and implements doWork
:
public class MyWorker extends Worker {
public MyWorker(Context context, WorkerParameters wp) {
super(context, wp);
}
@NonNull
@Override
public Result doWork() {
//Do some work here.
//You can return Result.failure() to indicate work failed
// or Result.retry() to retry the work.
return Result.success(); //You can also pass a Data object to send results (10KB max)
// Data data = new Data.Builder().putString(tag,"").build();
// return Result.success(data);
}
}
This will include what work the Worker
actually performs and it can even instantiate new Workers
, although that can be tricky to debug.
Step 3: Let’s Build a WorkRequest
Once we have the worker we need to define the characteristics of the WorkRequest
to indicate various options we want to choose. For example, a OneTimeWorkRequest
may look like this:
//create a Data object to pass to the work
Data string = new Data.Builder().putString(tag, "tag").build();
//create constraints for the work
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
//create the actual WorkRequest calling the Worker above
OneTimeWorkRequest work = new OneTimeWorkRequest
.Builder(MyWorker.class)
.setInitialDelay(1000, TimeUnit.MILLISECONDS)
.setInputData(string)
.setConstraints(constraints)
.addTag("workTag")
.build();
In this WorkRequest
we create some data to pass to the Worker
(in my real example a URL to fetch), some constraints to block execution if, in this case, the phone is not connected to the Internet, and then set a delay on execution (in my real example a delay to sometime at night to perform the work when the phone is most likely idle), and add a tag to the work so we can monitor or cancel it easily in the future. Most of these options are, of course, optional, and you can choose whichever combination works for your work needs.
A similar PeriodicWorkRequest
would look like this:
PeriodicWorkRequest work = new PeriodicWorkRequest
.Builder(MyWorker.class, 24, TimeUnit.HOURS)
.setInputData(string)
.setConstraints(constraints)
.addTag("workTag")
.build();
The only differences are that the Builder
requires a period to be defined, in this case every 24 hours, and we cannot yet use setInitialDelay
with PeriodicWorkRequests
.
Step 4: Let’s Enqueue the WorkRequest
We now have all the pieces of the puzzle so we can actually tell Android to run our WorkRequest
, either as soon as possible or based on the constraints and delay we specified. To do this all we need is to get a WorkManager
instance and enqueue the work like this:
WorkManager.getInstance(this).enqueue(work);
Please note that as of WorkManager
2.1.0-alpha01 getInstance
needs to be provided a context and WorkManager.getInstance()
is deprecated.
And that’s it! Provided you didn’t set any constraints, the work will be executed at the first available moment!
Bonus Step: Check Work Status
While I don’t feel it’s the best implementation, you also have the ability to monitor the status of WorkRequests
. I have found the simplest way to do this was to create a custom Observer
and observe the LiveData
we get from WorkManager
in onCreate()
, which is a List
of WorkInfo
. In the example below, my observer gets details on the currently scheduled work and prints them to logcat.
// Create the observer which fetches WorkManager status.
final Observer<List<WorkInfo>> observer = list ->
{
StringBuilder sb = new StringBuilder();
if (list != null) {
int i = 0;
for (WorkInfo w : list) {
sb.append(" \n");
sb.append(i++);
sb.append(". ");
sb.append(w.getId());
sb.append(" ");
sb.append(w.getTags());
sb.append(" ");
sb.append(w.getState());
sb.append(" ");
}
}
Log.i("WRK status", sb.toString());
};// Observe the LiveData, passing in this activity as the
// LifecycleOwner and the observer.
WorkManager.getInstance()
.getWorkInfosByTagLiveData(tag)
.observe(this, observer);
You can use the Observer
to run code once the work has finished, like the code below that goes into the for loop. I’m using this in the events app to refresh the display once a forced task finishes.
if (w.getState() == WorkInfo.State.SUCCEEDED) {
//do stuff
}
You can also use this to get the result of the work if passed on as Data
in Result.success(data)
by using w.getOutputData
.
Further Reading
To read more about WorkManager
I highly recommend the official Google guides:
This video announcing the release of WorkManager
also has a number of interesting links in the description:
And, of course, my own article on using WorkManager
to schedule notifications in the future. I use a OneTimeWorkRequest
with a known initial delay to schedule a notification in the day of each event at noon:
Thanks for reading this article. You can connect with me on LinkedIn.
I’m currently looking for a job in the Geneva area, Switzerland, so please contact me if you know someone in need of an Experienced Junior Developer with Java (Android) and JavaScript (React Native Android/iOS) experience.
If you liked this article, please hit the clap icon 👏 to show your support.