Color Mode


    Language

Android WorkManager

August 6, 2018

WorkManager is a new API in Android Architecture Components introduced in the Google I/O 2018. It simplifies and makes it much easier to do work on background threads. The WorkManager schedules tasks as instances of the Worker class and can schedule these workers based on certain conditions which you can set by using the provided The Constraints class. Examples of conditions you can set from the Constraints class, can be things like available internet/wifi connection or if a charger is connected. The WorkManager can also schedule all Worker instances to launch in any order and we can also pass input data into a Worker and get the output data.

Also a very important note about WorkManager: “WorkManager is intended for tasks that require a guarantee that the system will run them even if the app exits...”*

How to use it

First you have to add the dependency in your build.gradle file: implementation "android.arch.work:work-runtime:1.0.0-alpha02"

We will look at the following:

  • How to create a Worker
  • How to create Constraints
  • How to give a Worker instance an input and get its output
  • How to schedule all Worker's to be executed in a particular order

User story

To demonstrate how to use WorkManager we will start by establishing a made-up user story. In our imaginary app QuickSnapper, we press a shutter button and the app will take a picture and apply some stickers on it and upload it all in one automatic process thanks to the WorkManager.

So let's split up the user story in 3 use cases:

  • 1 The user takes a photo (We want to compress it here)
  • 2 The app adds weather and location information on the picture after step 1 (GPS and Internet must be available)
  • 3 The app uploads the image immediately after step 1-2 (Internet must be available)

For each use case we will make a Worker class. To do that we need to make a class and extend Worker which requires us to implement a doWork() method with a return type of a WorkerResult that can either be WorkerResult.SUCCESS or WorkerResult.FAILURE

Creating Workers

The first Worker compress our Bitmap into a smaller size, convert the Bitmap to ByteArray passes it in the WorkManager's outputData object and then we return WorkerResult.SUCCESS or WorkerResult.FAILURE.

    class ImageCompressionTask(val bitmap: Bitmap?) : Worker() {
        override fun doWork(): WorkerResult {
            val newBitmap: Bitmap?
            try {
                //Create a compressed bitmap
                newBitmap = Bitmap.createScaledBitmap(bitmap, 500, 500, false)
                return WorkerResult.SUCCESS
            } catch (e: IllegalArgumentException) {
                return WorkerResult.FAILURE
            }
        }
    }

In the second Worker we retrieve the Bitmap from the Workers inputData object and adds some weather and location stickers on the image

    class AddStickersTask : Worker() {
        override fun doWork(): WorkerResult {
            try {
                //Adding stickers on the bitmap...
                return WorkerResult.SUCCESS
            } catch (e: Exception) {
                return WorkerResult.FAILURE
            }
        }
    }

In the last Worker we just upload our Bitmap to our server

    class UploadImageTask : Worker() {
        override fun doWork(): WorkerResult {
            //Retrieve bitmap and upload work here
            return WorkerResult.SUCCESS
        }
    }

Creating Constraints for workers

Now we have created 3 Worker classes and can chain them together so they run when each previous Worker has returned WorkerResult.SUCCESS. The WorkManager won't proceed if any of the Worker instances returns WorkerResult.FAILURE.

But first we have to make our Constraints for our Worker instances, so the Worker only runs if the conditions we have set in the Constraints class is met.

val constraint = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()

GPS requirement is not yet supported in the Constraints class but we will instead check for enabled GPS in the AddStickersTask and if it’s not enabled we will return FAILURE and the next WorkManager won’t proceed to the next Worker

Now lets use our Worker classes

Here we set our Constraints for each Worker and build() will return an instance of OneTimeWorkRequest

val imageCompressionTask = OneTimeWorkRequest.Builder(ImageCompressionTask::class.java).build()
val addStickersTask = OneTimeWorkRequest.Builder(AddStickersTask::class.java).setConstraints(constraint).build()
val uploadImageTask = OneTimeWorkRequest.Builder(UploadImageTask::class.java).setConstraints(constraint).build()

We make them as OneTimeWorkRequest because we only want these Worker to execute once. PeriodicWorkRequest can be used in cases where you want a Worker for some repetitive work which can run in intervals you can set.

