How to Use WorkManager in Android (Step-by-Step with Kotlin)

How to Use WorkManager in Android (Step-by-Step with Kotlin)


Want a reliable way to run background tasks that survive app restarts and device reboots? This Android WorkManager tutorial for beginners walks you through exactly how to use WorkManager in Android with modern Kotlin + Jetpack Compose, plus a small Java example. You’ll learn when to use it, how to set it up, build OneTime and Periodic requests, chain tasks, add constraints (Wi‑Fi, charging), observe progress/results, and avoid common mistakes. By the end, you’ll be comfortable scheduling background tasks in Android with WorkManager, step by step.

What is WorkManager in Android?

How to use WorkManager in Android: chained workers passing data with retries, delays, and parallel then combine flow
Chaining workers with input/output data and retries for reliable background processing.

WorkManager is a Jetpack library for deferrable, persistent background work. “Persistent” means the system will reschedule your work even if the app process is killed or the device restarts. It’s the recommended way for most background operations that don’t need exact timing and can run under constraints like “only on Wi‑Fi” or “only when charging.”

  • Guarantees execution under the constraints you specify
  • Survives process death and device reboot
  • Each worker has a hard execution window of about 10 minutes
  • Kotlin-first APIs with CoroutineWorker, progress reporting, and foreground support if needed

As of July 15, 2026, the latest stable WorkManager is androidx.work 2.11.2 (requires compileSdk 33+). See the official release notes for details.

When should I use WorkManager vs. other options?

  • Use WorkManager for deferrable, guaranteed background processing (sync, upload, cleanup, backups) that should run even if the app or device restarts.
  • Use platform‑specific APIs when they fit better:
    • DownloadManager for large or user‑visible downloads
    • FCM for push messages (and trigger WorkManager from the message if work is needed)
  • Use a Foreground Service only for long, user‑visible tasks (ongoing location, media playback). Android 14+ tightened foreground‑service rules, so many background cases should migrate to WorkManager or user‑initiated data transfer APIs.
  • Not for exact timing: WorkManager is not a precise scheduler; the system chooses the best time to run.

WorkManager vs other Android background options (quick comparison)

Criteria WorkManager Foreground Service AlarmManager (exact) JobScheduler
Best for Deferrable, guaranteed background tasks with constraints Long, user‑visible operations (must show notification) Precise alarms/one‑off triggers at exact times Lower‑level scheduled work (API 21+), fewer conveniences
Timing Not exact; system optimizes execution Immediate while service is running Exact (if allowed), or inexact otherwise Not exact; system scheduled
Persists across reboot Yes (rescheduled automatically) No (you must restart it explicitly) Yes, if you re‑set alarms after boot Yes (persisted jobs)
User notification required Only for foreground work in a Worker Yes, mandatory while running No No
Minimum repeat interval 15 minutes (PeriodicWork) N/A (continuous while active) N/A (one‑off alarms) Typically 15 minutes for periodic
Chaining / unique work Yes (one‑time chains, unique names, tags) No No No (you implement orchestration yourself)
Beginner‑friendly Yes (Kotlin‑first, higher‑level) Moderate (strict platform rules) Low for background workflows Lower‑level API

Project setup (2026‑ready)

Add the latest dependencies and use compileSdk 33 or higher.

Gradle
android {
compileSdk 34 // or newer (33+ required by androidx.work:2.11.2)
defaultConfig {
minSdk 21
targetSdk 34
}
}

dependencies {
// Kotlin-first runtime
implementation "androidx.work:work-runtime-ktx:2.11.2"

// If you write Java-only workers, you can use:
// implementation "androidx.work:work-runtime:2.11.2"

// Optional: testing helpers
androidTestImplementation "androidx.work:work-testing:2.11.2"
}

That’s it—WorkManager auto‑initializes in most apps. If you use DI (like Hilt), you can plug in a custom WorkerFactory.

How to use WorkManager in Android (step by step, Kotlin + Compose)

