KMPWorker
A reliability-first Kotlin Multiplatform background task library for Android & iOS.

Features
- One-time, periodic, exact-time & windowed background tasks
- Exponential / linear retry policies with public
RetryEngine
- Task state monitoring via
Flow with 10 extension operators
- Progress reporting from within task handlers
- Task chaining with crash-safe resume + builder DSL
- Task dependency graph (DAG) with parallel execution
- Chain policies (KEEP / REPLACE / ALLOW_DUPLICATE)
- Batch operations (enqueueBatch / cancelBatch)
- Rate limiting with coroutine Semaphore
- SQLDelight-backed persistence (survives app restarts)
- Execution history & telemetry
- Offline queue with automatic replay on network restore
- HTTP download & upload (resume, SHA-256 checksum, progress)
- Group cancellation via tags
- Content URI triggers (Android)
- Full constraint support (network, charging, battery, device idle)
- Foreground service configuration (Android)
- Swift interop helpers (FlowWrapper, TaskStateObserver)
- Testing utilities —
FakeKmpWorker, FakeNetworkMonitor, FakeTaskRepository
- Android test rule —
KmpWorkerTestRule for JUnit4
- No Ktor dependency — pure platform APIs
- ProGuard/R8 consumer rules included
Installation
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.neuralheads:kmpworker:0.1.0-beta06")
}
androidMain.dependencies {
implementation("com.neuralheads:kmpworker-android:0.1.0-beta06")
}
}
}
Optional modules
implementation("com.neuralheads:kmpworker-transfer:0.1.0-beta06")
implementation("com.neuralheads:kmpworker-inspector:0.1.0-beta06")
testImplementation("com.neuralheads:kmpworker-testing:0.1.0-beta06")
Package Names
All classes live under io.neuralheads.kmpworker.*:
Quick Start
kmpWorker.register("sync-users") {
repository.syncUsers()
}
kmpWorker.enqueue(
TaskRequest(
id = "sync-users",
type = TaskType.OneTime,
constraints = Constraints(requiresInternet = true),
retryPolicy = RetryPolicy.Exponential(initialDelayMillis = 5_000, maxRetries = 3)
)
)
kmpWorker.observe("sync-users")
.onRunning { showSpinner() }
.onProgress { progress, msg -> updateBar(progress) }
.onSuccess { hideSpinner() }
.onFailed { error -> showError(error.throwable.message) }
.collect()
DSL shortcuts
kmpWorker.oneTime(id = "upload", retryPolicy = exponentialRetry(5.seconds, maxRetries = 3)) {
uploader.upload()
}
kmpWorker.periodic(id = "sync", repeatInterval = 6.hours) {
repository.sync()
}
Android Setup
class MyApp : Application() {
val kmpWorker: KmpWorker by lazy {
KmpWorkerBuilder(
AndroidKmpWorker(
context = this,
telemetry = SqlDelightTelemetryCollector(database),
foregroundConfig = ForegroundConfig(
notificationTitle = "Syncing..."
)
)
)
.configure {
logLevel = KmpWorkerLogger.Level.DEBUG
logger = KmpWorkerAndroidLogger
}
.task("sync") { repository.sync() }
.build()
}
}
WorkManager is initialized automatically via Jetpack App Startup.
iOS Setup
let kmpWorker = IOSKmpWorker()
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]) -> {
kmpWorker.register(taskId: ) { }
kmpWorker.initialize()
}
Info.plist:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>sync</string>
</array>
Swift Flow observation
let observer = TaskStateObserver(flow: kmpWorker.observe(taskId: "sync"))
observer.onStateChange { state in
print("State: \(state)")
}
observer.stop()
Task Types
TaskType.OneTime
TaskType.Periodic(repeatIntervalMillis = 900_000)
TaskType.ExactTime(runAtMillis = epochMs)
TaskType.Windowed(earliestMillis = t1, latestMillis = t2)
Constraints
Constraints(
requiresInternet = true,
requiresCharging = false,
batteryNotLow = true,
requiresUnmeteredNetwork = true,
requiresNonRoamingNetwork = true,
requiresDeviceIdle = false,
contentUris = listOf("content://media/external/images")
)
Task Chaining
Constructor API
val chain = TaskChain(
id = "onboarding",
steps = listOf(
TaskRequest(id = "fetch-profile", type = TaskType.OneTime),
TaskRequest(id = "upload-avatar", type = TaskType.OneTime),
TaskRequest(id = "notify-server", type = TaskType.OneTime)
)
)
kmpWorker.enqueueChain(chain, ChainPolicy.REPLACE)
Builder DSL
kmpWorker.chain("onboarding", policy = ChainPolicy.REPLACE) {
beginWith("fetch-profile")
then("upload-avatar") {
constraints = Constraints(requiresInternet = true)
}
then("notify-server") {
retryPolicy = RetryPolicy.Exponential(5_000, 3)
}
}
Chain Policies
| Policy | Behavior |
|---|
KEEP | Skip if chain ID already running |
REPLACE |
Task Dependency Graph (DAG)
Execute tasks with complex dependencies — independent nodes run in parallel:
@OptIn(ExperimentalKmpWorkerApi::class)
kmpWorker.graph("pipeline") {
val fetch = task("fetch-data")
val process = task("process")
val validate = task("validate")
val upload = task("upload")
fetch then process
fetch then validate
process then upload
validate then upload
}
Progress Reporting
kmpWorker.registerWithContext("upload") {
for (i in 0..100 step 10) {
reportProgress(i / 100f, "Uploading chunk $i")
delay(500)
}
}
kmpWorker.observe("upload")
.onProgress { progress, message -> updateBar(progress) }
.collect()
Batch Operations
kmpWorker.enqueueBatch(listOf(
TaskRequest("task-1", TaskType.OneTime),
TaskRequest("task-2", TaskType.OneTime),
TaskRequest("task-3", TaskType.OneTime)
))
kmpWorker.cancelBatch(listOf("task-1", "task-2", "task-3"))
Rate Limiting
@OptIn(ExperimentalKmpWorkerApi::class)
val limiter = RateLimiter(maxConcurrent = 3)
limiter.withPermit {
doHeavyWork()
}
Execution History & Telemetry
val worker = AndroidKmpWorker(
context = this,
telemetry = SqlDelightTelemetryCollector(database)
)
val records = worker.getExecutionHistory(limit = 50)
records.forEach { println("${it.taskId}: ${it.state} (${it.durationMs}ms)") }
worker.clearExecutionHistory()
Compose Live Inspector
KMPWorker includes a gorgeous, reactive live inspector dashboard built with Compose Multiplatform to monitor and simulate task execution in real-time.
KmpWorkerInspectorScreen(kmpWorker = kmpWorker)
The inspector provides:
- Real-Time Metrics Grid: View active task counts, success rate, and error/timeout counters.
- Registered Handlers: Displays all registered handlers in with individual buttons to trigger them.
HTTP Transfers
No Ktor required — uses HttpURLConnection (Android) and NSURLSession (iOS):
val manager = AndroidTransferManager()
manager.download(DownloadRequest(
id = "large-file",
url = "https://example.com/file.zip",
savePath = "/downloads/file.zip",
expectedChecksum = "sha256:abc123...",
resumable = true
))
manager.observeProgress("large-file").collect { progress ->
println("${progress.percentComplete}%")
}
manager.upload(UploadRequest(
id = "backup",
url = "https://api.example.com/upload",
filePath = "/data/backup.zip"
))
Offline Queue
val queue = OfflineQueue(
worker = kmpWorker,
repository = SqlDelightTaskRepository(database),
networkMonitor = AndroidNetworkMonitor(context)
)
queue.start()
queue.enqueue(request)
Testing
fake = FakeKmpWorker()
fake.register() { repository.sync() }
fake.enqueue(TaskRequest(, TaskType.OneTime))
assertEquals(TaskState.Success, fake.lastStateFor())
fake.failureCount[] =
{
rule = KmpWorkerTestRule()
= runTest {
rule.worker.register() { }
rule.worker.enqueue(TaskRequest(, TaskType.OneTime))
assertEquals(TaskState.Success, rule.worker.lastStateFor())
}
}
Flow Extensions
Published Modules
Requirements
License
Copyright 2026 NeuralHeads
Licensed under the Apache License, Version 2.0