Input data and output data

Input data

As it is right now, our ImageCompressionTask doesn't have any Bitmap to compress. So we have to give it a Bitmap before it begins its work. We can pass a Bitmap or any type of data to our Worker classes by sending it an instance of a Data object from the WorkerManger API.

The Data class dosn't support Bitmap but it does support ByteArray, so we can convert our Bitmap to a ByteArray by using this static method to create a new intance of Data containg our ByteArray

val compressionData = Data.fromByteArray(getBitmapByteArray())

Now we just add a String tag to identify and retrieve our Worker later and we give it the compressionData like this:

 val imageCompressionTask = OneTimeWorkRequest.Builder(ImageCompressionTask::class.java).addTag(TAG_WORKER_1).setInputData(compressionData).build()

Getting the input data in the Worker class

Now when we have given our ImageCompressionTask some input data, we can extrat that by just calling the inputData object provided from the Worker class and when we are finished with our data, we can make it put in the outputData object so we can retrieve it outside of the ImageCompressionTask class

class ImageCompressionTask() : Worker() {

    override fun doWork(): WorkerResult {
        try {
             //get the input data
            val bitmapByteArray = Data.toByteArray(inputData)
            val bitmap = BitmapFactory.decodeStream(ByteArrayInputStream(bitmapByteArray))

            val newBitmap = Bitmap.createScaledBitmap(bitmap, 500, 500, false)

            //Save the bitmap back to the outputData
            outputData = Data.fromByteArray(getBitmapByteArrayHelper(newBitmap))
            return WorkerResult.SUCCESS
        } catch (e: IllegalArgumentException) {
            return WorkerResult.FAILURE
        }
    }

Output data

Usually we want to get the output data from a Worker when it have finished its work. To do that we can listen on a specific Worker by retrieving the Worker by its tag:

     WorkManager.getInstance().getStatusesByTag(TAG_WORKER_1).observe(this, Observer { workerStatusList ->
            val workstatus = workerStatusList?.get(0)
            workstatus?.let {
                if (it.state.isFinished) {
                    val outputData = it.outputData
                }
            }
        })

Putting everything together

Now we just feed our WorkManager with our Worker's in the order as described in our user story and we done!

WorkManager.getInstance().beginWith(imageCompressionTask).then(addStickersTask).then(uploadImageTask).enqueue()

When should you use it?

The WorkManager is very useful for tasks running in background threads and for tasks which need to fulfill certain conditions before they can run or automated tasks running in a certain order.

Some example of when WorkManager also can be really useful

  • Uploading data
  • Download data
  • Bitmap Compression work
  • GPS location logging.
  • Chat apps
  • Playlists apps
  • Repetitive work that needs to run on background threads

Links

A more detailed and advanced tutorial on how to work with WorkManager from Google: https://codelabs.developers.google.com/codelabs/android-workmanager/#0

More about WorkManager: https://developer.android.com/topic/libraries/architecture/workmanager

Article Photo by Annie Spratt

androidworkmanagerworkersjetpackthreads

Author

Muddi Walid

Muddi Walid

Android Developer

You may also like

November 7, 2024

Introducing Shorebird, code push service for Flutter apps

Update Flutter apps without store review What is Shorebird? Shorebird is a service that allows Flutter apps to be updated directly at runtime. Removing the need to build and submit a new app version to Apple Store Connect or Play Console for review for ev...

Christofer Henriksson

Christofer Henriksson

Flutter

May 27, 2024

Introducing UCL Max AltPlay, a turn-by-turn real-time Football simulation

At this year's MonstarHacks, our goal was to elevate the sports experience to the next level with cutting-edge AI and machine learning technologies. With that in mind, we designed a unique solution for football fans that will open up new dimensions for wa...

Rayhan NabiRokon UddinArman Morshed

Rayhan Nabi, Rokon Uddin, Arman Morshed

MonstarHacks

ServicesCasesAbout Us
CareersThought LeadershipContact
© 2022 Monstarlab
Information Security PolicyPrivacy PolicyTerms of Service