WorkManager lifecycle at a glance
1) Define Worker (+ optional Constraints)
2) Enqueue unique work (OneTime / Periodic / expedited)
3) System schedules based on constraints, battery, quotas
4) Worker doWork() runs (can report progress)
5) Return Success / Failure / Retry (+ small output Data)
6) UI observes WorkInfo (state, progress, output)
7) Optionally chain next OneTime work or schedule Periodic

1) Create a Worker

Use CoroutineWorker for Kotlin coroutines. The doWork() method runs off the main thread.

Kotlin
Code
class="cd-package">import android.content.Context
class="cd-package">import androidx.work.CoroutineWorker
class="cd-package">import androidx.work.Data
class="cd-package">import androidx.work.WorkerParameters
class="cd-package">import androidx.work.workDataOf
class="cd-package">import kotlinx.coroutines.delay

class SyncWorker(
    appContext: Context,
    params: WorkerParameters
) : CoroutineWorker(appContext, params) {

    override suspend fun doWork(): Result {
        // Read small inputs (max ~10 KB total via Data)
        val endpoint = inputData.getString("endpoint") ?: return Result.failure()

        try {
            // Report incremental progress (0..100)
            setProgress(workDataOf("progress" to 0))

            // Simulate network sync
            // TODO: replace with your repository call
            repeat(5) { step ->
                delay(500) // simulate work
                setProgress(workDataOf("progress" to(step + 1) * 20))
            }

            // Output small results via Data (≤ ~10 KB)
            val output: Data = workDataOf("itemsSynced" to 42)
            return Result.success(output)
        } catch (e: Exception) {
            // Let WorkManager retry based on backoff if desired
            return Result.retry()
        }
    }
}

Important: Don’t pass large objects (Bitmaps, files) in Data. Use files, Room, or your own storage and return small keys/IDs.

2) Add constraints (Wi‑Fi, charging, etc.)

Constraints make your work smarter and battery‑friendly.

Kotlin
import androidx.work.Constraints
import androidx.work.NetworkType

val wifiAndCharging = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // Wi‑Fi
.setRequiresCharging(true)
.build()

3) Enqueue a OneTimeWorkRequest

Use OneTimeWorkRequest for a single job.

Kotlin
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf

// Build the request with constraints and input
val request = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(wifiAndCharging)
.setInputData(workDataOf("endpoint" to "https://api.example.com/sync"))
.addTag("sync-now")
.build()

// Enqueue as unique work to avoid duplicates
val workManager = WorkManager.getInstance(context)
val workId = workManager.enqueueUniqueWork(
"sync_now",
ExistingWorkPolicy.REPLACE, // or KEEP/APPEND for chains
request
).id

4) Observe status, progress, and result in Jetpack Compose

WorkManager provides LiveData and Flow to observe WorkInfo. In Compose, Flow works great.

Kotlin
Code
class="cd-package">import androidx.compose.runtime.*
class="cd-package">import androidx.compose.material3.*
class="cd-package">import androidx.work.WorkInfo
class="cd-package">import androidx.work.WorkManager
class="cd-package">import kotlinx.coroutines.flow.map

class="cd-annotation">@Composable
fun SyncScreen(context: Context, workId: java.util.UUID?) {
    val workManager = remember { WorkManager.getInstance(context) }

    // Observe this job's WorkInfo via Flow
    val workInfo by remember(workId) {
        if (workId == null) {
            mutableStateOf<WorkInfo?>(null)
        } else {
            workManager.getWorkInfoByIdFlow(workId)
                .map { it }
        }
    }.collectAsState(initial = null)

    val progress = workInfo?.progress?.getInt("progress", 0) ?: 0
    val stateText = workInfo?.state?.name ?: "Idle"

    Column {
        Text("Sync state: $stateText")
        LinearProgressIndicator(progress / 100f)
        Text("Progress: $progress%")

        if (workInfo?.state == WorkInfo.State.SUCCEEDED) {
            val items = workInfo?.outputData?.getInt("itemsSynced", 0) ?: 0
            Text("Items synced: $items")
        }
    }
}

