Skip to main content
Version: 4.2

Shared Modules Pattern

This guide covers patterns for organizing Koin modules in Kotlin Multiplatform projects.

The Shared Module Pattern

Create a common initialization function that can be extended by each platform:

// commonMain/kotlin/di/KoinHelper.kt
fun initKoin(config: KoinAppDeclaration? = null): KoinApplication {
return startKoin {
includes(config) // Platform-specific extensions
modules(
sharedModule,
dataModule,
domainModule,
platformModule
)
}
}

Module Organization

By Layer

// commonMain/kotlin/di/modules/

// Data layer
val dataModule = module {
single<ApiClient>()
single<UserRepository>()
single<ProductRepository>()
}

// Domain layer
val domainModule = module {
factory<GetUserUseCase>()
factory<GetProductsUseCase>()
factory<CreateOrderUseCase>()
}

// Shared module aggregates all
val sharedModule = module {
includes(dataModule, domainModule)
}

By Feature

// User feature
val userModule = module {
single<UserRepository>()
factory<GetUserUseCase>()
factory<UpdateUserUseCase>()
}

// Product feature
val productModule = module {
single<ProductRepository>()
factory<GetProductsUseCase>()
factory<SearchProductsUseCase>()
}

// Order feature
val orderModule = module {
single<OrderRepository>()
factory<CreateOrderUseCase>()
factory<GetOrderHistoryUseCase>()
}

Platform Extensions

Android Extension

// androidMain/kotlin/di/KoinAndroid.kt
fun initKoinAndroid(context: Context) {
initKoin {
androidContext(context)
androidLogger()
modules(androidModule)
}
}

val androidModule = module {
single<PlatformContext> { AndroidContext(get()) }
single<FileStorage> { AndroidFileStorage(get()) }
single<NetworkMonitor> { AndroidNetworkMonitor(get()) }
}

iOS Extension

// iosMain/kotlin/di/KoinIos.kt
fun initKoinIos() {
initKoin {
modules(iosModule)
}
}

val iosModule = module {
single<PlatformContext> { IosContext() }
single<FileStorage> { IosFileStorage() }
single<NetworkMonitor> { IosNetworkMonitor() }
}

Desktop Extension

// desktopMain/kotlin/di/KoinDesktop.kt
fun initKoinDesktop() {
initKoin {
printLogger()
modules(desktopModule)
}
}

val desktopModule = module {
single<PlatformContext> { DesktopContext() }
single<FileStorage> { DesktopFileStorage() }
}

Expect/Actual Module Pattern

Common Definition

// commonMain/kotlin/di/PlatformModule.kt
expect val platformModule: Module

Platform Implementations

// androidMain
actual val platformModule = module {
single<DatabaseDriver> { AndroidSqliteDriver(AppDatabase.Schema, get(), "app.db") }
single<HttpEngine> { Android.create() }
single<Settings> { AndroidSettings(get()) }
}

// iosMain
actual val platformModule = module {
single<DatabaseDriver> { NativeSqliteDriver(AppDatabase.Schema, "app.db") }
single<HttpEngine> { Darwin.create() }
single<Settings> { NSUserDefaultsSettings(NSUserDefaults.standardUserDefaults) }
}

// jsMain
actual val platformModule = module {
single<DatabaseDriver> { JsSqliteDriver() }
single<HttpEngine> { Js.create() }
single<Settings> { LocalStorageSettings() }
}

Complete Example

Project Structure

shared/
├── src/
│ ├── commonMain/kotlin/
│ │ ├── di/
│ │ │ ├── KoinHelper.kt
│ │ │ ├── PlatformModule.kt // expect
│ │ │ └── modules/
│ │ │ ├── DataModule.kt
│ │ │ ├── DomainModule.kt
│ │ │ └── SharedModule.kt
│ │ ├── data/
│ │ │ ├── api/
│ │ │ └── repository/
│ │ └── domain/
│ │ └── usecase/
│ ├── androidMain/kotlin/
│ │ └── di/
│ │ └── PlatformModule.android.kt
│ └── iosMain/kotlin/
│ └── di/
│ └── PlatformModule.ios.kt

commonMain/kotlin/di/KoinHelper.kt

package com.myapp.di

import org.koin.core.context.startKoin
import org.koin.core.KoinApplication
import org.koin.dsl.KoinAppDeclaration

fun initKoin(config: KoinAppDeclaration? = null): KoinApplication {
return startKoin {
config?.invoke(this)
modules(
sharedModule,
platformModule
)
}
}

commonMain/kotlin/di/modules/SharedModule.kt

package com.myapp.di.modules

import org.koin.dsl.module

val sharedModule = module {
includes(dataModule, domainModule)
}

val dataModule = module {
single<ApiClient>()
single<UserRepository>()
}

val domainModule = module {
factory<GetUserUseCase>()
factory<UpdateUserUseCase>()
}

commonMain/kotlin/di/PlatformModule.kt

package com.myapp.di

import org.koin.core.module.Module

expect val platformModule: Module

androidMain/kotlin/di/PlatformModule.android.kt

package com.myapp.di

import org.koin.dsl.module

actual val platformModule = module {
single<DatabaseDriverFactory> { AndroidDatabaseDriverFactory(get()) }
single<HttpClientEngine> { Android.create() }
}

// Convenience function for Android
fun initKoinAndroid(context: Context): KoinApplication {
return initKoin {
androidContext(context)
androidLogger()
}
}

iosMain/kotlin/di/PlatformModule.ios.kt

package com.myapp.di

import org.koin.dsl.module

actual val platformModule = module {
single<DatabaseDriverFactory> { IosDatabaseDriverFactory() }
single<HttpClientEngine> { Darwin.create() }
}

// Export for Swift
fun initKoinIos() {
initKoin()
}

Compose Multiplatform

Shared ViewModel

// commonMain
@KoinViewModel
class UserViewModel(
private val getUserUseCase: GetUserUseCase
) : ViewModel() {
private val _state = MutableStateFlow<UserState>(UserState.Loading)
val state = _state.asStateFlow()

fun loadUser(id: String) {
viewModelScope.launch {
_state.value = UserState.Success(getUserUseCase(id))
}
}
}

Shared UI

// commonMain
@Composable
fun UserScreen(
viewModel: UserViewModel = koinViewModel()
) {
val state by viewModel.state.collectAsState()

when (val s = state) {
is UserState.Loading -> LoadingIndicator()
is UserState.Success -> UserContent(s.user)
is UserState.Error -> ErrorMessage(s.message)
}
}

Testing

Common Test Module

// commonTest
val testModule = module {
single<ApiClient> { MockApiClient() }
single<UserRepository> { MockUserRepository() }
factory<GetUserUseCase>()
}

class GetUserUseCaseTest : KoinTest {
@BeforeTest
fun setup() {
startKoin { modules(testModule) }
}

@AfterTest
fun teardown() {
stopKoin()
}

@Test
fun testGetUser() = runTest {
val useCase: GetUserUseCase = get()
val user = useCase("123")
assertEquals("Test User", user.name)
}
}

Best Practices

  1. Single init function - One initKoin() in commonMain
  2. Platform extensions via config - Use includes(config) pattern
  3. Minimize platform modules - Only truly platform-specific code
  4. Share ViewModel logic - Business logic in commonMain
  5. Use expect/actual for factories - Platform-specific instance creation
  6. Test in commonTest - Most tests can be shared

Next Steps