Implementing Koin in a Compose Multiplatform App - The right way

May 04, 2025
This tutorial walks you through setting up Koin for dependency injection in a Kotlin Multiplatform (KMP) project using Compose. We’ll cover shared code, platform-specific configurations, and best practices.
You may be interested in these articles as well:

1. Add Koin Dependencies

In your shared module’s build.gradle.kts, add Koin dependencies:
commonMain.dependencies { // Koin core api("io.insert-koin:koin-core:3.5.0") // Koin for Compose api("io.insert-koin:koin-androidx-compose:3.5.0") api("io.insert-koin:koin-androidx-compose-viewmodel:3.5.0") // ... other dependencies } androidMain.dependencies { // Android-specific Koin integration implementation("io.insert-koin:koin-android:3.5.0") }

2. Define Your Koin Modules

Create a file (e.g., Koin.kt) in your commonMain to define your modules. I recommend this to be in the root package (e.g. com.example), so it’s easy to see which dependencies are available to inject.
expect val platformModule: Module fun initKoinModules(): Array<Module> { val dataModule = module { } val viewModeModules = module { } return arrayOf(dataModule, viewModeModules, platformModule) }
Some modules may be specific to Android or iOS. These should be included in the platformModule for their respective platforms.

3. Platform specific Koin startup

For each platform, we’ll provide the actual body of the platformModule function along with a helper function to initialize Koin.

Android

Let’s create a new file KoinHelper.kt in androidMain. Maintain the same package name as Koin.kt created in commonMain.
androidMain:
actual val platformModule = module { // Android-specific singletons/factories } fun initKoin(context: Context) { val koinModules = initKoinModules() startKoin { androidContext(context) modules(*koinModules) } }
Now call it in your Application class.
class MyApplication : Application() { override fun onCreate() { super.onCreate() initKoin(this) } }
Done for Android.

iOS

In iosMain, in a Kotlin file for iOS (e.g., KoinHelper.kt):
(Maintain the same package name as Koin.kt created in commonMain)
actual val platformModule = module { // iOS-specific singletons/factories } fun initKoin() { val koinModules = initKoinModules() startKoin { modules(*koinModules) } }
Now call it in your iOSApp.swift file:
@main struct iOSApp: App { init() { KoinHelperKt.doInitKoin() } var body: some Scene { WindowGroup { ContentView().edgesIgnoringSafeArea(.all) } } }
Done.

4. Injecting Dependencies

  • For classes: implement KoinComponent and use by inject() or get().
  • For Compose screens: use koinViewModel() to get your view models.
  • For Classes added in Koin modules: using constructor params.
Example in a Compose screen:
@Composable fun HomeScreen(viewModel: HomeViewModel = koinViewModel()) { // Use your viewModel here }
Example in a repository or utility:
class SomeRepository : KoinComponent { private val dao: SomeDao by inject() }

5. Best Practices

  • Use single for shared instances, factory for new instances, and viewModel for Compose view models.
  • Use platformModule for platform-specific implementations (e.g., file pickers, context).

6. Summary

  1. Add Koin dependencies to your shared and platform-specific source sets.
  2. Define modules in shared code, and use expect/actual for platform-specific modules.
  3. Initialize Koin in your platform entry points (Application for Android, a helper for iOS).
  4. Inject dependencies using Koin’s APIs in your repositories, view models, and Compose screens.