| name | kmp-di |
| description | Dependency Injection for KMP. Koin multiplatform setup, platform modules, and manual DI patterns. |
KMP Dependency Injection
Cross-platform dependency injection with Koin.
Koin Multiplatform Setup
Dependencies
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.insert-koin:koin-core:${koinVersion}")
implementation("io.insert-koin:koin-test:${koinVersion}")
}
}
val androidMain by getting {
dependencies {
implementation("io.insert-koin:koin-android:${koinVersion}")
}
}
}
Module Definition
val sharedModule = module {
factory { HomeViewModel(get(), get()) }
factory { DetailViewModel(get()) }
factory { GetUsersUseCase(get()) }
factory { GetUserDetailUseCase(get()) }
single<UserRepository> { UserRepositoryImpl(get(), get()) }
single { UserApi(get()) }
single { createDatabase(get()) }
}
Platform Modules
val androidPlatformModule = module {
includes(sharedModule)
single { android.content.Context() }
single { PlatformConnectivityMonitor(get()) }
single { PlatformFileService(get()) }
}
val iosPlatformModule = module {
includes(sharedModule)
single { PlatformConnectivityMonitor() }
single { PlatformFileService() }
}
Koin Start
Android
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MyApp)
modules(androidPlatformModule)
}
}
}
iOS
fun initKoin() {
startKoin {
modules(iosPlatformModule)
}
}
Compose Multiplatform
fun main() {
startKoin {
modules(sharedModule)
}
App()
}
ViewModel Injection
Android (ViewModel)
@Composable
fun HomeScreen(
viewModel: HomeViewModel = koinViewModel()
) {
val state by viewModel.state.collectAsStateWithLifecycle()
HomeContent(state)
}
iOS (ScreenModel)
@Composable
fun HomeScreen() {
val model = getScreenModel<HomeScreenModel>()
val state by model.state.collectAsState()
HomeContent(state)
}
Repository Pattern
Factory vs Single
factory { HomeViewModel(get(), get()) }
single<UserRepository> { UserRepositoryImpl(get(), get()) }
scoped(HomeScope.homeScope) { HomeData(get()) }
Named Dependencies
module {
single(named("default")) { DefaultLogger() }
single(named("analytics")) { AnalyticsLogger() }
factory { MyRepository(logger = get(named("default"))) }
}
Manual DI (Alternative)
Simple Service Locator
object ServiceLocator {
private val services = mutableMapOf<String, Any>()
fun <T> get(key: String): T {
return services[key] as T
}
fun register(key: String, service: Any) {
services[key] = service
}
fun init() {
register("repository", UserRepositoryImpl())
register("api", UserApi())
}
}
Pure Kotlin DI
class AppContainer(
private val platformService: PlatformService
) {
val api: UserApi = UserApi()
val database: AppDatabase = createDatabase()
val userRepository: UserRepository by lazy {
UserRepositoryImpl(api, database)
}
val homeViewModel: HomeViewModel by lazy {
HomeViewModel(userRepository, platformService)
}
}
Testing with DI
val testModule = module {
single<MockUserService> { MockUserService() }
single<UserService>(override = true) { get<MockUserService>() }
}
@BeforeTest
fun setup() {
startKoin { modules(testModule) }
}
@AfterTest
fun tearDown() {
stopKoin()
}
Remember: DI simplifies testing. Use it to inject mocks and fakes easily.