Working with WorkManager
1. Setting Up WorkManager Dependencies
| Dependency | Version | Notes |
|---|---|---|
| androidx.work:work-runtime | 2.9.0 |
Core WorkManager library |
| androidx.work:work-runtime-ktx | 2.9.0 |
Kotlin coroutines support |
| androidx.work:work-testing | 2.9.0 |
For testing WorkManager in unit tests |
Example: Gradle Dependencies
dependencies {
implementation "androidx.work:work-runtime:2.9.0"
implementation "androidx.work:work-runtime-ktx:2.9.0"
}
2. Creating Worker Classes
Worker classes in Android's WorkManager are responsible for executing background tasks. They extend the Worker class and implement the doWork() method.
| Property | Description | Values |
|---|---|---|
| Worker | Base class for all worker implementations | androidx.work.Worker |
| doWork() | Method that executes the background task | Returns ListenableWorker.Result |
Example: Basic Worker Class
import androidx.work.Worker
import androidx.work.WorkerParameters
class MyWorker(
context: Context,
params: WorkerParameters
) : Worker(context, params) {
override fun doWork(): Result {
// Perform background task here
return Result.success()
}
}
Example: Worker with Input Parameters
import androidx.work.Worker
import androidx.work.WorkerParameters
class DataProcessingWorker(
context: Context,
params: WorkerParameters
) : Worker(context, params) {
override fun doWork(): Result {
val inputData = inputData
val data = inputData.getString("input_data")
// Process the data
val processedData = data?.uppercase()
return Result.success(
workDataOf("output_data" to processedData)
)
}
}
3. Creating CoroutineWorker
A CoroutineWorker is a type of Worker that runs using Kotlin coroutines, making it ideal for asynchronous operations that need to be executed in the background.
| Property | Description | Values |
|---|---|---|
| context | The application context | Context |
| params | Parameters passed to the worker | WorkerParameters |
Example: Creating a CoroutineWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import kotlinx.coroutines.delay
class MyCoroutineWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// Simulate a background task
delay(2000)
return Result.success()
}
}
doWork() method must be marked as suspend because it runs in a coroutine.
Key Points
CoroutineWorkeris ideal for asynchronous tasks that use Kotlin coroutines.- It must override the
doWork()method and mark it assuspend. - It can return
Result.success(),Result.failure(), orResult.retry().
4. Building WorkRequest
A WorkRequest is the fundamental unit of work in Android's WorkManager. It defines what work to do, when to do it, and how to handle it.
| Property | Description | Values |
|---|---|---|
| workSpec | Defines the work's constraints, backoff policy, and retry logic. | WorkSpec |
| inputData | Data to pass into the work. | Data |
| outputData | Data returned by the work. | Data |
| initialDelay | Time to wait before starting the work. | Duration |
| backoffPolicy | How to handle retries if the work fails. | BackoffPolicy.LINEAR, BackoffPolicy.EXPONENTIAL |
| constraints | Conditions that must be met for the work to run. | Constraints |
Example: Creating a OneTimeWorkRequest
val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
.setInputData(workData)
.setInitialDelay(10, TimeUnit.MINUTES)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
10, TimeUnit.MINUTES
)
.build()
Example: Creating a PeriodicWorkRequest
val periodicWorkRequest = PeriodicWorkRequestBuilder<MyWorker>(15, TimeUnit.MINUTES)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.NOT_ROAMING)
.build()
)
.build()
WorkRequest is immutable once built. Use WorkManager to enqueue and manage it.
5. Setting Work Constraints
| Constraint | Description | Usage |
|---|---|---|
| NetworkType | Defines the network type required for work to run. | NetworkType.NOT_REQUIRED, NetworkType.UNMETERED, NetworkType.METERED |
| BatteryNotLow | Work runs only when the battery is not low. | true or false |
| StorageNotLow | Work runs only when storage is not low. | true or false |
| DeviceIdle | Work runs only when the device is idle. | true or false |
| Charging | Work runs only when the device is charging. | true or false |
Example: Setting Network Constraint
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.build()
Example: Combining Multiple Constraints
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.METERED)
.setRequiresCharging(true)
.setRequiresDeviceIdle(true)
.build()
6. Chaining Work Requests
WorkManager allows you to chain work requests so that one or more works are executed after another. This is useful for creating sequences of operations that must be performed in a specific order.
| Method | Description | Usage |
|---|---|---|
| beginWith() | Starts a chain with one or more work requests | WorkManager.getInstance(context).beginWith(workRequestA) |
| then() | Adds a work request to the chain | WorkManager.getInstance(context).beginWith(workRequestA).then(workRequestB) |
| enqueue() | Enqueues the entire chain | WorkManager.getInstance(context).beginWith(workRequestA).then(workRequestB).enqueue() |
Example: Chaining Work Requests
val workRequestA = OneTimeWorkRequestBuilder<WorkerA>().build()
val workRequestB = OneTimeWorkRequestBuilder<WorkerB>().build()
WorkManager.getInstance(context)
.beginWith(workRequestA)
.then(workRequestB)
.enqueue()
7. Observing Work Status
| Property | Description | Notes |
|---|---|---|
| workInfo | Represents the current status of a work request | Includes state, progress, and output data |
| state | Current execution state of the work | Can be ENQUEUED, RUNNING, SUCCEEDED, FAILED, CANCELLED |
| progress | Progress of ongoing work | Can be updated using WorkManager.setProgress() |
| outputData | Data produced by the work once completed | Accessible via WorkInfo.outputData |
Example: Observing Work Status
val workManager = WorkManager.getInstance(context)
val workInfoLiveData = workManager.getWorkInfoByIdLiveData(workId)
workInfoLiveData.observe(lifecycleOwner) { workInfos ->
for (workInfo in workInfos) {
when (workInfo.state) {
WorkInfo.State.ENQUEUED -> {
// Work is queued
}
WorkInfo.State.RUNNING -> {
// Work is currently running
val progress = workInfo.progress
}
WorkInfo.State.SUCCEEDED -> {
// Work completed successfully
val output = workInfo.outputData
}
WorkInfo.State.FAILED -> {
// Work failed
}
WorkInfo.State.CANCELLED -> {
// Work was cancelled
}
}
}
}
8. Passing Data to Workers
WorkManager allows you to pass data to workers using DataStore or WorkerParameters. The data is typically passed as key-value pairs using Data objects.
| Property | Type | Description |
|---|---|---|
| inputData | Data |
Input data provided to the worker when it is enqueued. |
| outputData | Data |
Data returned by the worker after execution. |
Example: Passing Data to Worker
class MyWorker(
context: Context,
params: WorkerParameters
) : Worker(context, params) {
override fun doWork(): Result {
val input = inputData.getString("message")
val output = "Processed: $input"
return Result.success(
workDataOf("result" to output)
)
}
}
Example: Enqueuing Worker with Data
val data = workDataOf(
"message" to "Hello from WorkManager"
)
val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
.setInputData(data)
.build()
WorkManager.getInstance(context).enqueue(workRequest)
9. Handling Work Failures
| Failure Type | Description | Handling Strategy |
|---|---|---|
| Worker Failure | When a Worker throws an exception or returns WorkerResult.Failure |
Use BackoffPolicy.LINEAR or BACKOFF_POLICY.EXPONENTIAL with retry limits |
| Work Constraints Not Met | When required constraints (e.g., network, battery) are not satisfied | WorkManager will retry when constraints are met; use setConstraints() to define conditions |
| Work Timeout | When a Worker exceeds its allowed runtime (default 10 minutes) | Use setRuntime() to increase timeout or optimize worker logic |
Example: Retry Logic with Backoff
class RetryableWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
return try {
// Simulate work that might fail
performTask()
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
private fun performTask() {
// Simulate task that may fail
if (Math.random() < 0.3) throw Exception("Random failure")
}
}
val workRequest = OneTimeWorkRequestBuilder<RetryableWorker>()
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10L, TimeUnit.SECONDS)
.build()
setExpedited is used. Use setConstraints() to define retry conditions based on network or battery state.
Failure Handling Best Practices
- Implement
Result.retry()for transient failures - Use exponential backoff to avoid overwhelming the system
- Set appropriate timeouts to prevent indefinite hanging
- Log failures for debugging and monitoring
10. Implementing Expedited Work
Expedited work in Android's WorkManager allows you to prioritize and execute tasks immediately, bypassing the normal scheduling queue. This is particularly useful for time-sensitive operations like push notifications or critical data syncs.
| Property | Description | Values |
|---|---|---|
| isExpedited | Flag indicating whether the work should be expedited | true or false |
| workRequest | The type of work request to be expedited | OneTimeWorkRequest, PeriodicWorkRequest |
Example: Scheduling Expedited Work
val expeditedWork = OneTimeWorkRequestBuilder<MyWorker>()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK)
.build()
WorkManager.getInstance(context).enqueue(expeditedWork)
11. Canceling Work
| Method | Description | Notes |
|---|---|---|
| cancelWorkById() | Cancel work by its unique ID | Works for both enqueued and scheduled work |
| cancelWorkByTag() | Cancel all work with a specific tag | Useful for canceling groups of related work |
| cancelAllWork() | Cancel all currently enqueued work | Does not cancel scheduled or ongoing work |
Example: Canceling Work by ID
val workManager = WorkManager.getInstance(context)
val workId = UUID.randomUUID()
val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
.setId(workId)
.build()
workManager.enqueue(workRequest)
// Cancel the work by ID
workManager.cancelWorkById(workId)
Example: Canceling Work by Tag
val workManager = WorkManager.getInstance(context)
val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
.setTags(setOf("important", "urgent"))
.build()
workManager.enqueue(workRequest)
// Cancel all work tagged as "important"
workManager.cancelWorkByTag("important")
12. Using Unique Work
| Property | Description | Values |
|---|---|---|
| workRequest.uniqueWorkName | Name of the unique work | String |
| workRequest.isUnique | Flag to mark work as unique | Boolean |
| workRequest.replace | Replace existing work with same name | Boolean |
Example: Unique Work Request
val uniqueWork = OneTimeWorkRequestBuilder<MyWorker>()
.setInputData(workDataOf("key" to "value"))
.build()
WorkManager.getInstance(context)
.beginUniqueWork("unique_work_name", ExistingWorkPolicy.REPLACE, uniqueWork)
.enqueue()
ExistingWorkPolicy.REPLACE replaces any existing work with the same name.