Workmanager : Onetime and Periodic work request — Android

SumOn Hasan
6 min readSep 19, 2021

--

Work Manager is an API introduced and developed by Google that allows you to perform asynchronous, periodic and scheduled tasks on Android in a much more efficient way than conventional methods.

Android developers know the hassle of identifying a user’s device and then choose to assign a task between JobScheduler, Firebase JobDispatcher, ScheduledExecutorService, or AlarmManager.

WorkManager gets rid of this pain point. Selecting one of the following three APIs will automatically lift all the weights for you as you want them to work:

  1. JobScheduler
  2. Firebase JobDispatcher
  3. Alarm Manager

To give you an idea, Work Manager is perfect for the apps you need:

  • Return data periodically. Example: Weather app
  • Perform an action at a specific time. Example: Reminder based apps
  • Periodically upload data to a server.
  • Perform a task based on device information. Example: Battery status
    … and more!

Exploring WorkManager’s components

The WorkManager API has 3 main components:

  1. Worker
  2. WorkRequest
  3. WorkManager

Let’s explore these components further.

Worker

It is a component of the WorkManager API that actually performs the task. It is responsible for determining what needs to be done. You will need to override the doWork() method and your long-term code will move inside this method.

The doWork() method performs the task in the background thread. The doWork() method returns a result, which is an enum with the following values:

  • Result.success() — The task has been performed successfully
  • Result.failure() — Task failed permanently; It will not be tried again
  • 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 WorkRequests:

  • 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 to true if you want to run your Worker only when the device is charging.
  • setRequiresDeviceIdle(boolean requiresDeviceIdle) – set to true if you want to run your Worker only when the device is idle.
  • setRequiresBatteryNotLow(boolean requiresBatteryNotLow) – set to true if you don’t want to run your Worker when the device’s battery is low.
  • setRequiresStorageNotLow(boolean requiresStorageNotLow) – set to true if you don’t want to run your Worker when the device’s storage is low.
  • setRequiredNetworkType(NetworkType networkType) – sets the NetworkType with which the Worker should run.
  • addContentUriTrigger(Uri uri, boolean triggerForDescendants) – runs the Worker when a content Uri is updated. If triggerForDescendants is true, 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.

Adding WorkManager to your project

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 {
implementation("androidx.work:work-runtime-ktx:2.5.0")
}

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
}
}

One-time work

You can read more about how to build PeriodicWorkRequest here.

Build:

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()

Run:

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

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.
}
})

Cancel:

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.

Periodic work

Build:

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).build()

You can read more about how to build PeriodicWorkRequest here.

Run:

WorkManager.getInstance().enqueueUniquePeriodicWork(
"uniqueWorkName",
ExistingPeriodicWorkPolicy.KEEP,
periodicWorkRequest
)

There are more advanced usages of the WorkManager, but they aren’t covered in this article. 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

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.

--

--