WorkManager in Android: Enqueue and execute background tasks

If you’ll recall, during Google I/O 2018, Google announced the WorkManager library as part of the Android Jetpack component set. It basically replaced JobScheduler as Google’s recommended way to enqueue background tasks that are guaranteed to execute.

What is WorkManager?

Like JobScheduler, but better, the WorkManager API makes it easy to specify deferrable, asynchronous tasks and indicate when they should run. These APIs let you create a task and hand it off to WorkManager to run immediately or at an appropriate time. It’s intended for tasks that require a guarantee that the system will run them, even if the app is exited.

For example, imagine you want your app to download new resources from the internet when a user’s phone is connected to WiFi. In this case, downloading is a task, and you can schedule this task to run at an appropriate time, depending upon a number of constraints (the availability of WiFi being one example).

You can relatively easily schedule tasks like this using WorkManager.

Advantages of WorkManager

WorkManager is a simple, but incredibly flexible library that has many benefits. These include:

  • Support for both asynchronous one-off and periodic tasks.
  • Support for constraints such as network conditions, storage space, and charging status, etc.
  • Chaining of complex work requests, including running work in parallel.
  • The output from one work request can be used as an input for the next.
  • Handles API compatibility back to API level 14. It sits on top of a few APIs such as JobScheduler and AlarmManager and picks the right APIs to use, based on conditions like the user’s device API level.
  • Works with or without Google Play services.
  • Follows system health best practices.
  • LiveData support to easily display work request state in the UI.

How it works

Before moving forward, let’s have a look at the various classes and concepts involved with WorkManager.

1. Worker

A task is defined using the Worker class. The doWork() method is run synchronously on a background thread provided by WorkManager. Worker is an abstract class and needs to be extended by us to perform the work.

2. WorkRequest

A WorkRequest defines how and when work should be run and performed. To this work request, you can add the data required by the worker to run, or different constraints that define the conditions under which the work should be performed.

A WorkRequest can be of two types:

  • OneTimeWorkRequest– A work request for non-repeating work.
  • PeriodicWorkRequest– A work request for repeating work. This work executes multiple times until it’s canceled, with the first execution happening immediately or as soon as the given Constraints are met.

3. WorkManager

WorkManager is a core library and is used to enqueue deferrable work that’s guaranteed to execute sometime after its constraints are met. It also allows observation of work status and the ability to create complex chains of work.

4. WorkInfo

WorkInfo contains information about a particular work request, including the id of the WorkRequest, it’s current state, output (WorkInfo.State.SUCCEEDED and WorkInfo.State.FAILED), tags, and run attempt count. WorkManager provides LiveData for each of the work request objects, and we can observe this and obtain the current status of the task.

Now let’s get started! Follow the below steps to implement WorkManager into your Android app.

1. Adding the Gradle dependencies

Add the following dependencies in your app’s build.gradle file:

2. Create a background task using the Worker class

The work is defined inside the dowork() method of the Worker class. Here we’ve assigned the task of sending notifications to the user. Also, the Result returned from doWork() informs WorkManager whether the task:

  • finished successfully via Result.success()
  • failed via Result.failure()
  • needs to be retried at a later time via Result.retry()
// NotificationRequestWorker extends the worker class which defines the work to be performed by the worker.
class NotificationRequestWorker(val context: Context, workerParameters: WorkerParameters) :
    Worker(context, workerParameters) {
    override fun doWork(): Result {
        // Do the work here
        val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val notification = NotificationCompat.Builder(context, "first").apply {
            setContentTitle("Background task")
            setContentText("Sample text")
            setSmallIcon(R.drawable.ic_launcher_foreground)
            priority = NotificationCompat.PRIORITY_DEFAULT
        }.build()

        nm.notify(System.currentTimeMillis().toInt(), notification)
      
        // Indicate whether the task finished successfully with the Result
        return Result.success()
    }

3. Configure how and when to run the task

As we know, that while a Worker defines the unit of work, a workerRequest defines how and when work should be run. Tasks may be one-off or periodic.

For one-time workerRequests, the code will look like this:

 private fun scheduleTask() {
        // build an object of OneTimeWorkRequestBuilder
        val workerRequest = OneTimeWorkRequestBuilder<NotificationRequestWorker>()
            .setInitialDelay(20, TimeUnit.SECONDS)
            .build()
        // Enqueue the above workrequest object to the WorkManager
        WorkManager.getInstance(this).enqueue(workerRequest)
    }

For periodic work requests, the code will look like this:

    private fun scheduleRepeatingTasks() {

        /*Setting up different constraints on the work request.
         */
        val constraints = Constraints.Builder().apply {
            setRequiredNetworkType(NetworkType.CONNECTED)
            setRequiresCharging(true)
            setRequiresStorageNotLow(true)
        }.build()
        
       /*Build up an obejct of PeriodicWorkRequestBuilder
       */
        val repeatingWork = PeriodicWorkRequestBuilder<NotificationRequestWorker>(
            1,
            TimeUnit.DAYS
        ).setConstraints(constraints)
            .build()
      
        /*Enqueue the work request to an instance of Work Manager
         */
        WorkManager.getInstance(this).enqueue(repeatingWork)
    }
  • We can also set up different constraints to the workerRequest object, like whether the network should be connected, the phone should be on charging mode, etc. All these specify the different conditions under which the Worker is designed to run.
  • You can also pass a data object to your workerRequest object, which is needed by the Worker to perform specified work.

4. Run your Worker

Call the scheduleTask() to schedule a one-time work request and scheduleRepeatingTask() to schedule a periodic, repeating wok request. For example, we’ve made a one-time work request to send one notification to the user after 20 seconds of delay:

And that’s all! Your Worker is set to run and will perform the work assigned to it! You can take rest easy now :p

You can perform various types of background work easily with the help of the WorkManager library—especially the work that involves extensive operations like downloading or uploading various resources, and many more.

Link to the GitHub repository:

Thanks for reading! If you enjoyed this story, please click the 👏 button and share it to help others find it!

Have feedback? Let’s connect on Twitter.

Fritz

Our team has been at the forefront of Artificial Intelligence and Machine Learning research for more than 15 years and we're using our collective intelligence to help others learn, understand and grow using these new technologies in ethical and sustainable ways.

Comments 0 Responses

Leave a Reply

Your email address will not be published. Required fields are marked *

wix banner square