
A Kotlin Multiplatform library for managing background work and scheduling. It provides a unified API similar to Android Jetpack WorkManager, supporting Android, iOS, and Desktop targets.
Background task scheduling for Kotlin Multiplatform
Write once, run everywhere — Schedule background tasks on Android and iOS from shared code
Features • Installation • Quick Start • Documentation • Examples
Building a multiplatform app that needs background task scheduling?
// ❌ Platform-specific nightmare
expect class BackgroundScheduler {
fun schedule(task: Task)
}
// Android implementation uses WorkManager
// iOS implementation uses BGTaskScheduler
// Different APIs, different behaviors, double the code// ✅ One API, both platforms
val scheduler = BackgroundTaskScheduler()
scheduler.enqueue(
id = "sync-data",
trigger = TaskTrigger.Periodic(intervalMs = 900_000),
workerClassName = "SyncWorker",
constraints = Constraints(requiresNetwork = true)
)No expect/actual. No platform checks. Just works.
Single API for Android WorkManager and iOS BGTaskScheduler. Write scheduling logic once in common code.
Sequential task execution with automatic state recovery. If interrupted, chains resume where they left off.
scheduler.beginWith(TaskRequest("Download"))
.then(TaskRequest("Process"))
.then(TaskRequest("Upload"))
.enqueue()Ready-to-use workers for common tasks:
HttpRequestWorker — Make HTTP callsHttpDownloadWorker — Download filesHttpUploadWorker — Upload filesHttpSyncWorker — Sync dataFileCompressionWorker — Compress filesBuilt-in SSRF protection, input validation, and resource limits (v2.3.1+).
Workers return structured results with custom data (v2.3.0+):
override suspend fun doWork(input: String?): WorkerResult {
val result = uploadFile()
return WorkerResult.Success(
message = "Upload complete",
data = mapOf("fileSize" to result.size, "duration" to result.duration)
)
}Add to your build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("dev.brewkits:kmpworkmanager:2.3.4")
}
}
}class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
KmpWorkManager.initialize(
context = this,
config = KmpWorkManagerConfig(
logLevel = Logger.Level.INFO
)
)
}
}Step 1: Create Worker Factory
// iosMain/MyWorkerFactory.kt
class MyWorkerFactory : IosWorkerFactory {
override fun createWorker(workerClassName: String): IosWorker? {
return when (workerClassName) {
"DataSyncWorker" -> DataSyncWorkerIos()
else -> null
}
}
}Step 2: Initialize in AppDelegate
// iOSApp.swift
import ComposeApp // Your shared framework name
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
override init() {
super.init()
// Initialize Koin with worker factory
KoinInitializerKt.doInitKoin(platformModule: IOSModuleKt.iosModule)
}
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Register background task handlers
registerBackgroundTasks()
return true
}
private func registerBackgroundTasks() {
// See platform-setup.md for full implementation
}
}Step 3: Add to Info.plist
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>kmp_chain_executor_task</string>
</array>→ Full setup guide: Platform Setup Documentation
// commonMain
class DataSyncWorker : CommonWorker {
override suspend fun doWork(input: String?): WorkerResult {
val api = ApiClient()
val data = api.fetchLatestData()
database.save(data)
return WorkerResult.Success(
message = "Synced ${data.size} items",
data = mapOf("count" to data.size)
)
}
}Platform-specific implementations:
// androidMain
class DataSyncWorkerAndroid : AndroidWorker {
override suspend fun doWork(input: String?): WorkerResult {
return DataSyncWorker().doWork(input)
}
}
// iosMain
class DataSyncWorkerIos : IosWorker {
override suspend fun doWork(input: String?): WorkerResult {
return DataSyncWorker().doWork(input)
}
}// In your shared code
val scheduler = BackgroundTaskScheduler()
scheduler.enqueue(
id = "data-sync",
trigger = TaskTrigger.Periodic(intervalMs = 900_000), // 15 minutes
workerClassName = "DataSyncWorker",
constraints = Constraints(
requiresNetwork = true,
requiresCharging = false
)
)The task will run every 15 minutes on both Android and iOS, only when network is available.
| Feature | Android | iOS |
|---|---|---|
| One-time tasks | ✅ WorkManager | ✅ BGTaskScheduler |
| Periodic tasks | ✅ Min 15 minutes | ✅ Opportunistic |
| Exact timing | ✅ AlarmManager | ❌ Not available |
| Task chains | ✅ WorkContinuation | ✅ With state recovery |
| Network constraint | ✅ Enforced | |
| Battery constraint | ✅ Enforced | |
| Runs when app closed | ✅ Yes |
iOS Limitations:
|
📊 Data Synchronization Sync user data with your server periodically, only when connected to WiFi. scheduler.enqueue(
id = "sync",
trigger = Periodic(15.minutes),
constraints = Constraints(
requiresNetwork = true,
requiresCharging = false
)
) |
📤 Background Uploads Upload photos/videos when device is charging and on WiFi. scheduler.enqueue(
id = "upload",
workerClassName = "UploadWorker",
constraints = Constraints(
requiresNetwork = true,
requiresCharging = true
)
) |
⛓️ Multi-step Workflows Chain tasks together with automatic retry on failure. scheduler.beginWith(
TaskRequest("Download")
).then(
TaskRequest("Process")
).then(
TaskRequest("Upload")
).enqueue() |
📘 Getting Started
📖 Core Concepts
🎯 Platform-Specific
Check the /composeApp directory for a complete demo app with:
🚀 Performance
🐛 Fixes
TaskChain.enqueue() is now suspending→ Full changelog: CHANGELOG.md
→ Migration guide: MIGRATION_V2.3.3_TO_V2.3.4.md
| Component | Version |
|---|---|
| Kotlin | 2.1.21+ |
| Android | 7.0+ (API 24) |
| iOS | 13.0+ |
| Gradle | 8.0+ |
Dependencies:
┌─────────────────────────────────────┐
│ Common API (Multiplatform) │
│ BackgroundTaskScheduler Interface │
└─────────────┬───────────────────────┘
│
┌───────┴────────┐
│ │
┌─────▼─────┐ ┌────▼─────┐
│ Android │ │ iOS │
│ WorkManager│ │BGTask │
│ │ │Scheduler │
└────────────┘ └──────────┘
All scheduling logic lives in common code. Platform implementations handle OS-specific details transparently.
Contributions are welcome! Please:
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)Before submitting:
./gradlew test
./gradlew ktlintCheck
Copyright 2024-2026 Nguyễn Tuấn Việt
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
💬 Need help?
⭐ Like this project? Give it a star on GitHub!
Built with ❤️ by Nguyễn Tuấn Việt
Background task scheduling for Kotlin Multiplatform
Write once, run everywhere — Schedule background tasks on Android and iOS from shared code
Features • Installation • Quick Start • Documentation • Examples
Building a multiplatform app that needs background task scheduling?
// ❌ Platform-specific nightmare
expect class BackgroundScheduler {
fun schedule(task: Task)
}
// Android implementation uses WorkManager
// iOS implementation uses BGTaskScheduler
// Different APIs, different behaviors, double the code// ✅ One API, both platforms
val scheduler = BackgroundTaskScheduler()
scheduler.enqueue(
id = "sync-data",
trigger = TaskTrigger.Periodic(intervalMs = 900_000),
workerClassName = "SyncWorker",
constraints = Constraints(requiresNetwork = true)
)No expect/actual. No platform checks. Just works.
Single API for Android WorkManager and iOS BGTaskScheduler. Write scheduling logic once in common code.
Sequential task execution with automatic state recovery. If interrupted, chains resume where they left off.
scheduler.beginWith(TaskRequest("Download"))
.then(TaskRequest("Process"))
.then(TaskRequest("Upload"))
.enqueue()Ready-to-use workers for common tasks:
HttpRequestWorker — Make HTTP callsHttpDownloadWorker — Download filesHttpUploadWorker — Upload filesHttpSyncWorker — Sync dataFileCompressionWorker — Compress filesBuilt-in SSRF protection, input validation, and resource limits (v2.3.1+).
Workers return structured results with custom data (v2.3.0+):
override suspend fun doWork(input: String?): WorkerResult {
val result = uploadFile()
return WorkerResult.Success(
message = "Upload complete",
data = mapOf("fileSize" to result.size, "duration" to result.duration)
)
}Add to your build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("dev.brewkits:kmpworkmanager:2.3.4")
}
}
}class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
KmpWorkManager.initialize(
context = this,
config = KmpWorkManagerConfig(
logLevel = Logger.Level.INFO
)
)
}
}Step 1: Create Worker Factory
// iosMain/MyWorkerFactory.kt
class MyWorkerFactory : IosWorkerFactory {
override fun createWorker(workerClassName: String): IosWorker? {
return when (workerClassName) {
"DataSyncWorker" -> DataSyncWorkerIos()
else -> null
}
}
}Step 2: Initialize in AppDelegate
// iOSApp.swift
import ComposeApp // Your shared framework name
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
override init() {
super.init()
// Initialize Koin with worker factory
KoinInitializerKt.doInitKoin(platformModule: IOSModuleKt.iosModule)
}
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Register background task handlers
registerBackgroundTasks()
return true
}
private func registerBackgroundTasks() {
// See platform-setup.md for full implementation
}
}Step 3: Add to Info.plist
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>kmp_chain_executor_task</string>
</array>→ Full setup guide: Platform Setup Documentation
// commonMain
class DataSyncWorker : CommonWorker {
override suspend fun doWork(input: String?): WorkerResult {
val api = ApiClient()
val data = api.fetchLatestData()
database.save(data)
return WorkerResult.Success(
message = "Synced ${data.size} items",
data = mapOf("count" to data.size)
)
}
}Platform-specific implementations:
// androidMain
class DataSyncWorkerAndroid : AndroidWorker {
override suspend fun doWork(input: String?): WorkerResult {
return DataSyncWorker().doWork(input)
}
}
// iosMain
class DataSyncWorkerIos : IosWorker {
override suspend fun doWork(input: String?): WorkerResult {
return DataSyncWorker().doWork(input)
}
}// In your shared code
val scheduler = BackgroundTaskScheduler()
scheduler.enqueue(
id = "data-sync",
trigger = TaskTrigger.Periodic(intervalMs = 900_000), // 15 minutes
workerClassName = "DataSyncWorker",
constraints = Constraints(
requiresNetwork = true,
requiresCharging = false
)
)The task will run every 15 minutes on both Android and iOS, only when network is available.
| Feature | Android | iOS |
|---|---|---|
| One-time tasks | ✅ WorkManager | ✅ BGTaskScheduler |
| Periodic tasks | ✅ Min 15 minutes | ✅ Opportunistic |
| Exact timing | ✅ AlarmManager | ❌ Not available |
| Task chains | ✅ WorkContinuation | ✅ With state recovery |
| Network constraint | ✅ Enforced | |
| Battery constraint | ✅ Enforced | |
| Runs when app closed | ✅ Yes |
iOS Limitations:
|
📊 Data Synchronization Sync user data with your server periodically, only when connected to WiFi. scheduler.enqueue(
id = "sync",
trigger = Periodic(15.minutes),
constraints = Constraints(
requiresNetwork = true,
requiresCharging = false
)
) |
📤 Background Uploads Upload photos/videos when device is charging and on WiFi. scheduler.enqueue(
id = "upload",
workerClassName = "UploadWorker",
constraints = Constraints(
requiresNetwork = true,
requiresCharging = true
)
) |
⛓️ Multi-step Workflows Chain tasks together with automatic retry on failure. scheduler.beginWith(
TaskRequest("Download")
).then(
TaskRequest("Process")
).then(
TaskRequest("Upload")
).enqueue() |
📘 Getting Started
📖 Core Concepts
🎯 Platform-Specific
Check the /composeApp directory for a complete demo app with:
🚀 Performance
🐛 Fixes
TaskChain.enqueue() is now suspending→ Full changelog: CHANGELOG.md
→ Migration guide: MIGRATION_V2.3.3_TO_V2.3.4.md
| Component | Version |
|---|---|
| Kotlin | 2.1.21+ |
| Android | 7.0+ (API 24) |
| iOS | 13.0+ |
| Gradle | 8.0+ |
Dependencies:
┌─────────────────────────────────────┐
│ Common API (Multiplatform) │
│ BackgroundTaskScheduler Interface │
└─────────────┬───────────────────────┘
│
┌───────┴────────┐
│ │
┌─────▼─────┐ ┌────▼─────┐
│ Android │ │ iOS │
│ WorkManager│ │BGTask │
│ │ │Scheduler │
└────────────┘ └──────────┘
All scheduling logic lives in common code. Platform implementations handle OS-specific details transparently.
Contributions are welcome! Please:
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)Before submitting:
./gradlew test
./gradlew ktlintCheck
Copyright 2024-2026 Nguyễn Tuấn Việt
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
💬 Need help?
⭐ Like this project? Give it a star on GitHub!
Built with ❤️ by Nguyễn Tuấn Việt