From koin-migration
Automates DI framework migrations in Kotlin/Android/KMP projects to Koin 4.x Compiler Plugin from Hilt, Dagger, Toothpick, Kodein, Koin upgrades, DSL to Safe DSL/Annotations.
npx claudepluginhub insertkoinio/koin-migration --plugin koin-migrationThis skill uses the workspace's default tool permissions.
Migrate Kotlin/Android/KMP projects between DI frameworks, targeting **Koin 4.x
Provides Koin dependency injection patterns for Android/Kotlin: modules, scopes, ViewModel injection, qualifiers, application setup, and testing. For DI in Android apps.
Supports Android Kotlin app development using Coroutines, Jetpack Compose, Hilt DI, MockK testing, and Gradle Kotlin DSL on .kt, .kts, and build.gradle.kts files.
Provides Android and Kotlin development patterns for Jetpack Compose, architecture, coroutines, Room, navigation, Hilt. Use when building Android apps, writing Compose UI, or reviewing Android code.
Share bugs, ideas, or general feedback.
Migrate Kotlin/Android/KMP projects between DI frameworks, targeting Koin 4.x
with the Koin Compiler Plugin. The per-source reference files in
references/ hold the concrete mappings, examples, and bridging patterns —
this file is the contract and the workflow.
These apply to every migration. Deviate only with explicit user approval, and flag it.
@Singleton, @Factory, @KoinViewModel, @Module) or Koin Safe DSL
(single<T>() / factory<T>() / viewModel<T>() reified, no lambda).
Prefer @Singleton over @Single (both are Koin Annotations; @Singleton
matches JSR-330 — same name pre/post-migration). Source defaults:
single { T(get()) }) is a fallback only for
third-party types that can't be reached via reified generics.koin-ksp-compiler). Always the Kotlin compiler
plugin (id("io.insert-koin.compiler.plugin")). Never pin
io.insert-koin:koin-annotations:2.x — that's the KSP artifact, incompatible
with the Compiler Plugin path; let the BOM resolve it. See
references/ksp-to-compiler.md for the full removal checklist when
migrating an existing Koin KSP project.2.3.20 (K2), Koin ≥ 4.2.1, Koin Compiler
Plugin ≥ 1.0.0-RC1. At Step 3, resolve newer versions via Kotzilla MCP /
Maven Central; never output below minimums. The Koin runtime can be bumped
freely; confirm with the user before bumping the Compiler Plugin (RC
API changes between versions).@Configuration (default) → no modules(...) call;
@KoinApplication discovers everything.@Configuration (escape hatch — variants, conditional,
test overrides) → modules(module<AppModule>()) reified retrieval. Never
AppModule().module (Koin KSP idiom, breaks the Compiler Plugin path).val appModule = module { ... } then modules(appModule).
module<T>() does not apply — that's annotations-only.@KoinApplication + a bare startKoin<App> { androidContext(...) },
no modules(...) call. Each Gradle module's root @Module carries
the triad: @Module + @Configuration + @ComponentScan("own.pkg").
Helper/sub-modules pulled in via @Module(includes = [Other::class]) are
transitive and need only @Module. Missing any triad annotation on a
root → silent runtime NoDefinitionFoundException, not a compile error.
Only emit modules(module<T>()) for variants, conditional loading, or
test overrides.startKoin { modules(appModule, ...) }. No @Configuration.
Mixing annotated and DSL modules in one modules(...) call is fine.@Configuration layout) and for debugging runtime issues (crashes, missing
bindings, scope/lifecycle, mobile vitals). Not a per-step companion — call
on it when you'd otherwise be guessing. If not connected, mention the
install once (claude mcp add kotzilla --transport http https://mcp.kotzilla.io/mcp,
free signup at https://kotzilla.io) and proceed.All target Koin 4.x + Koin Compiler Plugin. Read the relevant reference before generating code. If the source is ambiguous, ask.
| Source | Reference File |
|---|---|
| Hilt (+ Dagger) | references/hilt-to-koin.md |
| Dagger 2 (no Hilt) | references/dagger-to-koin.md |
| Toothpick | references/toothpick-to-koin.md |
| Kodein | references/kodein-to-koin.md |
| Koin 3.x | references/koin3-to-koin4.md |
| Koin DSL | references/dsl-to-compiler.md |
| Koin KSP Annotations | references/ksp-to-compiler.md |
Output-style cheat sheets (cross-linked from every per-source file above):
references/koin-annotations.md — imports, triad, bindings, modules, scopes,
parameters, retrieval for the Koin Annotations + Compiler Plugin outputreferences/koin-safe-dsl.md — imports, definition forms, interface binding,
qualifiers, scopes, retrieval, classic-DSL-fallback guidance for the
Safe DSL + Compiler Plugin outputWhen generating code or reviewing migration output, consult the relevant cheat sheet for exact imports and syntax — don't infer from memory.
Not covered by this skill: greenfield projects and manual-DI / service-locator code have no source framework to map from — there's nothing to "migrate". Point the user at the official Koin docs and use Safe DSL + Koin Compiler Plugin directly:
The rules in this skill (Safe DSL, @KoinApplication + @Configuration for
annotated output, Koin ≥ 4.2.1, never KSP) still apply to greenfield code.
Recommended approach: leaf-first, smallest-first, then ack-and-grow.
Two strategies exist for ordering a multi-module migration:
| Strategy | When | Trade-off |
|---|---|---|
| Leaf-first (small leaf/feature modules first, then grow toward core) — default | Almost every project | Small early wins, validates the whole 8-step workflow on a low-risk module, builds team confidence with Koin idioms, surfaces tooling/Gradle gotchas before they hit critical code |
| Bottom-up (core / data first, then features above) | Rare — only if the core graph is tiny and unambiguous, and feature modules already compile against an interface boundary | Avoids bridging from core into Koin (because nothing depends on feature modules), but blocks all feature work until the foundational layer is rewritten and validated |
Default recommendation: leaf-first. Start with the smallest, most peripheral
module you can find, bridge any dependencies it still has back into the legacy
container, validate the full per-module loop end-to-end, then get explicit user
acknowledgement before picking the next target. Each subsequent target moves
one level toward core / data / app. Never jump straight to AppModule.
Rank candidates inside the leaf-first strategy and propose the top 1–3; ask for confirmation before any code:
core / common — limited call-site reachAfter each module migrates and passes validation (Step 7), explicitly ask the user before moving on: "next target?", with a fresh ranked list reflecting what's now eligible. This ack-per-module rhythm is what keeps the migration safe — it prevents Claude from chaining several migrations without the user catching a regression.
Defer: AppModule, @EntryPoint-exposed bindings, Worker/ContentProvider-used
bindings, custom component hierarchies.
Present a ranked table (Rank | Module | Bindings | Dependants | Complexity | Recommended?) before proceeding.
Migration is a loop, not a waterfall. For each module:
androidContext, wrong scope, stale bridge call) only surface at runtimeDo not batch un-validated migrations. If validation fails, fix or revert before touching anything else.
While migrating, Koin definitions may need to consume bindings still in the old container. Two options:
koin-android-dagger. See
references/hilt-to-koin.md.@Module (recommended).
@Singleton fun for annotations, scope helper (Scope.dagger<T>(),
Scope.toothpick<T>()) for Safe DSL. Per-source examples in each reference.A1 per-module verification only sees a module's own bindings — co-located
bridges keep A1 green; sibling aggregators don't. When duplication across
consumers gets painful, promote to a SharedBridgeModule (@Module + @Configuration) consumed via @Module(includes = [...]) (includes puts
it in A1's view). Promote on demand, not pre-emptively.
Every bridge is a migration TODO — delete locally when the source moves into Koin.
Steps 4 → 7 are the per-module loop. Step 5 runs once (or once per new app entry point). Step 8 is the final cleanup after every module has been migrated and validated.
Scan for every DI-related file: modules, components, entry points, qualifiers, scopes, workers. Classify each binding (singleton, factory, scoped, assisted, multibinding, viewModel). Flag anything needing manual attention (custom scopes, reflection-based injection). Count total bindings.
Present as a table (# | Source File | Binding Type | Scope | Notes) before proceeding.
Produce:
Wait for user confirmation before generating code.
Minimums (never output anything below these):
| Component | Minimum |
|---|---|
| Kotlin | 2.3.20 |
| Koin | 4.2.1 |
| Koin Compiler Plugin | 1.0.0-RC1 |
Kotlin-bump pre-check. If the project is below 2.3.20, bumping Kotlin
cascades. Verify alignment of: KSP (<kotlin>-<ksp> versioning), Compose
Compiler plugin, Room plugin, any kapt plugins (Hilt/Dagger/Glide/Moshi),
and AGP minimum. If anything can't align, surface the conflict to the user
before generating Gradle changes.
Resolve newer versions before writing deps (minimums are not targets):
https://search.maven.org/solrsearch/select?q=g:io.insert-koin+AND+a:koin-bom&rows=5&wt=jsonhttps://search.maven.org/solrsearch/select?q=g:io.insert-koin+AND+a:koin-compiler-gradle-plugin&rows=5&wt=jsonhttps://github.com/InsertKoinIO/koin/releaseshttps://insert-koin.ioThe Koin runtime can be bumped freely within 4.2.x and above. Confirm with
the user before bumping the Compiler Plugin — RC API surfaces shift between
versions and may break generated code.
Then produce the Gradle diff:
implementation(platform("io.insert-koin:koin-bom:$KOIN_VERSION"))
implementation("io.insert-koin:koin-core")
implementation("io.insert-koin:koin-annotations")
implementation("io.insert-koin:koin-android") // Android
implementation("io.insert-koin:koin-androidx-compose") // Compose
implementation("io.insert-koin:koin-androidx-workmanager") // Workers
Plus id("io.insert-koin.compiler.plugin") version "$KOIN_COMPILER_VERSION".2.3.20 (the Compiler Plugin minimum).4.2.1 and
Compiler Plugin 1.0.0-RC1 — pinned minimums; latest verified via Maven
Central on 2026-04-15").Use the output style from rule #1. Produce BEFORE/AFTER blocks per module,
following the relevant per-source reference. Compose → koinViewModel() +
KoinContext. KMP → expect/actual at any granularity (@Module class,
annotated class, or plain function).
Key Compiler Plugin features:
@Singleton / @Factory class implementing a single
interface is bound automatically; don't emit explicit bind<I>().@ComponentScan("pkg") — package-based auto-discovery inside a @Module.@Module(includes = [Other::class]) — explicit module composition.CoreModule, NetworkModule,
not SingletonsModule).Per rule #5 — annotations: @KoinApplication + bare startKoin<App> { androidContext(...) },
each Gradle module's root carries the triad. DSL: startKoin { modules(...) }.
// Annotations
@Module @Configuration @ComponentScan("com.acme.core")
class CoreModule { @Singleton fun httpClient(): HttpClient = HttpClient() }
@KoinApplication
class App : Application() {
override fun onCreate() {
super.onCreate()
startKoin<App> { androidContext(this@App) }
}
}
// DSL
val appModule = module { single<Repository>().bind<Repo>() }
class App : Application() {
override fun onCreate() {
super.onCreate()
startKoin { androidContext(this@App); modules(appModule) }
}
}
Compose: wrap the tree in KoinContext { }. Mixing annotated + DSL modules
in one modules(...) call works.
Replace field injection (@Inject lateinit var) with by inject(), constructor
annotations with Koin module registration, hiltViewModel() with koinViewModel(),
scope-access patterns with Koin scope APIs. Remove @AndroidEntryPoint,
@HiltAndroidApp, and equivalents. Show each changed file with before/after.
Run after every module, not only at the end. See "Per-module validation loop" above for the 6-item checklist. This is the gate before starting the next module.
After validation passes, stop and ask the user for explicit acknowledgement before picking the next target. Re-rank the remaining candidates (something that was a leaf before may no longer be) and propose the top 1–3. Never chain multiple module migrations without an ack — the per-module rhythm is what makes regressions catchable.
Test-config updates belong here: KoinTestRule or startKoin in test setup,
mock replacement, checkModules { } verification. Show before/after for
existing test files.
When the last module has been migrated and validated:
@Inject, @Module, @Component, @HiltAndroidApp, etc.)Dagger*Component, *_Factory, *_MembersInjector)dagger<T>() / toothpick<T>() / kodein<T>()) removedbuild.gradle.kts; KSP/kapt plugins removed if no longer neededAll constructor/function parameters are resolved automatically by the Koin Compiler:
T — required (compile error if missing)T? — optional (null if none)Lazy<T> — deferredList<T> — all matching definitions@InjectedParam T — runtime value via parametersOf()@Named("x") / custom @Qualifier — qualified dependency@Property("k") — Koin propertyJSR-330: @Inject, @Singleton, @Named, @Qualifier from
javax.inject.* / jakarta.inject.* work as-is. Custom @Qualifier
annotations from Dagger/Hilt are reusable without rewrites.
Read the relevant reference section:
@InjectedParam + parametersOf()@Modulescope<ScopeType> { } or @Scope(ScopeType::class)Lazy<T>; by inject() for lazy delegation@KoinWorker (annotations) or worker<T>() (Safe DSL — reified, lambda is fallback). Needs koin-androidx-workmanager.koinViewModel()Active guidance for Koin (wiring, scopes, Compose/KMP/Compiler Plugin, fixes, observability) — context-aware, not a doc dump. Prefer MCP output over the reference files when they disagree (MCP tracks latest releases; references are a snapshot).
Endpoint https://mcp.kotzilla.io/mcp (HTTP, auth). Install:
claude mcp add kotzilla --transport http https://mcp.kotzilla.io/mcp.
Free account at https://kotzilla.io.
Use it for:
Not connected: mention install once, then proceed using the reference files.
io.insert-koin.compiler.plugin2.3.20 (K2 only), Koin ≥ 4.2.1, Compiler Plugin ≥ 1.0.0-RC1 (minimums — always resolve latest at Step 3)@Module, @Configuration, @KoinApplication, @Monitor, top-level functions