Use of WorkManager in Android App (Part-2)

Abhishek Srivastava
7 min readAug 13, 2020

In the last article, we discussed how to work manager APIs work under and how you can use these APIs to schedule the work to run in particular conditions. (If you haven’t read the previous article, I highly recommend you read it before proceeding.)

In this tutorial, we will talk about how to integrate the work manager in your project and much more advanced features and customization which makes your life easy in scheduling tasks.

So, Let’s start.

To Integrate work manager in your project,

dependencies {
def work_version = "2.2.0"
implementation "androidx.work:work-runtime:$work_version"
}

Now, as a next step, we will create a Worker class. Worker class is responsible to perform work synchronously on a background thread provided by the work manager.

class YourWorkerClass(appContext: Context, workerParams: WorkerParameters): Worker(appContext, workerParams) 
{
override fun doWork(): Result
{
// Your work here.
// Your task result
return Result.success()
}
}

In this above class,

  • doWork() method is responsible to execute your task on the background thread. Whatever task you want to perform has to be written here.
  • The result returns the status of the work done in doWork() method. If it returns Result.success() it means the task was successful if the status is Result.failure(), the task was not-successful and lastly, if it returns Result.retry() it means the task will execute again after some time.

Now, let’s configure how to execute the task

We need to create a WorkRequest which defines how and when work should be run. It has two types,

  • OneTimeWorkRequest — Runs the task only once
  • PeriodicWorkTimeRequest — Runs the task after a certain time interval.
val yourWorkRequest = OneTimeWorkRequestBuilder<YourWorkerClass>().build()

and once we have defined our work request, we can just schedule the task using,

WorkManager.getInstance(context).enqueue(yourWorkRequest)

Now, let’s talk about the customization we can do in our task execution.

We can add specific constraints in our WorkRequest to customize it. To add constraints, we use

val myConstraints = Constraints.Builder()     .setRequiresDeviceIdle(true) //checks whether device should be idle for the WorkRequest to run     
.setRequiresCharging(true) //checks whether device should be charging for the WorkRequest to run .setRequiredNetworkType(NetworkType.CONNECTED) //checks whether device should have Network Connection .setRequiresBatteryNotLow(true) // checks whether device battery should have a specific level to run the work request .setRequiresStorageNotLow(true) // checks whether device storage should have a specific level to run the work request
.build()

and to add it in our work request, we use

val yourWorkRequest = OneTimeWorkRequestBuilder<YourWorkerClass>()
.setConstraints(myConstraints)
.build()

Here, we set the above-defined constraints to the previously defined workRequest. Now, this work request will only run if all the constraints are satisfied.

We can also set the Periodic Task which will run after a certain time interval. To run a workRequest which runs periodically we use,

val yourPeriodicWorkRequest = PeriodicWorkRequestBuilder   <YourPeriodicWorkerClass>(1, TimeUnit.HOURS)     .setConstraints(myConstraints)     
.build()

This will run every 1 hour periodically as we have set the period to be 1 hour.

Minimum time interval to run a periodic task is 15mins

  • If you do not want the task to be run immediately, you can specify your work to start after a minimum initial delay using,
val yourWorkRequest = OneTimeWorkRequestBuilder<YourWorkerClass>()
.setInitialDelay(10, TimeUnit.MINUTES)
.build()

This will run after an initial delay of 10minutes.

Now, let’s check some of the amazing features we have in Work Manager

CHAINING WORKER WITH ANDROID WORK MANAGER

Chaining the Worker is the most attractive feature of WorkManager. With the work manager, we can easily chain multiple workers together and run workers in a particular order.

WHEN TO CHAIN WORKS:

  • There might be some chance that you want to run some tasks in a particular order. The work manager provides chains to order your works in a predefined order.
  • For example, your photo editor app needs to compress the image first and later that image should be uploaded to the server. For this example, you need two tasks — one for compressing an image and another for uploading an image — to be run sequentially.
  • With the work manager, you can easily chain these tasks to run sequentially or parallelly or in any combinations of that.
  • Remember that if any of the work falls in the chain, the chain won’t run all the works scheduled to run after that failed work.

