You may have heard great things about WorkManager
. Maybe you’re curious about WorkManager
, and want to know how Android Engineers can leverage the Google library to build better apps. Whatever the case, this is the complete, up-to-date, definitive guide to WorkManager
. Let’s begin our journey.
What is WorkManager?
With WorkManager, developers can schedule tasks in the background that are guaranteed to run either once or periodically. Oftentimes, developers need to download lots of data. With WorkManager, developers can schedule the data to be downloaded, but downloaded only when the end user is on WiFi (free) and if the device is charging. With cellular data, the user might be charged a lot for the data. You can schedule the data to only be downloaded when the user is on a “unmetered” network. (Metered data is cellular, and costs money. Think of unmetered data as a household WiFi or the local coffee shop.)
Is WorkManager free?
Yes, WorkManager is a free library from Google. It’s solid, stable and ready for production. As mentioned, this library is created by Google, and will likely receive support for many years to come. Don’t expect this to become a paid-only library; use WorkManager for anything, including commercial projects.
How do I use WorkManager?
Let’s build a news app that download new stories, images and videos while the user is sleeping at night. When morning comes, all stories, images and videos are already downloaded on the user’s device, ready to be read or watched. We will ensure that this content will only be downloaded when the device is on a non-metered network, as well as on a power source (charging). We’ll use Kotlin for this project.
First, let’s add the android.arch.work library to our build.gradle.
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0-rc02'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:design:28.0.0-rc02'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha08"
}
Next, let’s create the Kotlin code that will download the latest news from a web server (such as Google Cloud Platform, Amazon Web Services, Firebase, etc.). To keep things simple, let’s omit the actual background task and just log something to the console:
package com.example.workmanagereveryday
import android.util.Log.d
import androidx.work.Worker
class StoryDownloadWork : Worker() {
override fun doWork(): Result {
d("daniel", "performing work now!")
return Result.SUCCESS
}
}
With above code displays a message in logcat. Later, we can replace d(“daniel”, “performing work now!”) with our actual script that will fetch and download the news in the background.
Next, let’s look at MainActivity
. Our MainActivity
currently looks like this:
package com.example.workmanagereveryday
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
}
}
Let’s run our work! Change MainActivity
to this:
package com.example.workmanagereveryday
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val downloadWork = OneTimeWorkRequestBuilder<StoryDownloadWork>().build()
WorkManager.getInstance().enqueue(downloadWork)
}
}
In the above code, first we create the downloadWork
variable and call OneTimeWorkRequestBuilder
, passing in the StoryDownloadWork
class we previously created. We then call build(). Next, we get an instance of WorkManager
, then enqueue the downloadWork
.
Run the app in an emulator or on a physical device and examine the logcat. You should see the string “performing work now!” That is because MainActivity
calls StoryDownloadWork
, and StoryDownloadWork
contains a log statement.
You may have noticed that in the above code we use OneTimeWorkRequestBuilder
. As you might guess, OneTime means that the StoryDownloadWork
will only happen once. Later, we’ll change that to PeriodicWorkRequest
, a class that can be executed daily, for example.
The above code will now run, but what if we want to be notified when StoryDownloadWork
is finished? We can use the Observable pattern. In MainActivity
, type the following code:
WorkManager.getInstance().getStatusById(downloadWork.id).observe(this, Observer {
d("daniel", "value? ${it?.state}")
})
In the above code, we first get an instance of WorkManager
, then call the method getStatusById
, passing in the downloadWork’s id. Run the app again, open logcat
, and you should see something like the following:
09-15 08:41:54.217 6918 6982 D daniel : performing work now!
09-15 08:41:55.190 6918 6918 D daniel : value? ENQUEUED
09-15 08:41:55.303 6918 6918 D daniel : value? SUCCEEDED
As you can tell logcat, first we see the “daniel” log statement “performing work now!” followed by the status (value) of the the performed work. First, we see ENQUEUED followed by SUCCEEDED. This means that the work was performed successfully!
WorkManager Constraints
So we have the WorkManager
functional, but what about the constraints? What if we want to only perform StoryDownloadWork
when the devices is plugged in and charging as well as only when the device is on an unmetered network? (Unmetered means that the device is connect to WiFi and not cellular data that costs money, for example.) We can do this using Constraints.
Let’s add the following to MainActivity:
val constraints = Constraints.Builder()
.setRequiresCharging(true)
.setRequiredNetworkType(NetworkType.UNMETERED)
.build()
The above code sets the Constraints
, and specifies that the device must be charging and and on an unmetered network.
Next, in MainActivity
, directly below the variable constraints, update downloadWork
to the following:
val downloadWork = OneTimeWorkRequestBuilder<StoryDownloadWork>().setConstraints(constraints).build()
WorkManager.getInstance().enqueue(downloadWork)
Next, run the app, and you should notice that the Constraints are in place. In an emulator, turn “Battery status” to “Not charging” as well as “Charging connection” to “None” and run the app again. You should notice that nothing happens. As soon as you re-enable charging (or plug your physical device into AC power), you should see StoryDownloadWork run.

Periodic Scheduling
Running WorkManager when the user opens MainActivity is great, but what if we want to schedule a repeating task? With only a slight modification, we can schedule a task to run every x amount of time. Open MainActivity, and modify downloadWork as follows:
val downloadWork = PeriodicWorkRequestBuilder<StoryDownloadWork>(12, TimeUnit.HOURS)
.setConstraints(constraints)
.build()
In the above example, the task downloadWork will run every 12 hours, given the Constraint requirements are met.
Use cases
Periodically polling a server for data can be burdening, especially if you have to write a lot of Kotlin code just to keep things working. What happens when your app goes to sleep? What if it gets destroyed by Android? We as Android Engineers don’t have final control over the system and should not rely on our app being alive. Also, we shouldn’t rely on AlarmManager, because it’s old and unpredictable. WorkManager enters the scene with a lot to offer: periodic (or immediate) checkins with your app to determine if code should be run.
One thought on “Android WorkManager: The Ultimate Guide”
Comments are closed.