• Skip to primary navigation
  • Skip to content
  • Skip to primary sidebar

CASTER.IO

Upgrade Your Career with Bite Sized Video Training

  • Articles
How to Migrate from Android-Job to WorkManager

How to Migrate from Android-Job to WorkManager

Annyce Davis
Posted: June 19, 2018 • Leave a Comment

The WorkManager API was recently announced at Google I/O. The goal of this library is to help simplify the way we handle background work. Specifically, it lets you create a task and hand it off to WorkManager to run immediately or at a later time. ​

Previously, we had to rely on multiple APIs to achieve this goal depending on the versions of Android that we needed to support in our apps. Thankfully the team at Evernote saw our frustrations and created the Android-Job library. Depending on the Android version that your user is running, either the JobScheduler, GcmNetworkManager or AlarmManager was being used behind a simple API. ​

However, with the release of WorkManager, it’s now time to move on. Here’s a direct quote from the README of the Android-Job library: ​

If you start a new project, you should be using WorkManager instead of this library. You should also start migrating your code from this library to WorkManager. At some point in the future this library will be deprecated. ​

So let’s see what it takes to make the move when you’ve already developed your app around the Android-Job library. ​

Adding the dependencies

The first step is to modify your application’s build.gradle file with the latest dependencies. If you’re using Kotlin inside of your application, there is an optional -ktx component with a few helper extension functions. ​

implementation "android.arch.work:work-runtime:$workManagerVersion"

// if you're using Kotlin
implementation "android.arch.work:work-runtime-ktx:$workManagerVersion"

WorkManager vs. Android-Job

​ The API and feature set of Android-Job and WorkManager are very similar. In order to understand the similarities and differences, we’re going to review the main components of WorkManager and how they are more or less represented in Android-Job. ​

Analogous classes in Android-Job and WorkManager:

Android-Job WorkManager
Job Worker
JobCreator WorkRequest
JobManager WorkManager
PersistableBundleCompat Data ​

Let’s see how these classes are similar across implementations when it comes to Android’s WorkManager:

  • Worker: This is where you put the code for the work you want to perform in the background. It’s analogous to the Job class from Android-Job.
  • WorkRequest: This represents a request to do some work. Very similar to the JobRequest class of Android-Job. However, unlike Android-Job we will need to pass in your Worker as part of creating your WorkRequest. This step eliminates the need for the JobCreator class that was part of the Android-Job setup.
  • WorkManager: This class actually schedules your WorkRequest and makes it run. Conceptually the same as the JobManager class in Android-Job.
  • Data: This class is a lightweight container for key/value pairs. The same as the PersistableBundleCompat class from Android-Job. It’s used for handling the small amounts of data that you pass in/out of WorkRequests.

To schedule requests the API is very similar: ​

Android-Job WorkManager
schedule(...) enqueue(...)

Example: ​

Android-Job

JobManager.instance().schedule(SimpleJob.buildRequest()) ​

WorkManager

WorkManager.getInstance().enqueue(SimpleJob.buildRequest())

With that covered, we’ll look at a few sample background jobs. First we’ll see the Android-Job version and then how we can achieve the same thing with WorkManager.

One-time requests

​Let’s start with the most basic type, those requests that occur immediately and with no special constraints.

Example: ​

Android-Job

class SimpleJob : Job() {
    override fun onRunJob(params: Params?): Result {
        Timber.i("Hello, %s!")
        return Result.SUCCESS
    }
​
    companion object {
        const val JOB_TAG = "simple_job"
​
        @JvmStatic
        fun buildRequest(): JobRequest? {
            return JobRequest.Builder(JOB_TAG)
                    .startNow()
                    .build()
        }
    }
}

WorkManager

class SimpleJob : Worker() {
    override fun doWork(): Worker.Result {
        Timber.i("Hello, %s!")
        return Worker.Result.SUCCESS
    }
​
    companion object {
        const val JOB_TAG = "simple_job"
​
        @JvmStatic
        fun buildRequest(): WorkRequest? {
            return OneTimeWorkRequestBuilder<SimpleJob>()
                    .addTag(JOB_TAG)
                    .build()
        }
    }
}

Here’s a step-by-step listing of what has changed:

  • We now extend the Worker class
  • We now override the doWork() method instead of onRunJob()
  • Worker.Result replaces Job.Result as the return value of our background work method
  • Our buildRequest() method returns a WorkRequest instead of a JobRequest
  • Finally we use the OneTimeWorkRequestBuilder class to build our request and supply our JOB_TAG ​

One-time requests with data

In this scenario we’re going to step it up a notch by dealing with some input data to our job.

Example: ​

