Complete API documentation for KMP WorkManager.
- BackgroundTaskScheduler
- WorkerResult (v2.3.0+)
- Task Triggers
- Constraints
- TaskChain
- Events
- Enums
- Platform-Specific APIs
The main interface for scheduling and managing background tasks.
Schedule a single background task.
suspend fun enqueue(
id: String,
trigger: TaskTrigger,
workerClassName: String,
input: String? = null,
constraints: Constraints = Constraints()
): ScheduleResultParameters:
id: String- Unique identifier for the task. If a task with the same ID exists, behavior depends onExistingWorkPolicyin constraints.trigger: TaskTrigger- When and how the task should be executed (OneTime, Periodic, Exact, etc.)workerClassName: String- Name of the worker class that will execute the taskinput: String?- Optional input data passed to the worker (must be serializable)constraints: Constraints- Execution constraints (network, battery, charging, etc.)
Returns: ScheduleResult - Result of the scheduling operation
Example:
val result = scheduler.enqueue(
id = "data-sync",
trigger = TaskTrigger.Periodic(intervalMs = 15_MINUTES),
workerClassName = "SyncWorker",
constraints = Constraints(requiresNetwork = true)
)Start building a task chain with a single task or multiple parallel tasks.
fun beginWith(request: TaskRequest): TaskChain
fun beginWith(requests: List<TaskRequest>): TaskChainParameters:
request: TaskRequest- Single task to start the chainrequests: List<TaskRequest>- Multiple tasks to run in parallel at the start
Returns: TaskChain - Builder for constructing task chains
Example:
// Sequential chain
scheduler.beginWith(TaskRequest(workerClassName = "DownloadWorker"))
.then(TaskRequest(workerClassName = "ProcessWorker"))
.enqueue()
// Parallel start
scheduler.beginWith(listOf(
TaskRequest(workerClassName = "SyncWorker"),
TaskRequest(workerClassName = "CacheWorker")
))
.then(TaskRequest(workerClassName = "FinalizeWorker"))
.enqueue()Cancel a specific task by its ID.
suspend fun cancel(id: String)Parameters:
id: String- ID of the task to cancel
Example:
scheduler.cancel("data-sync")Cancel all scheduled tasks.
suspend fun cancelAll()Example:
scheduler.cancelAll()New in v2.3.0: Workers can now return structured results instead of just boolean.
sealed class WorkerResult {
data class Success(
val message: String? = null,
val data: Map<String, Any?>? = null
) : WorkerResult()
data class Failure(
val message: String
) : WorkerResult()
}Common Worker:
interface CommonWorker {
suspend fun doWork(input: String?): WorkerResult
}Backward Compatibility:
Workers returning Boolean are automatically converted to WorkerResult:
true→WorkerResult.Success()false→WorkerResult.Failure("Task failed")
class SyncWorker : CommonWorker {
override suspend fun doWork(input: String?): WorkerResult {
return try {
syncData()
WorkerResult.Success(message = "Sync completed")
} catch (e: Exception) {
WorkerResult.Failure("Sync failed: ${e.message}")
}
}
}class DownloadWorker : CommonWorker {
override suspend fun doWork(input: String?): WorkerResult {
val config = Json.decodeFromString<DownloadConfig>(input!!)
val file = downloadFile(config.url, config.savePath)
return WorkerResult.Success(
message = "Downloaded ${file.length()} bytes in 5s",
data = mapOf(
"filePath" to config.savePath,
"fileSize" to file.length(),
"url" to config.url,
"duration" to 5000L
)
)
}
}// In your application code
when (val result = worker.doWork(input)) {
is WorkerResult.Success -> {
println("Success: ${result.message}")
val fileSize = result.data?.get("fileSize") as? Long
println("File size: $fileSize bytes")
}
is WorkerResult.Failure -> {
println("Failed: ${result.message}")
}
}// Worker 1: Download file and return metadata
class DownloadWorker : CommonWorker {
override suspend fun doWork(input: String?): WorkerResult {
val file = download(url)
return WorkerResult.Success(
data = mapOf("filePath" to file.path, "size" to file.size)
)
}
}
// Worker 2: Process downloaded file
class ProcessWorker : CommonWorker {
override suspend fun doWork(input: String?): WorkerResult {
// In v2.3.0: Access previous worker data via event bus or custom implementation
// In v2.4.0: Automatic data passing will be supported
return WorkerResult.Success(message = "Processed file")
}
}
// Chain them together
scheduler.beginWith(TaskRequest("DownloadWorker"))
.then(TaskRequest("ProcessWorker"))
.withId("download-process-chain", policy = ExistingPolicy.KEEP)
.enqueue()✅ Structured Data Return: Return any data from workers ✅ Better Error Messages: Detailed failure messages ✅ Type Safety: Explicit success/failure handling ✅ Backward Compatible: Boolean returns still work ✅ Built-in Workers: All 5 built-in workers return meaningful data
Task triggers define when and how tasks should be executed.
Execute a task once after an optional delay.
data class OneTime(
val initialDelayMs: Long = 0
) : TaskTriggerParameters:
initialDelayMs: Long- Delay before execution in milliseconds (default: 0)
Supported Platforms: Android, iOS
Example:
TaskTrigger.OneTime(initialDelayMs = 5_000) // Execute after 5 secondsExecute a task repeatedly at fixed intervals.
data class Periodic(
val intervalMs: Long,
val flexMs: Long? = null
) : TaskTriggerParameters:
intervalMs: Long- Interval between executions in milliseconds (minimum: 15 minutes)flexMs: Long?- Flex time window for Android WorkManager (optional)
Supported Platforms: Android, iOS
Important Notes:
- Android: Minimum interval is 15 minutes (enforced by WorkManager)
- iOS: Task automatically re-schedules after completion
- iOS: Actual execution time determined by BGTaskScheduler (opportunistic)
Example:
TaskTrigger.Periodic(
intervalMs = 30 * 60 * 1000, // 30 minutes
flexMs = 5 * 60 * 1000 // 5 minutes flex
)Execute a task at a precise time.
data class Exact(
val atEpochMillis: Long
) : TaskTriggerParameters:
atEpochMillis: Long- Exact timestamp in epoch milliseconds
Supported Platforms: Android, iOS
Implementation:
- Android: Uses
AlarmManager.setExactAndAllowWhileIdle() - iOS: Uses
UNUserNotificationCenterlocal notifications
Example:
val targetTime = Clock.System.now()
.plus(1.hours)
.toEpochMilliseconds()
TaskTrigger.Exact(atEpochMillis = targetTime)Execute a task within a time window.
data class Windowed(
val startEpochMillis: Long,
val endEpochMillis: Long
) : TaskTriggerParameters:
startEpochMillis: Long- Window start time in epoch millisecondsendEpochMillis: Long- Window end time in epoch milliseconds
Supported Platforms: Android only (iOS returns REJECTED_OS_POLICY)
Example:
val now = Clock.System.now().toEpochMilliseconds()
TaskTrigger.Windowed(
startEpochMillis = now + 60_000, // Start in 1 minute
endEpochMillis = now + 5 * 60_000 // End in 5 minutes
)Trigger a task when content provider changes are detected.
data class ContentUri(
val uriString: String,
val triggerForDescendants: Boolean = true
) : TaskTriggerParameters:
uriString: String- Content URI to observe (e.g., "content://media/external/images/media")triggerForDescendants: Boolean- Whether to trigger for descendant URIs (default: true)
Supported Platforms: Android only (iOS returns REJECTED_OS_POLICY)
Example:
TaskTrigger.ContentUri(
uriString = "content://media/external/images/media",
triggerForDescendants = true
)Trigger tasks based on device state changes.
data object BatteryLow : TaskTrigger
data object BatteryOkay : TaskTrigger
data object StorageLow : TaskTrigger
data object DeviceIdle : TaskTriggerSupported Platforms:
BatteryLow,BatteryOkay: Android, iOSStorageLow,DeviceIdle: Android only
Example:
// Run heavy processing when battery is good
scheduler.enqueue(
id = "ml-training",
trigger = TaskTrigger.BatteryOkay,
workerClassName = "MLTrainingWorker",
constraints = Constraints(requiresCharging = true)
)Constraints define the conditions under which a task can run.
data class Constraints(
// Network
val requiresNetwork: Boolean = false,
val networkType: NetworkType = NetworkType.CONNECTED,
val requiresUnmeteredNetwork: Boolean = false,
// Battery
val requiresCharging: Boolean = false,
val requiresBatteryNotLow: Boolean = false,
// Storage
val requiresStorageNotLow: Boolean = false,
// Device State
val requiresDeviceIdle: Boolean = false,
val allowWhileIdle: Boolean = false,
// Task Properties
val isHeavyTask: Boolean = false,
val expedited: Boolean = false,
// Retry Policy
val backoffPolicy: BackoffPolicy = BackoffPolicy.EXPONENTIAL,
val backoffDelayMs: Long = 10_000,
// Existing Work Policy
val existingWorkPolicy: ExistingWorkPolicy = ExistingWorkPolicy.KEEP,
// iOS Quality of Service
val qos: QualityOfService = QualityOfService.DEFAULT
)requiresNetwork: Boolean = falseWhether the task requires network connectivity.
Platforms: Android, iOS
networkType: NetworkType = NetworkType.CONNECTEDType of network required. Options:
NetworkType.NOT_REQUIRED- No network neededNetworkType.CONNECTED- Any network connectionNetworkType.UNMETERED- WiFi or unlimited data (Android only)NetworkType.NOT_ROAMING- Non-roaming network (Android only)NetworkType.METERED- Cellular data allowed (Android only)NetworkType.TEMPORARILY_UNMETERED- Temporarily free network (Android only)
Platforms: Android (full support), iOS (only CONNECTED/NOT_REQUIRED)
requiresUnmeteredNetwork: Boolean = falseShortcut for requiring WiFi (same as networkType = NetworkType.UNMETERED).
Platforms: Android only
requiresCharging: Boolean = falseWhether the device must be charging.
Platforms: Android, iOS
requiresBatteryNotLow: Boolean = falseWhether the battery level must be above the low threshold.
Platforms: Android, iOS
requiresStorageNotLow: Boolean = falseWhether the device must have sufficient storage available.
Platforms: Android only
requiresDeviceIdle: Boolean = falseWhether the device must be idle (screen off, not recently used).
Platforms: Android only
allowWhileIdle: Boolean = falseWhether the task can run while the device is in Doze mode.
Platforms: Android only
isHeavyTask: Boolean = falseWhether this is a long-running task (>10 minutes).
- Android: Uses
KmpHeavyWorkerwith foreground service - iOS: Uses
BGProcessingTaskinstead ofBGAppRefreshTask
Platforms: Android, iOS
expedited: Boolean = falseWhether the task should be expedited (run as soon as possible).
Platforms: Android only (uses expedited WorkManager jobs)
backoffPolicy: BackoffPolicy = BackoffPolicy.EXPONENTIALRetry strategy when a task fails. Options:
BackoffPolicy.EXPONENTIAL- Exponential backoff (10s, 20s, 40s, 80s, ...)BackoffPolicy.LINEAR- Linear backoff (10s, 20s, 30s, 40s, ...)
Platforms: Android, iOS
backoffDelayMs: Long = 10_000Initial backoff delay in milliseconds (default: 10 seconds).
Platforms: Android, iOS
existingWorkPolicy: ExistingWorkPolicy = ExistingWorkPolicy.KEEPWhat to do when a task with the same ID already exists. Options:
ExistingWorkPolicy.REPLACE- Cancel existing and schedule new taskExistingWorkPolicy.KEEP- Keep existing task, ignore new requestExistingWorkPolicy.APPEND- Queue new task after existing oneExistingWorkPolicy.APPEND_OR_REPLACE- Append if existing is running, replace otherwise
Platforms: Android (full support), iOS (only REPLACE/KEEP)
qos: QualityOfService = QualityOfService.DEFAULTiOS priority hint for task execution. Options:
QualityOfService.HIGH- User-initiated priorityQualityOfService.DEFAULT- Default priorityQualityOfService.LOW- Background priority
Platforms: iOS only
Builder for creating sequential and parallel task workflows.
Add the next step to the chain.
fun then(request: TaskRequest): TaskChain
fun then(requests: List<TaskRequest>): TaskChainParameters:
request: TaskRequest- Single task to execute nextrequests: List<TaskRequest>- Multiple tasks to run in parallel
Returns: TaskChain - The chain builder for further chaining
Set a unique ID for the chain and specify the ExistingPolicy.
fun withId(
id: String,
policy: ExistingPolicy = ExistingPolicy.REPLACE
): TaskChainParameters:
id: String- Unique identifier for the chainpolicy: ExistingPolicy- How to handle if a chain with this ID already existsExistingPolicy.KEEP- Skip if chain already runningExistingPolicy.REPLACE- Cancel old chain and start new one
Returns: TaskChain - New chain instance with the specified ID and policy
Example:
// Prevent duplicate chain execution
scheduler.beginWith(TaskRequest("DownloadWorker"))
.then(TaskRequest("ProcessWorker"))
.withId("download-process-workflow", policy = ExistingPolicy.KEEP)
.enqueue()
// Click button multiple times - only runs once
button.onClick {
scheduler.beginWith(TaskRequest("SyncWorker"))
.withId("sync-chain", policy = ExistingPolicy.KEEP)
.enqueue()
}Execute the constructed task chain.
fun enqueue()Note: No return value in v2.3.0. The chain is enqueued asynchronously.
Data class representing a task in a chain.
data class TaskRequest(
val id: String = UUID.randomUUID().toString(),
val workerClassName: String,
val input: String? = null,
val constraints: Constraints = Constraints()
)Parameters:
id: String- Unique task identifier (auto-generated if not provided)workerClassName: String- Name of the worker classinput: String?- Optional input dataconstraints: Constraints- Execution constraints
// Sequential execution
scheduler
.beginWith(TaskRequest(workerClassName = "DownloadWorker"))
.then(TaskRequest(workerClassName = "ProcessWorker"))
.then(TaskRequest(workerClassName = "UploadWorker"))
.enqueue()
// Parallel execution
scheduler
.beginWith(listOf(
TaskRequest(workerClassName = "SyncWorker"),
TaskRequest(workerClassName = "CacheWorker"),
TaskRequest(workerClassName = "CleanupWorker")
))
.then(TaskRequest(workerClassName = "FinalizeWorker"))
.enqueue()
// Mixed sequential and parallel
scheduler
.beginWith(TaskRequest(workerClassName = "DownloadWorker"))
.then(listOf(
TaskRequest(workerClassName = "ProcessImageWorker"),
TaskRequest(workerClassName = "ProcessVideoWorker")
))
.then(TaskRequest(workerClassName = "UploadWorker"))
.enqueue()Event system for worker-to-UI communication.
Singleton object for emitting and collecting task completion events.
object TaskEventBus {
val events: SharedFlow<TaskCompletionEvent>
suspend fun emit(event: TaskCompletionEvent)
}Event emitted when a task completes.
data class TaskCompletionEvent(
val taskName: String,
val success: Boolean,
val message: String,
val timestamp: Long = Clock.System.now().toEpochMilliseconds()
)Parameters:
taskName: String- Name of the worker that completedsuccess: Boolean- Whether the task succeededmessage: String- Human-readable messagetimestamp: Long- Event timestamp in epoch milliseconds
Emitting events from workers:
class SyncWorker : IosWorker {
override suspend fun doWork(input: String?): Boolean {
return try {
syncDataFromServer()
TaskEventBus.emit(
TaskCompletionEvent(
taskName = "SyncWorker",
success = true,
message = "✅ Data synced successfully"
)
)
true
} catch (e: Exception) {
TaskEventBus.emit(
TaskCompletionEvent(
taskName = "SyncWorker",
success = false,
message = "❌ Sync failed: ${e.message}"
)
)
false
}
}
}Collecting events in UI:
@Composable
fun TaskMonitor() {
LaunchedEffect(Unit) {
TaskEventBus.events.collect { event ->
when {
event.success -> {
showSuccessToast(event.message)
}
else -> {
showErrorToast(event.message)
}
}
}
}
}Result of a task scheduling operation.
enum class ScheduleResult {
SUCCESS, // Task scheduled successfully
REJECTED_OS_POLICY, // OS rejected the task (e.g., iOS background restrictions)
REJECTED_INVALID_PARAMS, // Invalid parameters provided
FAILED_UNKNOWN // Unknown error occurred
}Retry strategy for failed tasks.
enum class BackoffPolicy {
EXPONENTIAL, // Exponential backoff (10s, 20s, 40s, 80s, ...)
LINEAR // Linear backoff (10s, 20s, 30s, 40s, ...)
}Policy for handling existing tasks with the same ID.
enum class ExistingWorkPolicy {
REPLACE, // Cancel existing and schedule new task
KEEP, // Keep existing task, ignore new request
APPEND, // Queue new task after existing one
APPEND_OR_REPLACE // Append if running, replace otherwise
}Network requirement for tasks.
enum class NetworkType {
NOT_REQUIRED, // No network needed
CONNECTED, // Any network connection
UNMETERED, // WiFi or unlimited data
NOT_ROAMING, // Non-roaming network
METERED, // Cellular data allowed
TEMPORARILY_UNMETERED // Temporarily free network
}Priority hint for iOS tasks.
enum class QualityOfService {
HIGH, // User-initiated priority
DEFAULT, // Default priority
LOW // Background priority
}Base worker class for deferrable tasks.
class KmpWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val workerClassName = inputData.getString("workerClassName")
return when (workerClassName) {
"YourWorker" -> executeYourWorker()
else -> Result.failure()
}
}
private suspend fun executeYourWorker(): Result {
// Your implementation
return Result.success()
}
}Foreground service worker for long-running tasks (>10 minutes).
class KmpHeavyWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
setForeground(createForegroundInfo())
// Your long-running work here
return Result.success()
}
private fun createForegroundInfo(): ForegroundInfo {
// Create notification for foreground service
}
}Interface for iOS background workers.
interface IosWorker {
suspend fun doWork(input: String?): Boolean
}Implementation:
class SyncWorker : IosWorker {
override suspend fun doWork(input: String?): Boolean {
// Your implementation (must complete within 25 seconds)
return true // Return true for success, false for failure
}
}Factory for creating worker instances.
object IosWorkerFactory {
fun createWorker(className: String): IosWorker? {
return when (className) {
"SyncWorker" -> SyncWorker()
"UploadWorker" -> UploadWorker()
else -> null
}
}
}const val ONE_SECOND = 1_000L
const val ONE_MINUTE = 60_000L
const val FIFTEEN_MINUTES = 900_000L
const val ONE_HOUR = 3_600_000L
const val ONE_DAY = 86_400_000L- Quick Start Guide - Get started in 5 minutes
- Platform Setup - Detailed platform configuration
- Task Chains - Advanced workflow patterns
- Constraints & Triggers - Detailed trigger documentation
- GitHub Issues