---
name: android-workmanager-jobs
description: "Background jobs done right — constraints, retries, chains, periodic work, expedited jobs, foreground service. Wires WorkManager for production: OneTime vs Periodic, constraint chaining, Hilt-injected workers, expedited jobs (foreground service), test patterns with WorkManagerTestInitHelper, and the migration story off the legacy Firebase JobDispatcher."
license: MIT
---

# Android WorkManager Jobs

Production-grade scaffolding for deferrable background work on Android using Jetpack WorkManager. This skill helps you pick the right worker type, wire constraints and backoff correctly, inject dependencies into workers with Hilt, and test the whole thing deterministically. It's aimed at Android engineers who keep getting bitten by jobs that never run, run twice, or get killed under Doze.

## When to use it

- You need work to survive process death and reboots — uploads, log syncs, cache cleanup, DB compaction.
- A task must wait for conditions: `NetworkType.UNMETERED`, charging, battery-not-low, or storage-not-low.
- You're chaining steps where one worker's output feeds the next (compress → upload → notify).
- You need a user-visible long task (large download) via an expedited/foreground job.
- You're ripping out the deprecated Firebase JobDispatcher or `GcmNetworkManager` and consolidating on WorkManager.

## How it works

The skill generates `CoroutineWorker` (or `Worker`) subclasses returning `Result.success()`, `Result.retry()`, or `Result.failure()`, plus the request builders that schedule them:

- **OneTimeWorkRequestBuilder** vs **PeriodicWorkRequestBuilder** (15-minute minimum interval, optional flex window).
- **Constraints.Builder** with `setRequiredNetworkType`, `setRequiresCharging`, etc.
- **Backoff** via `setBackoffCriteria(BackoffPolicy.EXPONENTIAL, ...)`.
- **Uniqueness** with `enqueueUniqueWork(... ExistingWorkPolicy.KEEP/REPLACE)` and `enqueueUniquePeriodicWork(... ExistingPeriodicWorkPolicy.UPDATE)`.
- **Chains** through `WorkManager.beginWith(a).then(b)` and `Data`/`workDataOf` passing.
- **Hilt** via `@HiltWorker`, `@AssistedInject`, and a `HiltWorkerFactory` registered through `Configuration.Provider` (disable the default initializer in the manifest).
- **Expedited** jobs with `setExpedited(OutOfQuotaPolicy...)` and `setForegroundAsync`/`getForegroundInfo`.

## Example

```kotlin
@HiltWorker
class UploadWorker @AssistedInject constructor(
    @Assisted ctx: Context,
    @Assisted params: WorkerParameters,
    private val repo: MediaRepo,
) : CoroutineWorker(ctx, params) {
    override suspend fun doWork(): Result =
        runCatching { repo.upload(inputData.getString("uri")!!) }
            .fold({ Result.success() }, { if (runAttemptCount < 3) Result.retry() else Result.failure() })
}

val req = OneTimeWorkRequestBuilder<UploadWorker>()
    .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED).build())
    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
    .build()
WorkManager.getInstance(ctx).enqueueUniqueWork("upload", ExistingWorkPolicy.KEEP, req)
```

## Tips & gotchas

- Periodic work can't be expedited and won't fire more often than every 15 minutes — the OS coalesces it.
- If you use `@HiltWorker`, you must remove the default `WorkManagerInitializer` provider in the manifest, or the custom factory is ignored.
- Read `runAttemptCount` to cap retries; `Result.retry()` alone loops forever under your backoff curve.
- Expedited quota is finite — pick `RUN_AS_NON_EXPEDITED_WORK_REQUEST` as the fallback policy to avoid silent failures.

## FAQ

**Why does my unique periodic work not pick up new constraints?** Use `ExistingPeriodicWorkPolicy.UPDATE` (API since WorkManager 2.8) so the request is replaced in place rather than kept.

**How do I test workers without a real device clock?** The skill scaffolds `WorkManagerTestInitHelper.initializeTestWorkManager`, then drives execution with `TestDriver.setAllConstraintsMet`, `setPeriodDelayMet`, and `setInitialDelayMet`.

**Can I observe results?** Yes — `getWorkInfoByIdLiveData` / `getWorkInfosForUniqueWorkFlow` expose `WorkInfo.State` and the output `Data`.
