A Prelude to WorkManager
If you’ve been an Android developer for a while, you‘ll be familiar with the difficulties faced when trying to write code for scheduling tasks in Android.
Up till now, you might have been using the following APIs for scheduling tasks:
All of these APIs have their own use-case scenarios, where they would be the right fit to get the job done. These APIs have been used by Android developers for years now.
While these APIs are great and have received constant development updates throughout the years, Google has introduced a new API called WorkManager, that does all of this in a way that's much more efficient.
What Is WorkManager?
WorkManager is an API introduced and developed by Google that lets you perform asynchronous, periodic and scheduled tasks on Android in a much more efficient way, compared to the traditional ways.
Android developers know the pain of detecting the user's device and then choosing between JobScheduler, Firebase JobDispatcher, ScheduledExecutorService, or AlarmManager to schedule a task.
WorkManager gets rid of this pain point. It does all the heavy-lifting for you by performing your task exactly the way you desire it to be performed by automatically choosing one of the following three APIs:
- JobScheduler
- Firebase JobDispatcher
- AlarmManager
To just give you an idea, WorkManager is fit for apps that require you to:
- Fetch data periodically. Example: Weather apps
- Do an action at a particular time. Example: Reminder based-apps
- Upload data to a server periodically.
- Perform a task on the basis of the device info. Example: Battery state
...and more!
WorkManager is part of the Android Architecture Components, which is a part of Android Jetpack. Android Jetpack is a collection of Android components by Google that makes app development easier and the apps more robust.
Note : It’s important to keep in mind that while WorkManager is a great solution to perform tasks in Android, it’s also still in the alpha stages.
This article will serve as a tutorial for anyone who’s looking to get started with using the WorkManager API in their Android app.
Prerequisites
In terms of Android knowledge, you’ll need a basic understanding of:
- Kotlin. You can also use Java but this tutorial will be in Kotlin;
- How scheduling in Android works;
- Restrictions for specific APIs, and
- LiveData from the Android Architecture Components.
For your setup, you’ll need to have:
- Android Studio 3.2 or above running on your system;
- Kotlin 1.2 or above;
- Android SDK Platform 28 or above;
- Android SDK Build-Tools 28.0.0 or above, and
- The compileSdkVersion in your app should be set to 28 or above.
Exploring WorkManager’s components…
The WorkManager API has 3 main components:
- Worker
- WorkRequest
- WorkManager
Let’s explore these components further.
Worker
This is the component of the WorkManager API that actually performs the work. It is responsible for specifying what work needs to be done. You need to override the doWork()
method and your long-running code goes inside this method.
The doWork()
method performs the work on a background thread. The doWork()
method returns a Result
, which is an enum
with the following values:
Result.SUCCESS
– The work was performed successfully.Result.FAILURE
– The work failed permanently; it won’t be retried.Result.RETRY
– The work failed but needs to be retried according to the specified backoff policy.
WorkRequest
A WorkRequest
is responsible for specifying which Worker
should run. It takes in a Worker
and performs the task specified in the Worker
.
There are two types of WorkRequest
s:
OneTimeWorkRequest
– For one-time tasks, i.e. non-repeating tasks.PeriodicWorkRequest
– For repeating tasks.
Both these requests are built with one of the subclasses of WorkRequest.Builder
, which is either OneTimeRequestBuilder
or PeriodicRequestBuilder
.
When a WorkRequest
is created, an ID associated with the request is also created, which is a of type UUID
. You can also set a tag
for your WorkRequest
if you want to access it later via the tag
, and a backoff policy in case you want to reschedule the work.
There are two more significant things that you might want to use in your WorkRequest
:
Constraints
The Constraints.Builder
helper class lets you specify certain criteria which makes sure that your Worker
only runs if these criteria are met. Some of these criteria include:
setRequiresCharging(boolean requiresCharging)
– set totrue
if you want to run yourWorker
only when the device is charging.setRequiresDeviceIdle(boolean requiresDeviceIdle)
– set totrue
if you want to run yourWorker
only when the device is idle.setRequiresBatteryNotLow(boolean requiresBatteryNotLow)
– set totrue
if you don’t want to run yourWorker
when the device’s battery is low.setRequiresStorageNotLow(boolean requiresStorageNotLow)
– set totrue
if you don’t want to run yourWorker
when the device’s storage is low.setRequiredNetworkType(NetworkType networkType)
– sets theNetworkType
with which theWorker
should run.addContentUriTrigger(Uri uri, boolean triggerForDescendants)
– runs theWorker
when a contentUri
is updated. IftriggerForDescendants
istrue
, the criteria is matched for its descendants too.
Data
The Data.Builder
helper class lets you provide data to your Worker
.
Please keep in mind that this should not exceed 10KB.You will get an IllegalStateException
if you pass data that exceeds this limit.
Think of Data
as a key-value pairs store, not unlike SharedPreferences
. You can find out more about it here.
WorkManager
The self-titled WorkManager
class lets you manage, schedule, and even cancel WorkRequest
s in your app. It guarantees the work specified in your WorkRequest
is executed with the Data
provided to it once the Constraints
are met.
It also lets you observe the state of the WorkRequest
via the WorkStatus
class. This is a simple class that is wrapped around with LiveData
by the WorkManager
, containing information regarding the state of the task being performed.
Depending on your device’s API, WorkManager uses the following techniques to run your WorkRequest
:
- API 14 to 22: If the optional WorkManager-related FirebaseJobDispatcher dependency is added to the project, it uses the FirebaseJobDispatcher itself. Otherwise, it uses a combination of AlarmManager and BroadcastReceiver, which is a custom implementation written in the API.
- API 23 and above: It uses the JobScheduler API for devices with API 23 and above.
Adding WorkManager to your project
Currently, the WorkManager API depends on Support Library 27.1, but future releases of the API will depend on the new semantic-version-based AndroidX library, introduced by Google in May, 2018.
It’s interesting to note that all the classes in the WorkManager API already reside in the androidx.work
package. This means that while the API depends on the traditional Android Support Library, the WorkManager API and all the code pertaining to the API itself is a part of the AndroidX library.
Adding the WorkManager API to your app is simple. You just need to add the following dependencies to your app-level build.gradle
file and you’re all set:
dependencies {
// WorkManager core dependency
implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha10"
// Firebase JobDispatcher support
implementation "android.arch.work:work-firebase:1.0.0-alpha10"
// Test helpers
androidTestImplementation "android.arch.work:work-testing:1.0.0-alpha10”
}
Creating a Worker
For the sake of this tutorial, we shall assume that the long running task we need to run via our WorkManager API is backing up data to the cloud. This is done via our backupData()
method.
We’re gonna name our Worker
as BackupWorker
, and run our backupData()
method inside the doWork()
method after overriding it.
class BackupWorker(context : Context, params : WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
// Perform your long-running backup operation here.
backupData()
// Return SUCCESS if the task was successful.
// Return FAILURE if the task failed and you don’t want to retry it.
// Return RETRY if the task failed and you want to retry it.
return Result.SUCCESS
}
}
Building your WorkRequest
Let’s build a OneTimeRequest
with the OneTimeRequestBuilder
and pass our BackupWorker
to it as followed:
val backupWorkRequest = OneTimeWorkRequestBuilder<BackupWorker>().build()
That’s how easy it is to build a OneTimeRequest
with WorkManager! Now let’s add some Constraints
, Data
that has the user’s name, and a tag
. It’s ideal if we let the device backup data only when it’s charging and if storage isn’t low. So let’s see how to do this and create some constraints:
val constraints = Constraints.Builder()
.setRequiresCharging(true)
.setRequiresStorageNotLow(true)
.build()
val data = Data.Builder()
.putString(“DATA_USER_FIRSTNAME”, “John”)
.build()
val backupWorkRequest = OneTimeWorkRequestBuilder<BackupWorker>()
.setConstraints(constraints)
.setInputData(data)
.addTag(“BACKUP_TAG”)
.build()
Managing your WorkRequest with WorkManager
Running your first WorkRequest
Executing your WorkRequest
with WorkManager
is easy. You just need to get an instance of WorkManager
and enqueue the request. Here’s how that can be done:
val backupWorkRequest = OneTimeWorkRequestBuilder<BackupWorker>()
.setConstraints(constraints)
.build()
WorkManager.getInstance().enqueue(backupWorkRequest)
Observing the state of the WorkRequest
Like we mentioned above, the WorkManager API lets you observe the State
of your WorkRequest
. There are enum
values in the State
class:
BLOCKED
– Request is awaiting to be executed.ENQUEUED
– Request is enqueued for execution.RUNNING
– Request is currently being executed.SUCCEEDED
– Request completed successfully.CANCELLED
– Request has been cancelled.FAILED
– Request has failed.
We can get the ID of a WorkRequest
with the getStatusById()
method and call the observe()
method to observe the state of the request. Let’s see how to do this:
WorkManager.getInstance()
.getStatusById(backupWorkRequest.id)
.observe(lifecycleOwner, Observer { workStatus ->
if (workStatus != null && workStatus.state.isFinished) {
// Do something here if request has finished executing.
}
})
Canceling a WorkRequest
You can cancel a WorkRequest
by calling the cancelWorkById()
method or even the cancelAllWorkByTag()
method if you want to cancel it by passing the tag
of the WorkRequest
.
WorkManager.getInstance().cancelWorkById(backupWorkRequest.getId())
It’s noteworthy to mention that your WorkRequest
may not be canceled by the WorkManager
if it is already running or has finished.
Where can you go from here?
Periodic tasks
We have covered the usage of OneTimeWorkRequest
in this tutorial, but you can also set up recurring tasks with the PeriodicWorkRequest
class. The following code snippet sets up a PeriodicWorkRequest
to run every 24 hours:
val periodicWorkRequest = PeriodicWorkRequestBuilder<BackupWorker>(24, TimeUnit.HOURS)
You can read more about how to build PeriodicWorkRequest
here.
More advanced usages
There are more advanced usages of the WorkManager, but they aren’t covered in this article for brevity purposes. You can fine-tune WorkManager and leverage advanced features provided by the API, like:
- Unique sequence of tasks, to run only one instance of a specific set of tasks;
- Chained sequence of tasks, to run tasks in a specified order;
- Tasks that take input data and return output data.
You can learn more on how to do this in the official documentation here.
Conclusion
While the WorkManager API is still in alpha stages, it is a great choice to schedule and perform tasks in the background in your Android app. It could be the ideal API to use because of its ease-of-use and regular API updates, thanks to Google making it a part of the Android Architecture Components.
It’s probably not recommended to be used in production as of now, but do go ahead and play around with it in a testing environment!