You can hold workId in a ViewModel state after enqueueing to continue observing across recompositions.

5) Run now with expedited work (user‑initiated)

If a user taps “Sync now,” consider expedited work. It runs with higher priority but is quota‑based and may fall back to regular work according to your policy.

Kotlin
import androidx.work.OutOfQuotaPolicy

val expeditedRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(workDataOf("endpoint" to "https://api.example.com/sync"))
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()

WorkManager.getInstance(context).enqueue(expeditedRequest)

Note: Expedited work is not unlimited. On older Android versions, WorkManager may use a foreground service under the hood. On Android 12+, it uses OS expedited jobs with quotas.

6) Chaining tasks (OneTime only)

Chain one‑time requests to build flows like: download → process → upload. Periodic work cannot be part of a chain.

Kotlin
import androidx.work.WorkContinuation
import androidx.work.OneTimeWorkRequestBuilder

val download = OneTimeWorkRequestBuilder<DownloadWorker>().addTag("pipe").build()
val process = OneTimeWorkRequestBuilder<ProcessWorker>().addTag("pipe").build()
val upload = OneTimeWorkRequestBuilder<UploadWorker>().addTag("pipe").build()

WorkManager.getInstance(context)
.beginUniqueWork("pipeline", ExistingWorkPolicy.REPLACE, download)
.then(process)
.then(upload)
.enqueue()

7) Periodic work (15‑minute minimum)

Use PeriodicWorkRequest for repeating jobs like daily cleanup. The repeat interval minimum is 15 minutes. There’s no initial delay; you can optionally set a flex window.

Kotlin
import androidx.work.PeriodicWorkRequestBuilder
import java.util.concurrent.TimeUnit

// Run every 24 hours; system chooses an exact time in the window
val periodicCleanup = PeriodicWorkRequestBuilder<CleanupWorker>(
24, TimeUnit.HOURS
)
// .setConstraints(wifiAndCharging) // if desired
// .setFlex(1, TimeUnit.HOURS) // optional flexibility window
.addTag("cleanup")
.build()

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"daily_cleanup",
androidx.work.ExistingPeriodicWorkPolicy.KEEP, // or UPDATE/CANCEL_AND_REENQUEUE
periodicCleanup
)

Remember: Periodic work cannot be chained and will not run more frequently than every 15 minutes.

8) Foreground work for long, user‑visible operations

If your task is long and must show a notification (e.g., user‑visible file processing), use the foreground APIs inside the Worker.

Kotlin
Code
class="cd-package">import androidx.core.app.NotificationCompat
class="cd-package">import androidx.work.ForegroundInfo

override suspend fun getForegroundInfo(): ForegroundInfo {
    val notification = NotificationCompat.Builder(applicationContext, "sync_channel")
        .setSmallIcon(android.R.drawable.stat_sys_download)
        .setContentTitle("Sync in progress")
        .setContentText("Please wait…")
        .setOngoing(true)
        .build()
    return ForegroundInfo(1001, notification)
}

override suspend fun doWork(): Result {
    setForeground(getForegroundInfo()) // Call early
    // ... long-running user-visible work
    return Result.success()
}

Choose an appropriate foreground service type in your manifest when required by platform rules.

WorkManager Java example (classic view button + Worker)

If you’re starting from Java/XML, here’s a minimal example.

Java
Code
class="cd-keyword cd-access">public class LogWorker extends androidx.work.Worker {
    class="cd-keyword cd-access">public LogWorker(class="cd-annotation">@NonNull Context context, class="cd-annotation">@NonNull WorkerParameters params) {
        super(context, params);
    }

