workmanager
Android WorkManager for guaranteed background execution - use for deferred tasks, periodic syncs, file uploads, notifications, and task chains. Covers CoroutineWorker, constraints, chaining, testing, and troubleshooting. Use when implementing background work that needs reliable execution across app restarts and doze mode.
What this skill does
# Android WorkManager
WorkManager is the recommended solution for persistent, guaranteed background work on Android.
## When to Use WorkManager
Use WorkManager for:
- **Periodic background sync** - Sync data with server every 15+ minutes
- **Deferred tasks** - Upload files, compress images when device is ready
- **Guaranteed execution** - Tasks that must run even if app is killed
- **Constraint-based work** - Run only when WiFi connected, battery charging, etc.
Don't use WorkManager for:
- **Immediate execution** - Use Kotlin coroutines directly
- **Precise timing** - Use AlarmManager for exact scheduling
- **Foreground work** - Use coroutines in ViewModel/Service
## Dependencies
```kotlin
// build.gradle.kts (androidMain or Android module)
dependencies {
implementation("androidx.work:work-runtime-ktx:2.10.0")
}
```
## CoroutineWorker Basics
### Simple Worker
```kotlin
class SyncWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
// Perform background work
val data = fetchDataFromServer()
saveToDatabase(data)
Result.success()
} catch (e: Exception) {
Log.e("SyncWorker", "Sync failed", e)
if (runAttemptCount < 3) {
Result.retry() // Retry with exponential backoff
} else {
Result.failure() // Give up after 3 attempts
}
}
}
}
```
### Worker with Input/Output Data
```kotlin
class UploadWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// Get input data
val fileUri = inputData.getString(KEY_FILE_URI) ?: return Result.failure()
val userId = inputData.getLong(KEY_USER_ID, -1L)
return try {
val uploadedUrl = uploadFile(fileUri)
// Return output data
val outputData = workDataOf(
KEY_UPLOADED_URL to uploadedUrl,
KEY_TIMESTAMP to System.currentTimeMillis()
)
Result.success(outputData)
} catch (e: Exception) {
Result.retry()
}
}
companion object {
const val KEY_FILE_URI = "file_uri"
const val KEY_USER_ID = "user_id"
const val KEY_UPLOADED_URL = "uploaded_url"
const val KEY_TIMESTAMP = "timestamp"
}
}
```
### Worker with Progress
```kotlin
class DownloadWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val url = inputData.getString(KEY_URL) ?: return Result.failure()
return try {
downloadFile(url) { progress ->
// Update progress (0-100)
setProgress(workDataOf(KEY_PROGRESS to progress))
}
Result.success()
} catch (e: Exception) {
Result.failure()
}
}
companion object {
const val KEY_URL = "url"
const val KEY_PROGRESS = "progress"
}
}
// Observe progress
WorkManager.getInstance(context)
.getWorkInfoByIdLiveData(workId)
.observe(lifecycleOwner) { workInfo ->
val progress = workInfo?.progress?.getInt(DownloadWorker.KEY_PROGRESS, 0) ?: 0
updateProgressBar(progress)
}
```
### Foreground Worker (with Notification)
```kotlin
class LongRunningWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// Show notification for long-running work
setForeground(createForegroundInfo())
return try {
performLongOperation()
Result.success()
} catch (e: Exception) {
Result.failure()
}
}
private fun createForegroundInfo(): ForegroundInfo {
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
.setContentTitle("Processing")
.setContentText("Processing your request...")
.setSmallIcon(R.drawable.ic_notification)
.setOngoing(true)
.build()
return ForegroundInfo(NOTIFICATION_ID, notification)
}
companion object {
private const val CHANNEL_ID = "work_channel"
private const val NOTIFICATION_ID = 1
}
}
```
## Scheduling Work
### One-Time Work
```kotlin
// Simple enqueue
val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.build()
WorkManager.getInstance(context).enqueue(syncRequest)
// With input data
val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(workDataOf(
UploadWorker.KEY_FILE_URI to fileUri,
UploadWorker.KEY_USER_ID to userId
))
.build()
WorkManager.getInstance(context).enqueue(uploadRequest)
// With constraints (see Constraints section)
val constrainedRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(context).enqueue(constrainedRequest)
```
### Periodic Work
```kotlin
// Minimum interval is 15 minutes
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
repeatInterval = 15,
repeatIntervalTimeUnit = TimeUnit.MINUTES
)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(context).enqueue(syncRequest)
// With flex interval (run within last 5 minutes of 15-minute period)
val flexRequest = PeriodicWorkRequestBuilder<SyncWorker>(
repeatInterval = 15,
repeatIntervalTimeUnit = TimeUnit.MINUTES,
flexTimeInterval = 5,
flexTimeIntervalUnit = TimeUnit.MINUTES
)
.build()
```
### Delayed Work
```kotlin
val delayedRequest = OneTimeWorkRequestBuilder<NotificationWorker>()
.setInitialDelay(1, TimeUnit.HOURS)
.build()
WorkManager.getInstance(context).enqueue(delayedRequest)
```
### Expedited Work (Android 12+)
```kotlin
// For important user-facing work
val expeditedRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
WorkManager.getInstance(context).enqueue(expeditedRequest)
```
## Constraints
See [references/constraints.md](references/constraints.md) for detailed constraint patterns and combinations.
Quick reference:
```kotlin
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // Any network
.setRequiresBatteryNotLow(true)
.setRequiresCharging(false)
.setRequiresStorageNotLow(true)
.setRequiresDeviceIdle(false) // API 23+
.build()
val request = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(constraints)
.build()
```
## Work Chaining
See [references/chaining.md](references/chaining.md) for advanced chaining patterns and parallel execution.
### Sequential Chain
```kotlin
WorkManager.getInstance(context)
.beginWith(downloadRequest)
.then(processRequest)
.then(uploadRequest)
.enqueue()
```
### Parallel Chains
```kotlin
val chain1 = WorkManager.getInstance(context).beginWith(work1A).then(work1B)
val chain2 = WorkManager.getInstance(context).beginWith(work2A).then(work2B)
val finalWork = OneTimeWorkRequestBuilder<FinalWorker>().build()
WorkContinuation.combine(listOf(chain1, chain2))
.then(finalWork)
.enqueue()
```
## Unique Work
### Replace Existing Work
```kotlin
WorkManager.getInstance(context)
.enqueueUniqueWork(
"daily_sync",
ExistingWorkPolicy.REPLACE,
syncRequest
)
```
### Keep Existing Work
```kotlin
WorkManager.getInstance(context)
.enqueueUniqueWork(
"upload_${fileId}",
ExistingWorkPolicy.KEEP,
uploadRequesRelated in Productivity
gitea-workflow
IncludedOrchestrate agile development workflows for Gitea repositories using the tea CLI. Use when working with Gitea-hosted repos and asking to 'run the workflow', 'continue working', 'what's next', 'complete the task cycle', 'start my day', 'end the sprint', 'implement the next task', or wanting guided step-by-step development assistance. Keywords: workflow, orchestrate, agile, task cycle, sprint, daily, implement, review, PR, standup, retrospective, gitea, tea.
microsoft-graph-gateway
IncludedRoute Microsoft Graph work in this workspace. Use when users want to read or write Outlook mail, calendar events, contacts, OneDrive or SharePoint files, Teams, Planner, To Do, users, groups, directory data, or arbitrary Microsoft Graph endpoints from VS Code. Prefer WorkIQ for common read scenarios. Use Microsoft Graph for write actions and gap-read scenarios that need exact Graph properties, filters, permissions, or endpoints.
copilotkit
IncludedUse when building with CopilotKit — setup, development, integrations, debugging, upgrading, or contributing. Routes to the appropriate specialized skill based on the task.
wordly-wisdom
IncludedProvides calibrated decision analysis using Charlie Munger-style multiple mental models, inversion, incentive mapping, circle-of-competence checks, misjudgment audits, second-order effects, and forecast updates. Use when the user asks for an oracle take, a hard call, a decision memo, a premortem, an outside view, a red-team, a sanity-check, what am I missing, think this through, or wants a strategy, hire, investment, plan, product, partnership, or major life choice analysed. Avoid for simple factual lookups or time-sensitive legal, medical, or market questions without fresh evidence.
swain-session
IncludedSession management and project status dashboard. Owns the full session lifecycle (start/work/close/resume), focus lane, bookmarks, worktree detection, and tab naming. Also serves as the project status dashboard — shows active epics, progress, actionable next steps, blocked items, tasks, GitHub issues, and recommendations. Worktree creation is deferred to swain-do task dispatch (SPEC-195). Triggers on: 'session', 'status', 'what's next', 'dashboard', 'overview', 'where are we', 'what should I work on', 'show me priorities', 'bookmark', 'focus on', 'session info'.
gandi
IncludedComprehensive Gandi domain registrar integration for domain and DNS management. Register and manage domains, create/update/delete DNS records (A, AAAA, CNAME, MX, TXT, SRV, and more), configure email forwarding and aliases, check SSL certificate status, create DNS snapshots for safe rollback, bulk update zone files, and monitor domain expiration. Supports multi-domain management, zone file import/export, and automated DNS backups. Includes both read-only and destructive operations with safety controls.