Workers and WorkRequests
What WorkManager Does
WorkManager is for work that must run eventually — even if the app exits or the device restarts. It is not for immediate work; use coroutines for that. WorkManager handles:
- Guaranteed execution: work persists and runs when constraints are met
- Backward compatibility: uses JobScheduler on API 23+, AlarmManager + BroadcastReceiver on older APIs
- Observable work: track status, progress, and results
Defining a Worker
class SyncWorker(
context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
val userId = inputData.getString("userId") ?: return Result.failure()
return try {
repository.syncUserData(userId)
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
CoroutineWorker gives you a suspend context. doWork() returns:
Result.success()— work completedResult.failure()— permanent failureResult.retry()— reschedule with backoff
Enqueueing Work
val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(workDataOf("userId" to "user_123"))
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
WorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
WorkManager.getInstance(context).enqueue(syncRequest)
Periodic Work
For recurring tasks like daily sync:
val periodicSync = PeriodicWorkRequestBuilder<DailySyncWorker>(
1, TimeUnit.HOURS, // repeat interval
15, TimeUnit.MINUTES // flex interval
).build()
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(
"daily_sync",
ExistingPeriodicWorkPolicy.KEEP,
periodicSync
)
The flex interval defines when the work can run within the repeat interval. Work runs once during the last 15 minutes of each hour.
Chaining, Tags, and Observing
Work Chaining
Chain work to run sequentially. Each worker receives the output of the previous one as input:
WorkManager.getInstance(context)
.beginWith(downloadWork)
.then(processWork)
.then(uploadWork)
.enqueue()
For parallel branches that converge:
val parallelWork = WorkManager.getInstance(context)
.beginWith(downloadImages)
.then(downloadVideos)
.enqueue()
WorkManager.getInstance(context)
.beginWith(parallelWork)
.then(mergeWork)
.enqueue()
Tags
Tag work for bulk operations:
val uploadWork = OneTimeWorkRequestBuilder<UploadWorker>()
.addTag("upload")
.addTag("media")
.build()
// Cancel all work with a tag
WorkManager.getInstance(context).cancelAllWorkByTag("upload")
// Observe all work with a tag
WorkManager.getInstance(context)
.getWorkInfosByTagLiveData("upload")
.observe(lifecycleOwner) { workList ->
val running = workList.any { it.state == WorkInfo.State.RUNNING }
}
Observing Work Status
WorkManager.getInstance(context)
.getWorkInfoByIdLiveData(syncRequest.id)
.observe(lifecycleOwner) { workInfo ->
when (workInfo?.state) {
WorkInfo.State.ENQUEUED -> { /* waiting */ }
WorkInfo.State.RUNNING -> { /* executing */ }
WorkInfo.State.SUCCEEDED -> {
val result = workInfo.outputData.getString("result")
}
WorkInfo.State.FAILED -> { /* permanent failure */ }
WorkInfo.State.CANCELLED -> { /* cancelled */ }
else -> {}
}
}
Unique Work
Prevent duplicate work by name:
// Only one sync can run at a time
WorkManager.getInstance(context)
.enqueueUniqueWork(
"user_sync",
ExistingWorkPolicy.REPLACE, // cancel previous, run new
syncRequest
)
// Options: KEEP (skip new), REPLACE (cancel old), APPEND (queue after)
Input and Output Data
Workers communicate through Data objects (key-value pairs limited to primitives):
class ProcessWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val inputUrl = inputData.getString("url") ?: return Result.failure()
val processed = processUrl(inputUrl)
val outputData = workDataOf("filePath" to processed)
return Result.success(outputData)
}
}
Chained workers automatically pass output as input to the next worker.
Quiz
1. When should you use WorkManager instead of coroutines?
2. What does Result.retry() do in a CoroutineWorker?
3. What is the difference between ExistingWorkPolicy.REPLACE and ExistingWorkPolicy.KEEP?
4. What is the maximum repeat interval for PeriodicWorkRequest?
Flashcards
Question
What is the difference between OneTimeWorkRequest and PeriodicWorkRequest?
Click to reveal answer
Answer
OneTimeWorkRequest runs once. PeriodicWorkRequest repeats at a fixed interval with a minimum of 15 minutes. Both support constraints and chaining.
Question
When should you use enqueueUniqueWork vs enqueueUniquePeriodicWork?
Click to reveal answer
Answer
enqueueUniqueWork for one-time unique work (with REPLACE/KEEP/APPEND policies). enqueueUniquePeriodicWork for repeating unique work with the same policies.
Question
How do Workers communicate data to each other in a chain?
Click to reveal answer
Answer
Through input and output Data objects. The output of one worker becomes the input of the next worker in the chain.
Question
What constraints can you set on a WorkRequest?
Click to reveal answer
Answer
Network type, battery level, storage level, charging state, idle state, and device charging. Set via Constraints.Builder().
Revision Notes
Key Takeaways
- 1. WorkManager guarantees eventual execution even after app exit or device restart
- 2. Use CoroutineWorker for suspend-based background work with Result.success/failure/retry
- 3. Chain work sequentially with beginWith().then() for multi-step workflows
- 4. Use enqueueUniqueWork to prevent duplicate executions by name
- 5. PeriodicWorkRequest has a minimum repeat interval of 15 minutes
Interview Tips
- • Explain when WorkManager is appropriate vs using coroutines directly
- • Discuss work chaining and how data flows between workers
- • Know the difference between REPLACE, KEEP, and APPEND policies
- • Be ready to design a background sync system with constraints and retry
Cheat Sheet
WorkManager Cheat Sheet
Worker Types:
CoroutineWorker— suspend-based, preferredRxWorker— RxJava-basedWorker— blocking, legacy
WorkRequest:
OneTimeWorkRequestBuilder<T>()— run oncePeriodicWorkRequestBuilder<T>(interval, unit)— repeat (min 15 min).setInputData(workDataOf(...))— pass data to worker.setConstraints(...)— set execution conditions.addTag("tag")— tag for bulk operations
Enqueueing:
.enqueue(request)— fire and forget.enqueueUniqueWork(name, policy, request)— prevent duplicates.enqueueUniquePeriodicWork(name, policy, request)— periodic unique
Policies:
ExistingWorkPolicy.KEEP— skip if existsExistingWorkPolicy.REPLACE— cancel existing, run newExistingWorkPolicy.APPEND— queue after existing
Chaining:
.beginWith(a).then(b).then(c)— sequential- Parallel branches: multiple
.beginWith()calls - Output of one worker = input of next
Result:
Result.success(data)— doneResult.failure(data)— permanent failureResult.retry()— reschedule with backoff