Skip to main content

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"
}
Note: Ensure you are using the latest stable version of WorkManager to benefit from bug fixes and new features.

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()
    }
}
Note: The doWork() method must be marked as suspend because it runs in a coroutine.

Key Points

  • CoroutineWorker is ideal for asynchronous tasks that use Kotlin coroutines.
  • It must override the doWork() method and mark it as suspend.
  • It can return Result.success(), Result.failure(), or Result.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()
Note: 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()
Note: The chaining is linear and sequential. Each work request in the chain will only start once the previous one has completed successfully.

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
            }
        }
    }
}
Note: WorkInfo objects are immutable and updated asynchronously. Always observe the LiveData to get real-time updates.

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()
Note: WorkManager does not automatically retry failed work if 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)
Note: Expedited work is only available on Android 12 (API level 31) and higher. On lower versions, it will fall back to regular work scheduling.
Warning: Using expedited work excessively can lead to battery drain and performance issues. It should be used sparingly for truly urgent tasks.

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()
Note: ExistingWorkPolicy.REPLACE replaces any existing work with the same name.
Warning: Unique work names must be globally unique across the app.

Was this article helpful?