Android WorkManager
WorkManager is the recommended Jetpack library for deferrable (able to be delayed, postponed, or put off until a later time), guaranteed background work that must execute even if the app exits or the device restarts.
It is particularly useful when the work has requirements such as:
- Network availability
- Charging
- Device idle conditions
- Retry after failure
- Background synchronization
- Deferrable work
WorkManager allows you to say: Android, please execute this work when the required conditions are satisfied.
The important word is reliable.
It is not simply: Run this code in the background right now.
It is: Schedule this work, and let Android decide the appropriate time and conditions to execute it.
- Why do we need a WorkManager?
Android controls background execution very aggressively because unrestricted background work can:
- Drain battery
- Consume network data
- Consume CPU
- Keep the device awake
- Affect other applications
2. Imagine an application continuously running
This would be terrible for battery and system performance. Instead, Android provides controlled background execution mechanisms.
WorkManager is particularly useful when the work is deferrable and should eventually complete.
User creates order → Save order locally → Internet unavailable → Wait → Internet available → WorkManager runs → Upload order
This is one of the most important WorkManager use cases for system design.
The book specifically describes WorkManager as a mechanism for background sync and for allowing the OS to batch and schedule work under suitable conditions.
3. WorkRequest Type
WorkManager revolves around a concept called a WorkRequest. Think of a WorkRequest as an instruction: “I want this particular background task to run.”
There are two important types.
- OneTimeWorkRequest: Used when you want the work to execute once.
Example: Upload photo
You want: Upload → Finish
Not repeatedly.
2. PeriodicWorkRequest: Used when you want the task to run repeatedly.
Example:
Sync data
Every day
Every few hours
Regular database cleanup
Periodic sync
val request = PeriodicWorkRequestBuilder<SyncWorker>( 1, TimeUnit.DAYS).build()
WorkManager.getInstance(context).enqueue(request)
Important: periodic WorkManager work is for approximate scheduling, not exact alarm-clock timing.
Conceptually:
Sync → Wait → Sync → Wait → Sync
Periodic work is useful for recurring maintenance or synchronization, but it is not a precise alarm mechanism. Android controls when the work actually runs.
- WorkManager = Manager → decides when/under what conditions work runs.
- Worker = Actual worker → defines what work needs to be done.
- Example: WorkManager → SyncWorker / UploadWorker / CleanupWorker
WorkManager decides when and under what conditions the work should execute. The Worker defines what the work actually does.
4. Constraints
You can tell WorkManager: “Don’t execute this work unless certain conditions are satisfied.”
For example:
- Network required
- Charging required
- Battery not low
- Storage not low
1. Network required
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
Meaning: Run the Worker only when the device has a network connection.
For Wi-Fi/unmetered network:
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED).build()
2. Charging required
val constraints = Constraints.Builder().setRequiresCharging(true).build()
Meaning: Run the Worker only when the device is connected to a charger.
Example: Large file backup or video upload.
3. Battery not low
val constraints = Constraints.Builder().setRequiresBatteryNotLow(true).build()
Meaning: Run the Worker only when the battery is not considered low.
Example: Processing a large number of images.
4. Storage not low
val constraints = Constraints.Builder().setRequiresStorageNotLow(true).build()
Meaning: Run the Worker only when device storage is not considered low.
Example: Downloading or generating large files.
5. Multiple constraints together
You can combine them:
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(true)
.setRequiresBatteryNotLow(true)
.setRequiresStorageNotLow(true)
.build()
This means:
Network available ✅
Device charging ✅
Battery not low ✅
Storage not low ✅
Get Deepak’s stories in your inbox
Join Medium for free to get updates from this writer.
↓
Worker can run
If any required condition is not satisfied, WorkManager waits until the condition becomes satisfied.
5. Backoff:
When a Worker returns Result.retry(), you usually don’t want WorkManager to retry continuously. Instead, you can configure a backoff policy. With exponential backoff, the waiting time increases between retries.
val request = OneTimeWorkRequestBuilder<UploadWorker>()
.setBackoffCriteria( BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS )
.build()
10 sec → 20 sec → 40 sec → 80 sec
The exact scheduling is controlled by WorkManager/Android, so don’t treat these as precise execution times.
6. Input Data: You can pass data to the Worker.
val data = workDataOf( “imageId” to “123”)
val request = OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(data)
.build()
Inside Worker:
val imageId = inputData.getString(“imageId”)
ViewModel → imageId = 123 → WorkRequest → Worker -> inputData → Upload image 123
7. Output Data: Workers can also return small output data.
return Result.success( workDataOf( “uploaded” to true))
You can observe the WorkInfo and read the output.
Observe WorkManager: With modern Kotlin code, you can also use Flow-based APIs.
workManager.getWorkInfoByIdLiveData(request.id)
.observe(this) { workInfo ->
when (workInfo.state) {
WorkInfo.State.SUCCEEDED -> {
// Done
}
WorkInfo.State.FAILED -> {
// Failed
}
WorkInfo.State.RUNNING -> {
// Running
}
else -> {}
}
}
WorkInfo:
WorkInfo provides information about a WorkRequest. You can use it to check whether the work is running, completed, failed, or cancelled. It can also provide information such as output data and progress.
For example, your UI could display: Uploading… → Upload completed
by observing the WorkManager state.
8. WorkManager states:
Every piece of WorkManager work has a state. Common states include ENQUEUED, RUNNING, SUCCEEDED, FAILED, CANCELLED, and BLOCKED.
ENQUEUED → RUNNING → SUCCEEDED
ENQUEUED → RUNNING → FAILED
ENQUEUED → RUNNING → RETRY → ENQUEUED → RUNNING
9. Parallel Work:
This is useful when independent jobs can happen at the same time.
10. Unique Work:
Imagine the user taps Sync four times. Without proper handling, you could end up with multiple sync operations running.
Sync Sync Sync Sync
workManager.enqueueUniqueWork( “data_sync”, ExistingWorkPolicy.KEEP, request)
“data_sync” becomes the unique name of that job. KEEP tells WorkManager to keep the existing work instead of creating another one when equivalent unique work is already pending/running.
ExistingWorkPolicy controls what happens when unique work with the same name already exists.
The important policies are:
KEEP — Keep the existing work and ignore the new request.
REPLACE — Cancel/replace the existing work with the new request.
APPEND — Add the new work after existing work in the chain.
APPEND_OR_REPLACE — Append when possible, otherwise start a new chain.
Choosing the policy depends on your business requirement.
11. Work Chaining:
Sometimes one background task depends on another task.
Download ⇒ Process ⇒ Upload
workManager
.beginWith(downloadRequest)
.then(processRequest)
.then(uploadRequest)
.enqueue()
Here, ProcessWorker waits for the previous work, and UploadWorker waits for processing. This is useful when background operations have a specific dependency order.
12. CoroutineWorker: CoroutineWorker is particularly useful for Kotlin applications because its doWork() method is suspendable.
class SyncWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
repository.syncData()
return Result.success()
}
}
You can call suspend functions from the Worker without manually creating threads. This makes it convenient for Retrofit network calls, Room operations, and other asynchronous Kotlin operations.
13. WorkManager + Offline-First Architecture:
WorkManager is especially useful for an offline-first application. Imagine the user creates a post without internet:
User creates post → Save post in Room → Mark status = PENDING → Schedule WorkManager → Internet becomes available → UploadWorker → Upload to server → Update Room → status = SYNCED
This is a very common real-world architecture for social media, banking, logistics, and e-commerce applications.
14. NOTE:
- If the app is killed: WorkManager keeps the scheduled work and can run/reschedule it later.
- If the phone restarts: WorkManager can restore eligible scheduled work and execute it again.
- Key point: WorkManager provides persistent, OS-managed background work, unlike a normal thread/coroutine that can stop when the app process dies.
- WorkManager provides reliable scheduling for appropriate background work; it does not give your app unlimited background execution.
- Process is killed: The application process can disappear, but WorkManager’s persistent scheduling allows eligible work to be rescheduled rather than relying only on an in-memory thread or coroutine. This is why WorkManager is better suited than a simple Thread for work that must be reliably scheduled.
- Device restart: WorkManager is designed to persist scheduled work and can restore/reschedule eligible work after a device restart. This means you don’t generally need to manually create your own boot-receiver-based scheduling system for ordinary persistent background work.
- WorkManager + Repository: The Worker acts as the background entry point, while the Repository handles the actual data operation. This keeps the code easier to test, maintain, and reuse.