    class="cd-annotation">@NonNull class="cd-annotation">@Override
    class="cd-keyword cd-access">public Result doWork() {
        android.util.Log.d("LogWorker", "Background job running");
        return Result.success(new androidx.work.Data.Builder()
                .putString("result", "ok")
                .build());
    }
}
Java
Code
// In Activity or Fragment (Java)
OneTimeWorkRequest request =
        new OneTimeWorkRequest.Builder(LogWorker.class).build();

WorkManager wm = WorkManager.getInstance(this);
UUID id = wm.enqueueUniqueWork("log_once",
        ExistingWorkPolicy.REPLACE, request).getId();

wm.getWorkInfoByIdLiveData(id).observe(this, workInfo -> {
    if (workInfo != null) {
        Log.d("WM", "State: " + workInfo.getState());
        if (workInfo.getState() == WorkInfo.State.SUCCEEDED) {
            String result = workInfo.getOutputData().getString("result");
            Log.d("WM", "Result: " + result);
        }
    }
});

How to chain tasks and set constraints in WorkManager

Quick checklist for robust pipelines:

  • Create each step as a CoroutineWorker and keep it idempotent (safe to retry).
  • Use Data only for small keys/values and pass file URIs or DB IDs for large data.
  • Apply Constraints to each OneTimeWorkRequest as needed (network, charging).
  • Use beginUniqueWork + ExistingWorkPolicy (KEEP, REPLACE, APPEND) to prevent duplicates.
  • Observe progress/output from the last step’s WorkInfo.

WorkManager vs JobScheduler for beginners

  • WorkManager is a higher-level API that internally uses JobScheduler (and AlarmManager/ForegroundService on older devices) to give you a consistent, Kotlin‑friendly interface with chaining, constraints, unique work, and guaranteed execution semantics.
  • JobScheduler is lower-level and limited to API 21+. You’d often re‑implement features WorkManager already provides.

Common WorkManager errors and how to fix them

  • Problem: “My task didn’t run exactly at the time I set.”
    Fix: WorkManager is not an exact scheduler. If you need precision, use AlarmManager+exact alarms (if allowed) or a foreground service for continuous, user‑visible work.
  • Problem: “Periodic work every 5 minutes doesn’t start.”
    Fix: Minimum is 15 minutes for PeriodicWorkRequest. Choose ≥ 15 minutes.
  • Problem: “Chaining periodic with one‑time work fails.”
    Fix: Periodic work cannot be in a chain. Chain OneTimeWorkRequest only.
  • Problem: “Crash or failure when passing large data to Worker.”
    Fix: Data is limited to about 10 KB serialized. Store large payloads in files/DB and pass URIs/IDs.
  • Problem: “Expedited jobs don’t always run immediately.”
    Fix: Expedited execution is quota‑based. Use setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) to gracefully fall back.
  • Problem: “My work stops when the app is swiped away.”
    Fix: WorkManager persists work; ensure you aren’t tying cancellation to a lifecycle. Use unique work and let the system reschedule after process death.
  • Problem: “Network tasks fail on metered data.”
    Fix: Add proper Constraints (e.g., UNMETERED for Wi‑Fi) and handle retries with backoff.

WorkManager pre‑flight checklist (beginner‑friendly)
  • Use CoroutineWorker for Kotlin and keep the work idempotent.
  • Pass only small inputs/outputs via Data; store big payloads in files/DB and pass URIs/IDs.
  • Add Constraints (network, charging, storage) to save battery and improve success rates.
  • Enqueue with enqueueUniqueWork/enqueueUniquePeriodicWork to avoid duplicates.
  • Tag requests with addTag() to group and cancel related work easily.
  • Report progress with setProgress and observe WorkInfo in your UI.
  • Long, user‑visible work: call setForeground() and show a notification.
  • Handle failures with Result.retry() and optional backoff policy when building requests.
  • Test with work-testing and verify behavior under Doze/Battery Saver and app restarts.

Output / Result (what you’ll see)

