-
Notifications
You must be signed in to change notification settings - Fork 2
single api guide
End-to-end consumer-facing guide for the worker-kmp single-API in commonMain. Consumers write
100% commonMain code for the worker-kmp scheduling/observation/init/Koin domain — per-platform
glue is auto-generated by cmp-worker-app-plugin at build time.
// commonMain — entire worker-kmp setup
@WorkerKmpApp(
title = "My App",
iosBundleId = "com.example.myapp",
)
public fun appKoinModules(): List<Module> = listOf(
DataModule,
SyncObserverKoinModule, // from cmp-worker-sync — binds UniqueWorkObserver
)
@WorkerKmpWorkers(workers = [DataSyncWorker::class, NotificationWorker::class])
public fun workerDeclarations() = Unit
public class DataSyncWorker(
context: WorkerContext,
private val repo: CurrencyRepository, // public
) : CoroutineWorker(context) { /* ... */ }// commonMain — your app's shared init (the function EVERY platform entry point calls).
// One line wires workers on Android + iOS + Desktop + Web. Do NOT put it in a single
// platform's app class — the others would silently get no workers.
fun initApp(config: KoinAppDeclaration? = null) {
startKoin {
config?.invoke(this) // Android binds androidContext(this@App) here
modules(appKoinModules())
}
WorkerKmpAuto.install() // single line — codegen handles the rest
}// androidMain — the app class only supplies the Android Koin context; NO worker code:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
initApp { androidContext(this@MyApp) }
}
}
// desktopMain: fun main() { initApp(); ... }
// wasmJsMain / jsMain: fun main() { initApp(); ... }
// iosMain: ViewController { initApp(); ... }That's it. iosMain / desktopMain / wasmJsMain source sets have ZERO consumer-written files
for the worker-kmp domain — and every platform is wired from the one commonMain install().
Use both @WorkerKmpApp + @WorkerKmpWorkers. The plugin generates per-platform launchers
(Android Application + Activity, iOS MainViewController, Desktop main(), Web main())
PLUS the worker-init files. Consumer's commonMain code is sufficient — no per-platform shell
classes needed.
Use only @WorkerKmpWorkers. The plugin skips launcher generation; you keep your own app
shells and call WorkerKmpAuto.install() once from your commonMain shared init (exactly as
the Quick start above shows), after Koin is started.
⚠️ Placement: commonMain, not a single platform's app class.install()is a no-arg commonMain call, so it goes in the one shared init every platform funnels through. Putting it inApplication.onCreateonly leaves Desktop / iOS / Web silently un-wired — they compile and run but schedule no workers, and an Android-only smoke test won't catch it. If your app has no shared commonMain init, addinstall()to each platform entry point.
samples/kmp-project-template uses this shape: @WorkerKmpWorkers in
cmp-shared/WorkerDeclarations.kt, and WorkerKmpAuto.install() in the commonMain
cmp-shared/utils/KoinExt.kt#initKoin — so all five of its platforms are wired from one line.
- Goes on a top-level commonMain function (typically
public fun workerDeclarations() = Unit) -
workers: Array<KClass<*>>— each must extendCoroutineWorkerwithWorkerContextas first primary-constructor param + bepublic - OPTIONAL — omit if no workers needed (e.g. CMP app shell only)
- Within-module aggregation — multiple sites in the SAME module are aggregated; cross- module aggregation is NOT supported (place annotation in the module that depends on every worker-owning module)
- Co-annotation on worker classes — opts the worker into a subset of generated init files
- Default (annotation absent): worker registered on all 4 platforms
- Goes on
public fun appKoinModules(): List<Module>— REMOVED the v3.1.xfactory: WorkManagerFactoryparameter (codegen handles factory selection now)
All worker classes AND their primary-constructor dep types MUST be public. KSP processor
errors with clear suggested-fix message if you use internal class or private class —
codegen-emitted code lives in your cmp-shared/build/generated/... and can only reference
public symbols across module boundaries.
public class IntervalWorker(
ctx: WorkerContext,
val intervalMs: Long = 5000L, // SKIPPED from Koin autowiring — default used at runtime
) : CoroutineWorker(ctx) { ... }KSP detects KSValueParameter.hasDefault == true and emits
register<IntervalWorker> { ctx -> IntervalWorker(context = ctx) } (no getKoin().get<Long>()).
// ❌ Compile error — Koin runtime erases generic type args
public class GenericWorker(
ctx: WorkerContext,
val store: Store<String, ExchangeRates>,
) : CoroutineWorker(ctx)
// ✅ Use @Named qualifier
public class GenericWorker(
ctx: WorkerContext,
@Named("exchange-rates") val store: Store<String, ExchangeRates>,
) : CoroutineWorker(ctx)
// + matching Koin binding
single<Store<String, ExchangeRates>>(named("exchange-rates")) { ... }startKoin { … } MUST complete BEFORE WorkerKmpAuto.install() — placing install() at the
end of your shared init (after the startKoin block) satisfies this on every platform. On
Android the actual additionally reads Context from the androidContext() binding, so bind it
inside your startKoin config. Wrong order → the shim throws IllegalStateException with a
clear fix message.
Pure setup state — safe to call from your shared init without runBlocking (no ANR risk). No
first-sync is enqueued — that's the consumer's responsibility after WorkerKmpAuto.install()
returns (e.g. get<WorkManager>().enqueueUniqueWork(...) from your bootstrap code).
// Customize via Koin binding (D22 — annotations can't carry data-class instances)
val MyAppModule = module {
single { WorkerKmpHostConfig(logTag = "my-app.worker") }
}Defaults: koinScopeQualifier = null (global scope), logTag = "worker-kmp.host".
Koin module overrides — allowOverride = true is REQUIRED:
@Before
fun setUp() {
startKoin { modules(appKoinModules()) }
WorkerKmpAuto.install()
// Replace the codegen-bound WorkManager with a fake
loadKoinModules(
module {
single<WorkManager>(allowOverride = true) { FakeWorkManager() }
},
)
}For pure-commonMain unit tests of workers (no factory needed), instantiate the worker class
directly with a fake WorkerContext (same pattern existing worker-kmp samples already use).
Your consumer's per-platform source sets contain ZERO files for the worker-kmp scheduling/observation/init/Koin domain. Verify:
find your-app/src/{androidMain,iosMain,desktopMain,wasmJsMain} \
\( -name 'SyncManager*.kt' \
-o -name 'WorkScheduler*.kt' \
-o -name 'WorkerKmpAuto.kt' \
-o -name 'Sync*Initializer.kt' \) \
-not -path '*/build/*'Expected output: EMPTY.
Koin Compiler Plugin — compileSafety + @Provided (#61)
If your app uses the Koin Compiler Plugin (io.insert-koin.compiler.plugin) with
koinCompiler { compileSafety = true } and you inject WorkManager into an
annotation-defined component, the compile-safety checker reports:
[Koin][KOIN-D001] Missing dependency: io.github.mobilebytelabs.worker.WorkManager
required by: SyncViewModel (parameter 'workManager')
This is expected, and correct — WorkManager is bound at runtime by
WorkerKmpAuto.install() (which calls loadKoinModules(...)), so it is deliberately
not part of the compile-time annotation graph. It is an externally-provided
dependency, exactly like Android's Context/SavedStateHandle.
Fix — mark the injection @Provided (Koin's designed escape for
runtime/externally-supplied types). No worker-kmp change is needed:
import org.koin.core.annotation.Provided
import org.koin.core.annotation.KoinViewModel
import io.github.mobilebytelabs.worker.WorkManager
@KoinViewModel
class SyncViewModel(
@Provided private val workManager: WorkManager, // supplied at runtime by WorkerKmpAuto.install()
) : ViewModel()@Provided tells the checker "this type is supplied externally at runtime" and
suppresses KOIN-D001 — while the real binding is still owned by
WorkerKmpAuto.install(). Do this at every site that injects a worker-kmp
runtime-provided type (WorkManager, and any other type bound only via install()).
Do not set
compileSafety = falseto work around this — that disables the whole safety net.@Providedis the targeted, idiomatic fix.
Getting Started
Platform Support
Features
Operations
Release