How to chain works?

  • To chain two or more works, work manager APIs provide two methods: 1) beginWith() and 2) then(). You can pass the WorkRequest of the Worker you want to run.
  • As the name suggests begin with beginWith() method indicates the start point of the chain.
  • After beginWith(), you can add multiple then() calls. The work will run after the previous work is completed.

TYPES OF CHAINING:

  1. Sequential Chaining: It is simple chaining in which the first task starts execution then only the second task executes. With sequential chaining, the output data from one worker become the input to the next worker.
  2. Parallel Chaining: In this type of chaining we run some parallel task. When this parallel task completes its execution then only the next one starts execution. In parallel chaining, the output data from these parallel workers will fall into as input in the next worker.
  3. Multiple Parallel Chaining: WorkManager gives us the ability to executes multiple chains in parallel. With WorkContinuation class we can combine multiple chains and run them in parallel sequence.

1. SEQUENTIAL CHAINING:

For sequential chaining, we can say that, we want to execute the first task and when it completes then only execute the next task. Below shows the example of sequential chaining.

WorkManager.getInstance()
.beginWith(firstWorker)
.then(secondWorker)
.enqueue()
  1. 1 BEGINWITH:

The beginWith methods indicate the start of the chain. The beginWith method returns WorkContinuation instance.

1.2 THEN:

With WorkContinuation instance we can add as many then as we want. It’s like the stream of worker executing in the given order.

Note: If the firstWorker failed due to some reason, then the chain won’t run all the next workers in the chain. So in our case, if firstWorker failed then the secondWorker will not run.

2. PARALLEL CHAINING:

Like I said before with parallel chaining you can execute multiple workers simultaneously and when these workers complete their execution then the next worker starts its execution. Below shows the example of parallel chaining.

WorkManager
.getInstance()
.beginWith(firstWork,secondWork,thirdWork)
.then(fourthWorker)
.then(fifthWorker,sixWorker)
.enqueue()

2.1 BEGINWITH:

Here beginWith method starts the execution of three workers in parallel. When these workers complete their execution then only the fourth worker starts its execution and then the fifth worker and Six worker will execute.

2.2 THEN:

Like I said above the then method will start if the previous worker completes its execution.

The same reason applies here if some worker fails due to some error then the next worker will not starts. In parallel chaining, if the keys are the same for input and output then the better approach is to use InputMerger, instead of simple override data.

3. MULTIPLE PARALLEL CHAINING:

You may want to run some parallel workers with chaining. Let’s say you have three large files and before sending them to the server you want to compress them as parallel. And when the individual worker chain completes the compressing then only send it to the server. For these types of work chaining, we can use the combine method inside the WorkContinuation class. Let’s see an example of how we can use the above scenario with work continuation.

val workManager: WorkManager = WorkManager.getInstance()
val chain1 = workManager
.beginWith(firstCompressFileWorker)
.then(uploadFileWorker)
val chain2 = workManager
.beginWith(secondCompressFileWorker)
.then(uploadFileWorker)
val chain3 = workManager
.beginWith(thirdCompressFileWorker)
.then(uploadFileWorker)
WorkContinuation.combine(chain1,chain2,chain3)
.enqueue()

All of these chain workers will execute in parallel when enqueueing the workers. One thing noticeable here that the upload file worker will not run before compress the file worker.

Now there are a lot of ways of combining the workers and chaining them with parallel and sequential. With your own need, you can easily define the dependencies of the worker or execute them in parallel.

Check Status of your WorkManager

If we want to do some task when WorkManager executes the task successfully like showing a Toast or something else we need to check the status of the task. To check the status we use,

WorkManager.getInstance(context)
.getWorkInfoByIdLiveData(yourWorkRequest.id)
.observe(lifecycleOwner, Observer {
workInfo ->
if (workInfo != null && workInfo.state ==
WorkInfo.State.SUCCEEDED)
{
//Toast
}
})

This is how we can integrate the work manager in our Android project.

Now more about Work Manager Multiple Input/output, we will discuss in Part-3.

Happy learning.

--

--

Abhishek Srivastava

Senior Software Engineer | Android | Java | Kotlin | Xamarin Native Android | Flutter | Go