After enqueueing the sample SyncWorker:

  • The UI displays states like ENQUEUED → RUNNING → SUCCEEDED/FAILED/RETRY.
  • Progress increases 0 → 100% via WorkInfo.progress.
  • On success, WorkInfo.outputData returns itemsSynced = 42, which you can show in Compose.
  • If you chose expedited work and you’re within quota, it should begin sooner than regular work; otherwise it may run as non‑expedited.

Quick Kotlin snippets to copy

One-time work with Wi‑Fi + charging

Kotlin
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build()

val oneTime = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(constraints)
.build()

WorkManager.getInstance(context).enqueue(oneTime)

Observe WorkInfo as Flow in Compose

Kotlin
val workInfo by WorkManager.getInstance(context)
.getWorkInfoByIdFlow(workId)
.collectAsState(initial = null)

Unique periodic work (daily)

Kotlin
val daily = PeriodicWorkRequestBuilder<CleanupWorker>(24, TimeUnit.HOURS).build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"daily_cleanup",
ExistingPeriodicWorkPolicy.KEEP,
daily
)

FAQ: People also ask

What is WorkManager in Android and when should I use it?

WorkManager is a Jetpack library for reliable, deferrable background tasks that should persist across app restarts and device reboots. Use it for syncing, scheduled uploads, cleanup, and similar tasks that don’t need exact timing but should be guaranteed under constraints.

How do I set up WorkManager in a new Android project?

Add implementation "androidx.work:work-runtime-ktx:2.11.2" (Kotlin) or work-runtime (Java), use compileSdk 33+, and start creating CoroutineWorker or Worker classes. No special initialization is needed in most apps.

What is the difference between OneTimeWorkRequest and PeriodicWorkRequest?

  • OneTimeWorkRequest: Runs once; can be chained with others; supports expedited mode.
  • PeriodicWorkRequest: Repeats on an interval (≥ 15 minutes); cannot be part of a chain; no initial delay (you can set a flex window).

How do I run WorkManager only on Wi‑Fi or when charging?

Add Constraints to your request:

Kotlin
val c = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // Wi‑Fi
.setRequiresCharging(true)
.build()

How can I observe WorkManager status and get the result?

Use getWorkInfoByIdFlow() (KTX) or getWorkInfoByIdLiveData() to observe WorkInfo. Read state, progress, and outputData from WorkInfo to update your UI.

Troubleshooting tips for beginners

  • Ensure your Worker does not do UI work. It runs off the main thread by default with CoroutineWorker.
  • Keep work idempotent. If the system retries, it shouldn’t create duplicate results.
  • Use enqueueUniqueWork/enqueueUniquePeriodicWork with a stable name to avoid duplicates.
  • Use tags (addTag()) to group and cancel related work.
  • For DI with Hilt, annotate with @HiltWorker and inject via HiltWorkerFactory.

Summary: How to schedule background tasks with WorkManager step by step

  1. Add the WorkManager dependency (2.11.2+), compileSdk 33 or higher.
  2. Create a CoroutineWorker that does small, idempotent work within ~10 minutes.
  3. Build a OneTimeWorkRequest (or PeriodicWorkRequest for repeating jobs), add Constraints if needed.
  4. Enqueue unique work to prevent duplicates; optionally mark as expedited for user actions.
  5. Observe WorkInfo via Flow/LiveData to show state, progress, and output.
  6. Chain one‑time requests for multi‑step flows; use periodic for repeating maintenance.
  7. Handle large data via files/DB, not Data; use foreground APIs for long, user‑visible tasks.

Sources / Further reading

Keywords covered

Focus keyphrase: How to use WorkManager in Android

Secondary keywords: Android WorkManager tutorial for beginners, what is WorkManager in Android, WorkManager Kotlin example, WorkManager Java example, background tasks in Android with WorkManager

Long‑tail keywords: how to schedule background tasks with WorkManager step by step; WorkManager one time and periodic work examples Kotlin; how to chain tasks and set constraints in WorkManager; WorkManager vs JobScheduler for beginners; common WorkManager errors and how to fix them

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted