0Pricing
Kotlin Academy · Lesson

Dependency Injection in KMP with Koin

Wire shared modules together using Koin in a multiplatform setup.

Dependency Injection in KMP with Koin is a free Kotlin Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why DI in KMP?

Dependency Injection decouples component creation from usage. In KMP, Koin is the go-to DI framework because it is pure Kotlin and works in commonMain without annotation processors.

// No annotation processing needed — Koin uses lambda DSL
// Add to build.gradle.kts:
// implementation("io.insert-koin:koin-core:3.5.0")
// androidMain: koin-android
// iosMain: no extra dep
fun main() { println("Koin works on all KMP targets") }

Koin Module DSL

Define bindings in a module { } block using single, factory, or scoped.

import org.koin.core.module.dsl.*
import org.koin.dsl.*
val appModule = module {
    single { HttpClient() }               // one instance
    single<UserRepository> { RemoteUserRepository(get()) }
    factory { GetUserUseCase(get()) }      // new instance each time
}

Shared Module in commonMain

Define a shared Koin module in commonMain. Platform modules extend it with platform-specific bindings.

// commonMain/di/SharedModule.kt
import org.koin.dsl.module
fun sharedModule() = module {
    single<UserRepository> { RemoteUserRepository(get()) }
    single { GetUserUseCase(get()) }
    single { UserViewModel(get()) }
}

Platform Module in androidMain

Platform modules provide the actual HTTP engine, database driver, and Android-specific singletons.

// androidMain/di/AndroidModule.kt
import io.ktor.client.engine.okhttp.*
import org.koin.dsl.module
fun androidModule() = module {
    single { OkHttp.create() }    // Ktor engine
    single { createHttpClient(get()) }
    includes(sharedModule())
}

startKoin in Android Application

Call startKoin { } in your Android Application.onCreate. Use androidContext(this) to make the context available.

// androidMain:
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
// In Application.onCreate():
startKoin {
    androidContext(this@MyApp)
    modules(androidModule())
}

Starting Koin on iOS

On iOS, call KoinApplication.start() from your Swift AppDelegate or App struct, passing the shared Koin modules.

// iosMain/KoinInit.kt:
import org.koin.core.context.startKoin
fun initKoin() {
    startKoin {
        modules(iosModule())
    }
}
// Swift: KoinInitKt.doInitKoin() in AppDelegate

Resolving Dependencies

In commonMain ViewModels or use cases, use get() inside a module or inject() / getKoin().get() at call sites.

import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class UserViewModel : KoinComponent {
    private val getUser: GetUserUseCase by inject()
    fun load(id: String) = runBlocking { getUser(id) }
}

Koin Scopes for ViewModels

Use viewModel { } in the Android module for Android ViewModels, or the platform-agnostic factory { } for shared VMs.

// androidMain (with koin-android):
import org.koin.androidx.viewmodel.dsl.viewModel
val androidVmModule = module {
    viewModel { UserViewModel(get()) }
}
// In Compose:
// val vm: UserViewModel = koinViewModel()

Testing with Koin

Use startKoin in test setup with a test module that replaces real dependencies with fakes.

import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.dsl.module
class UserViewModelTest {
    @BeforeTest fun setup() {
        startKoin { modules(module {
            single<UserRepository> { FakeUserRepository() }
            single { GetUserUseCase(get()) }
        })}
    }
    @AfterTest fun teardown() = stopKoin()
}

Single vs Factory vs Scoped

single: one instance for app lifetime. factory: new instance per get(). scoped: one instance per Koin scope (e.g., screen scope).

import org.koin.dsl.module
val lifetimeModule = module {
    single { Database() }           // shared, one instance
    factory { Analytics() }         // new on each get()
    scope<ScreenScope> {
        scoped { ScreenViewModel() } // one per screen lifecycle
    }
}

Koin vs Manual DI vs Hilt

Koin: multiplatform, no KSP/KAPT, runtime DI. Hilt: Android-only, compile-time, annotation-driven. Manual DI: maximum control, no framework overhead. KMP projects use Koin for sharing.

fun main() {
    // Koin  : commonMain, runtime, lambda DSL
    // Hilt  : androidMain only, compile-time
    // Manual: no framework, explicit wiring in factory/module files
    println("Choose Koin for KMP; Hilt for Android-only projects")
}

Lazy Injection with by inject()

by inject() lazily resolves a dependency on first access — useful in classes that implement KoinComponent.

import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class AppService : KoinComponent {
    private val repo: UserRepository by inject()  // resolved on first use
    fun doWork() = println(repo.javaClass.simpleName)
}

Quick Check

Which Koin scope creates a new instance every time get() is called?

Recap

Koin uses a lambda DSL with no annotation processing — ideal for KMP. Define shared modules in commonMain, platform modules in platform source sets. Use single for singletons, factory for fresh instances, and by inject() for lazy resolution.

Frequently asked questions

Is the “Dependency Injection in KMP with Koin” lesson free?

Yes — the full text of “Dependency Injection in KMP with Koin” is free to read here on the web, and the Kotlin Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Kotlin Academy course, upgrade to CoddyKit PRO.

What will I learn in “Dependency Injection in KMP with Koin”?

Wire shared modules together using Koin in a multiplatform setup. You practise Kotlin Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Kotlin Academy?

No prior experience is required. Kotlin Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Dependency Injection in KMP with Koin” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Kotlin Academy lesson?

Yes. Every Kotlin Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. KMP Project Structure: commonMain, androidMain, iosMain
  2. expect/actual Mechanism for Platform APIs
  3. Sharing Repository and Use Case Layers
  4. Dependency Injection in KMP with Koin
← Back to Kotlin Academy