Android WorkManager Example

Hi, guys today I’m gonna talked to you about a new library Google released is WorkManager. WorkManager is a part of AndroidJetpack and an Architecture Component for a background work. WorkManager chooses an appropriate way to schedule a background task depending on the device API level and including dependencies. It might use JobScheduler, Firebase JobDispatcher, or AlarmManager

Features Of WorkManager

  1. Guaranteed, constraint-aware execution: Let’s say If I wanna upload a photo. I only wanna do it when the device has the network, that’s the constraint.
  2. Respectful of system background restrictions: Let’s say If your app in a doze mode it won’t wake up your phone just to do those work.
  3. Backwork compatible with or without Google play services
  4. Queryable: If you have a queue of work, you can actually check what’s the state. Is it running right now, has it succeeds or fails.
  5. Chainable: It is also chainable like you have work A depending on work B and C which in turn out a dependent on work D.
  6. Opportunistic: This means WorkManager tried to execute the work in your process as soon as the constraint match- without actually needing JobScheduler.

WorkManager Basics (Core Classes)

There are few WorkManager classes you need to know about.

  1. Worker: This is the class that does the background work. This is where you write most of your business logic.
  2. WorkRequest: This class actually schedules your work request and make it run.

Types of WorkRequest

  1. OneTimeWorkRequest: Things which can only be done once.
  2. PeriodicWorkRequest: For recurring work.

Android App Setup

So, enough of this theory let’s see how we can integrate WorkManager in Android app.

First, open your app level build.gradle file and add a dependency.

implementation 'android.arch.work:work-runtime:1.0.0-alpha01'   // Please use current version of library

Now the setup part is done. Let’s see how we can make a simple Worker.

class UploadPhotoWorker : Worker() {

    override fun doWork(): WorkerResult {
        return try {
            // uploadPhoto()     // Replace this function with logn running task.
            WorkerResult.SUCCESS
        } catch (ex: Exception) {
            WorkerResult.FAILURE
        }
    }
}

The doWork method actually runs on a background thread. WorkManager automatically runs this function on a background thread you don’t need to do run. You see doWork method have return type WorkerResult. So, we need to return Success if everything goes well and Failure if any error occurred.

Now let’s see how we can call this UploadPhotoWorker class.

val workManager = WorkManager.getInstance()
val uploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoWorker::class.java)
            .build()
workManager.enqueue(uploadPhotoRequest)

Soon after this request enqueued it will start uploading pictures. Now, what if you lose connectivity in the middle of this or even before this what if you do not have connectivity. You actually wanna constraints in this case.

The following shows how to add constraints in WorkRequest.

val workManager = WorkManager.getInstance()
        val constraints = Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()
        val uploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoWorker::class.java)
                .setConstraints(constraints)
                .build()

        workManager.enqueue(uploadPhotoRequest)

Now let’s say I want to observe the request. I wanna show a progress bar while this work is executing and I wanna hide the spinner when it’s done.

The following shows how to observe a WorkRequest.

val status = workManager.getStatusById(uploadPhotoRequest.id)

        status.observe(LifeCycleOwner, Observer {
            if (it != null && it.state.isFinished) {
                progressBar.setVisbility(GONE)
            }
        })

You see every WorkRequest has a unique UUID. With this id, we can observe that request. In here it means WorkStatus of the requestIf you want to learn more about WorkStatus see this link.

So, let’s move a step up in concept here and talk about Chaining Work. Let’s say my problem is I want to upload multiple photos to the server. Before this, I want to compress these photos then upload to the server. So, how would I do that, here comes the Chaining work concept.

The following shows how to Chain work.

class CompressPhotoWorker : Worker() {
    override fun doWork(): WorkerResult {
        return try {
            // compressPhotos()
            WorkerResult.SUCCESS
        } catch (ex: Exception) {
            WorkerResult.FAILURE
        }
    }
}

class UploadPhotoWorker : Worker(){
    override fun doWork(): WorkerResult {
        return try {
            // uploadPhoto()
            WorkerResult.SUCCESS
        } catch (ex: Exception) {
            WorkerResult.FAILURE
        }
    }
}

val compressPhotoRequest = OneTimeWorkRequest.Builder(CompressPhotoWorker::class.java)
                .setConstraints(constraints)
                .build()

val uploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoWorker::class.java)
                .setConstraints(constraints)
                .build()

workManager.beginWith(compressPhotoRequest).then(uploadPhotoRequest).enqueue()

In here we’re telling to our workManager that first execute compressPhotoRequest then execute uploadPhotoRequest. The beginWith ensures that compressPhotoRequest execute first then execute uploadPhotoRequest.

Now let’s say I want to upload multiple photos in parallel. The following shows how to execute requests in parallel.

val oneUploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoWorker::class.java).build()
val twoUploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoWorker::class.java).build()
val threeUploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoWorker::class.java).build()

workManager.enqueue(oneUploadPhotoRequest, twoUploadPhotoRequest, threeUploadPhotoRequest)

These all work requests will be executed in parallel.

Now let’s see how to cancel the work request. The following shows how to cancel work request.

workManager.cancelWorkById(uploadPhotoRequest.id)

Note: Cancellation is the best effort. The work is an asynchronous thing, they may be happening in the background before you had a chance to cancel it. It may be completed.

Now I have a problem here, I want to update my profile picture to the server and my network state is not good. The first picture is not uploaded yet, I decided that I want to upload another profile picture. As a user, I want my updated profile photo on my account. How would I solve this, here comes UniqueWork in the picture?

The following shows how to use unique work.

fun uploadProfilePhoto(bitmap: Bitmap) {
    val dataInput = Data.Builder()
    dataInput.putString("image", encodeToString(bitmap))    // encoding function convert bitmap to String
    val data = dataInput.build()
    val uploadPhotoRequest = OneTimeWorkRequest.Builder(UploadPhotoRequest::class.java)
            .setInputData(data)
            .build()
    mWorkManager.getInstance().beginUniqueWork("update_photo", ExistingWorkPolicy.REPLACE, uploadPhotoRequest)
            .enqueue()
}

In this, we’re using beginUniqueWork method. In this method, the first parameter is for uniqueWorkName. The second parameter is the ExistingWorkPolicy.REPLACE. Replace basically cancel and deletes any existing in-flight operation of this name. So, the last one always does win. In our case, the last profile picture will be uploaded and previous will be canceled. The third parameter is WorkRequest.

How WorkManager Works Under The Hood

Alright so, we’ve talked a lot about code. Let’s talked about how it all works under the hood. I have made a diagram and I want to explain WorkManager with this.

android workmanager

When To Use WorkManager

  1. It’s ok to use WorkManager to upload data to the server.
  2. It’s ok to parse data and store it in the database.
  3. Not ok to extract palette color and update ImageView.
  4. Not ok to parse data and update the contents of a view.
  5. It’s not good to process the payment transaction.

That’s it guys, I’m going to end this blog here. I’m sure there’s a lot more about WorkManager but it’s good if you guys explore it. The complete example of WorkManager is on GitHub.

Download Complete Code

I hope this blog gives you a good understanding of WorkManager. If you’ve any queries please do comment below.

Thank you for being here and keep reading…

3 COMMENTS

  1. I like the helpful info you provide in this article. I will bookmark your weblog and check again here regularly.

LEAVE A REPLY

Please enter your comment!
Please enter your name here