Android-Job

class SimpleJob : Job() {
    override fun onRunJob(params: Params?): Result {
        val extras = params?.extras
        val name = extras?.getString(PARAM_NAME, "nobody")
​
        Timber.i("Hello, %s!", name)
        return Result.SUCCESS
    }
​
    companion object {
        const val JOB_TAG = "demo_job"
        const val PARAM_NAME = "param_name"
​
        @JvmStatic
        fun buildRequest(name: String): JobRequest? {
            val extras = PersistableBundleCompat()
            extras.putString(PARAM_NAME, name)
​
            return JobRequest.Builder(JOB_TAG)
                    .startNow()
                    .setExtras(extras)
                    .build()
        }
    }
}

WorkManager

class SimpleJob : Worker() {
    override fun doWork(): Worker.Result {
        val name = inputData.getString(PARAM_NAME, "nobody")
​
        Timber.i("Hello, %s!", name)
        return Worker.Result.SUCCESS
    }
​
    companion object {
        const val JOB_TAG = "demo_job"
        const val PARAM_NAME = "param_name"
​
        @JvmStatic
        fun buildRequest(name: String): WorkRequest? {
            val data = mapOf(PARAM_NAME to name).toWorkData()

            return OneTimeWorkRequestBuilder<SimpleJob>()
                    .addTag(JOB_TAG)
                    .setInputData(data)
                    .build()
        }
    }
}

Here’s a step-by-step listing of what has changed:

  • By means of an extension method on the Map class, toWorkData(), we create the Data class that is needed to send inputs to our job
  • Inside of the doWork() method we access our job inputs via the inputData variable ​

One-time requests with constraints

​This time we want to require that there is a netwok connection and that the phone is charging before we execute our job. This only affects our buildRequest() method.

Example:​

Android-Job

@JvmStatic
fun buildRequest(name: String): JobRequest? {
    val extras = PersistableBundleCompat()
    extras.putString(PARAM_NAME, name)

    return JobRequest.Builder(JOB_TAG)
            .setExtras(extras)
            .setRequiresCharging(true)
            .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
            .setRequirementsEnforced(true)
            .build()
}

WorkManager

@JvmStatic
fun buildRequest(name: String): WorkRequest? {
    val data = mapOf(PARAM_NAME to name).toWorkData()
    val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresCharging(true)
            .build()
​
    return OneTimeWorkRequestBuilder<SimpleJob>()
            .addTag(JOB_TAG)
            .setInputData(data)
            .setConstraints(constraints)
            .build()
}

​Unlike Android-Job’s JobRequest.Builder class, the WorkRequest.Builder class uses a separate class, Constraints, to hold on to the set of desired conditions for executing a job. ​

Periodic requests

​The final example we’ll cover is when you want to perform some piece of work on a repeating basis. Let’s say you need to take an action every hour in the background.

Example:

Android-Job

@JvmStatic
fun buildRequest(name: String): JobRequest? {
    val extras = PersistableBundleCompat()
    extras.putString(PARAM_NAME, name)
​
    return JobRequest.Builder(JOB_TAG)
            .setPeriodic(TimeUnit.HOURS.toMillis(1))
            .setExtras(extras)
            .setRequiresCharging(true)
            .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
            .setRequirementsEnforced(true)
            .build()
}

WorkManager

@JvmStatic
fun buildRequest(name: String): WorkRequest? {
    val data = mapOf(PARAM_NAME to name).toWorkData()
    val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresCharging(true)
            .build()
​
    return PeriodicWorkRequestBuilder<SimpleJob>(1, TimeUnit.HOURS)
            .addTag(JOB_TAG)
            .setInputData(data)
            .setConstraints(constraints)
            .build()
}

​Notice that we now have an entirely new type of WorkRequest, the PeriodicWorkRequest class. This class is used to handle our repeating work. This differs from Android-Job where we were relying on a field of the JobRequest.Builder to specify that a request should be periodic.

All-in-all, the move from Android-Job to WorkManager is very seamless. It’s nice that the concepts are similar and even the naming is almost identical.

Hope you found this helpful. Happy coding!

Filed Under: Android

Free Stuff in Your Inbox

Drop your email in that box below and we’ll notify you when we release new articles, courses & lessons. Simple as that – easy peasy. 💥

Primary Sidebar

Recent Posts

  • Caster.IO Has Shut Down
  • Caster.IO is Shutting Down
  • Building Objects with Kotlin
  • Rxify: Error Handling with Subjects (Relays)
  • Self Destructing Smart Contracts in Ethereum

Search Articles

Copyright © 2021 · Maker Pro on Genesis Framework · WordPress · Log in