Scheduling Tasks With Android WorkManager
In this WorkManager tutorial, you’ll learn how to schedule different kinds of tasks, test the tasks, as well as debug different tasks. By Harun Wangereka.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Scheduling Tasks With Android WorkManager
25 mins
- Getting Started
- Looking Into WorkManager
- Persisting Requests
- Running Your Request
- Defining Your Work
- Creating Your WorkRequest
- Creating a One-Time WorkRequest
- Observing Work Progress
- Creating a Periodic WorkRequest
- Creating a Delayed WorkRequest
- Querying Work Information
- WorkManager Initialization Types
- Using On-Demand Initialization
- Testing Your Workers
- Testing Delayed Work
- Testing Periodic Work
- Testing Constraints
- Creating Foreground Work
- Requesting Diagnostic Information from WorkManager
- Where to Go From Here?
WorkManager is a useful and important component of Android Jetpack. It allows the app to do things in the background even when the app is exited or the device is restarted.
WorkManager also has many advantages over its predecessors. For instance, it’s battery conscious, allows you to determine the conditions for your task to run such as having a Wi-Fi connection, flexible retrying, and integration with Coroutines and RxJava.
In this tutorial, you’ll build WorkManagerApp. The app downloads an image from a URL and saves the image to the device in the background. During the process, you’ll learn about:
- WorkManager basics
- Creating different workers and querying work progress
- WorkManager initialization types
- Testing your workers
Getting Started
Download the materials using the Download Materials button at the top or bottom of this tutorial. Open Android Studio 4.1.2 or later and import the starter project.
Build and run the project. You’ll see the following screen:
Looking Into WorkManager
WorkManager is a Jetpack Library that allows you to run deferrable jobs.
To understand how WorkManager operates under the hood, you need to know how it interacts with the Android operating system regarding:
- Persisting requests
- Running your requests
Persisting Requests
Once you enqueue your work request, WorkManager stores it in the database, which acts as a Single Source of Truth.
After this, WorkManager sends the work request to JobScheduler for API 23 and above. For API below 23, WorkManager performs a further check. If the device has Play Services installed, WorkManager sends the work request to GCM Network Manager. If it’s not installed, WorkManager uses Alarm Manager to run the work.
WorkManager does all this for you. :] Next, you’ll look at how WorkManager actually runs your work request.
Running Your Request
JobScheduler, GCM Network Manager and Alarm Manager are aware of your work. They’ll start your application if need be, given that the device meets all the constraints. In turn, they’ll request WorkManager to run your work.
There’s also GreedyScheduler, which runs inside the app process. Hence, it doesn’t start your app if it’s in the background. GreedyScheduler isn’t affected by the OS.
Now that you understand how WorkManager operates, it’s time to define your first WorkRequest.
Defining Your Work
You define your work by extending from Worker
and overriding doWork
. doWork
is an asynchronous method that executes in the background.
WorkManager has support for long-running tasks. It keeps the process in these tasks alive when the work is running. Instead of a normal Worker
, you’ll use a ListenableWorker
if you’re in Java or a CoroutineWorker
if you’re using Kotlin Coroutines.
First, navigate to ImageDownloadWorker.kt inside the workers package. You’ll see a class like the one below:
class ImageDownloadWorker(
private val context: Context,
private val workerParameters: WorkerParameters
) : CoroutineWorker(context, workerParameters) {
override suspend fun doWork(): Result {
TODO("Not yet implemented")
}
}
The code above does the following:
- The
ImageDownloadWorker
class extendsCoroutineWorker
. -
ImageDownloadWorker
requires instances ofContext
andWorkerParameters
as parameters in the constructor. You pass them toCoroutineWorker
, too. - It overrides
doWork()
, asuspend
method. It can do long-running tasks off the main thread.
doWork()
is pretty bare. You’ll add the code to do work in this method shortly.
Navigate to ImageUtils.kt and add the following extension function:
fun Context.getUriFromUrl(): Uri? {
// 1
val imageUrl =
URL(
"https://images.pexels.com/photos/169573/pexels-photo-169573.jpeg" +
"?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"
)
// 2
val bitmap = imageUrl.toBitmap()
// 3
var savedUri: Uri? = null
bitmap?.apply {
savedUri = saveToInternalStorage(this@getUriFromUrl)
}
return savedUri
}
Here’s a breakdown of what the code above does:
-
imageUrl
converts a link to a qualified URL. - Next, you convert
imageUrl
to a bitmap usingtoBitmap()
. - Last, you save the image bitmap to internal storage. Moreover, you get the URI of the path to the image’s storage location.
Head to ImageDownloadWorker.kt. Replace TODO(“Not yet implemented”) with the following code:
// 1
delay(10000)
// 2
val savedUri = context.getUriFromUrl()
// 3
return Result.success(workDataOf("IMAGE_URI" to savedUri.toString()))
Here’s a code breakdown:
- The image downloads quickly. To simulate a near real-world situation, you add a delay of 10,000 milliseconds so the work can take time.
- You get the URI from the extension function you added in the previous step.
- Last, once you have the URI, you return a successful response to notify that your work has finished without failure.
workDataOf()
converts a list of pairs to aData
object. AData
object is a set of key/value pairs used as inputs/outputs forListenableWorker
‘s.IMAGE_URI
is a key for identifying the result. You’re going to use it to get the value from this worker.
Now, you might see some warnings due to missing imports. At the top of the file, add:
import androidx.work.workDataOf
import com.raywenderlich.android.workmanager.utils.getUriFromUrl
import kotlinx.coroutines.delay
You have defined your work. Next, you’ll create a WorkRequest
to run the work.
Creating Your WorkRequest
For your scheduled work to run, the WorkManager service has to schedule it. A WorkRequest
contains information of how and when your work will run.
You can schedule your work to run either periodically or once.
For running work periodically, you’ll use a PeriodicWorkRequestBuilder
. For one-time work, you’ll use OneTimeWorkRequestBuilder
.
Creating a One-Time WorkRequest
To create a one-time request, you’ll begin by adding WorkManager initialization. Navigate to HomeActivity.kt. Add this at the top class definition:
private val workManager by lazy {
WorkManager.getInstance(applicationContext)
}
Here, you create an instance of WorkManager as a top-class variable. Members of HomeActivity
can use this WorkManager instance. Be sure to add the WorkManager import when prompted to do so.
Second, add the following method below onCreate
:
private fun createOneTimeWorkRequest() {
// 1
val imageWorker = OneTimeWorkRequestBuilder<ImageDownloadWorker>()
.setConstraints(constraints)
.addTag("imageWork")
.build()
// 2
workManager.enqueueUniqueWork(
"oneTimeImageDownload",
ExistingWorkPolicy.KEEP,
imageWorker
)
}
In the code above, you:
- Create your
WorkRequest
. You also set constraints to the work. Additionally, you add a tag to make your work unique. You can use this tag to cancel the work and observe its progress, too. - Finally, you submit your work to WorkManager by calling
enqueueUniqueWork
.
You might see some warnings due to missing imports. At the top of the file, add:
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import com.raywenderlich.android.workmanager.workers.ImageDownloadWorker
Still, you’ll see the constraints
is still highlighted in red. To resolve this, add the following code below your WorkManager initialization at the top of the class:
private val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setRequiresBatteryNotLow(true)
.build()
The following constraints are set for work to run. The device must have:
- An active network connection
- Enough storage
- Enough battery
Inside onCreate
, add the following code below requestStoragePermissions()
:
activityHomeBinding.btnImageDownload.setOnClickListener {
showLottieAnimation()
activityHomeBinding.downloadLayout.visibility = View.GONE
createOneTimeWorkRequest()
}
In the code above, you call createOneTimeWorkRequest()
when you tap START IMAGE DOWNLOAD. You’re also showing the animation and hiding the layout as you wait for your work to complete.
Build and run the app. The UI remains the same.
Tap START IMAGE DOWNLOAD. Notice that the animation doesn’t stop. Don’t worry, you’ll fix this behavior in the next steps.