Sparround

WorkManager & background work

WorkManager is Jetpack's solution for deferrable, guaranteed background work: the work runs even if the app closes or the device restarts (tasks persist in a DB).

Core concepts:

  • Worker / CoroutineWorker — the work itself: doWork() returns Result.success() / failure() / retry().
  • WorkRequest — OneTimeWorkRequest / PeriodicWorkRequest (minimum interval 15 minutes).
  • Constraints — network available, battery not low, charging, etc.
  • Backoff policy — LINEAR/EXPONENTIAL delays between retries.
  • Unique workenqueueUniqueWork(name, ExistingWorkPolicy.KEEP/REPLACE/APPEND) — deduplication.
  • ChainingbeginWith(A).then(B).enqueue() — sequential/parallel chains.

When not WorkManager: work needed immediately (coroutines), work continuing while the user watches (foreground service), server-triggered work (FCM push). For exact-time execution, AlarmManager (setExactAndAllowWhileIdle).

kotlin
class UploadAuditLogsWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result = try {
        val logs = logStore.pending()
        api.uploadLogs(logs)
        logStore.markUploaded(logs)
        Result.success()
    } catch (e: IOException) {
        if (runAttemptCount < 5) Result.retry() else Result.failure()
    }
}

val request = OneTimeWorkRequestBuilder<UploadAuditLogsWorker>()
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()
    )
    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
    .build()

WorkManager.getInstance(context).enqueueUniqueWork(
    "upload_audit_logs",
    ExistingWorkPolicy.KEEP,     // artıq növbədədirsə, təzəsini əlavə etmə
    request
)

Guaranteed log upload: constraints + retry + uniqueness

The classic comparison question — "how would you do sync?": immediate + user waiting → coroutine + repository; deferrable + guaranteed (logs, analytics, backup) → WorkManager; exact time (a payment reminder) → AlarmManager; the server knows when (a new transaction) → FCM push handing off to WorkManager. Know Doze mode too: on a sleeping device WorkManager runs in maintenance windows — no timing guarantees.

🛠 Practice task

Write a CoroutineWorker: a network constraint (NetworkType.CONNECTED) + exponential backoff + enqueueUniqueWork(KEEP).

  • Enqueue it in airplane mode, fully close the app, then re-enable the network — log that the work runs on its own.
  • Return Result.retry() and watch runAttemptCount climb.
  • Tap a sync button 5 times and confirm via WorkManager.getWorkInfos that KEEP left exactly one job.

Done when: you have a decision tree ready for "WorkManager, foreground service or AlarmManager?".

📚 Sources and documentation