Skip to main content
Version: 4.2

KMP Sharing Patterns

This page covers how to declare definitions that differ per platform in a Kotlin Multiplatform project: a platform module with expect/actual, an expect/actual class, and an interface with one implementation per platform. It assumes your project is set up as in KMP Setup. For the Kotlin rules of expect/actual, see the Kotlin documentation.

Organizing shared modules​

Put everything that doesn't depend on a platform in commonMain modules, split by layer or by feature, and assemble them with includes():

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

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

val appModule = module {
includes(dataModule, domainModule, platformModule())
}

Module composition is described in Modules. The rest of this page is about platformModule(): the part that differs per platform.

Platform module​

Declare the module in commonMain with expect, and write one actual per platform. Each platform module declares its own classes:

// commonMain
interface PlatformComponent {
fun getInfo(): String
}

expect fun platformModule(): Module
// androidMain
class AndroidComponent(private val context: Context) : PlatformComponent {
override fun getInfo() = "Android ${context.packageName}"
}

actual fun platformModule(): Module = module {
single<AndroidComponent>() bind PlatformComponent::class
}
// iosMain
class IOSComponent : PlatformComponent {
override fun getInfo() = "iOS"
}

actual fun platformModule(): Module = module {
single<IOSComponent>() bind PlatformComponent::class
}

Use this pattern when platform implementations need different constructor parameters. Common code depends on the PlatformComponent interface only, and each platform is free to inject what it needs (here, Context on Android). On Android, Context resolves once androidContext() is set at start (see KMP Setup).

Platform libraries​

Platform drivers and engines (SQLDelight, Ktor client engines, settings) are built with a function call. Declare them in the platform module with a lambda:

// androidMain
actual fun platformModule(): Module = module {
single<SqlDriver> { AndroidSqliteDriver(AppDatabase.Schema, androidContext(), "app.db") }
single<HttpClientEngine> { OkHttp.create() }
}

// iosMain
actual fun platformModule(): Module = module {
single<SqlDriver> { NativeSqliteDriver(AppDatabase.Schema, "app.db") }
single<HttpClientEngine> { Darwin.create() }
}

The common module then builds on them without knowing the platform: single { AppDatabase(get()) }, single { HttpClient(get<HttpClientEngine>()) }. For other ways to declare classes you don't own, see Definitions.

expect/actual class​

When the class has the same constructor on every platform, declare it once in common code and let each platform implement it:

// commonMain
expect class NativeComponent() {
fun getInfo(): String
}

val nativeComponentModule = module {
single<NativeComponent>()
}
// androidMain
actual class NativeComponent actual constructor() {
actual fun getInfo() = "Android NativeComponent"
}

// iosMain
actual class NativeComponent actual constructor() {
actual fun getInfo() = "iOS NativeComponent"
}

An expect class has one constructor contract for all platforms. If a platform needs another dependency (an Android Context, for example), you can't add it to that platform only. Use the platform module pattern with an interface instead, or a platform wrapper (see KMP Platform Integration).

Components on one platform only​

A platform module can declare classes that exist on one platform and nowhere else. The other platforms keep an empty module:

// androidMain
actual fun platformModule(): Module = module {
single<NotificationHelper>()
}

// iosMain
actual fun platformModule(): Module = module { }

Only Android code can then use NotificationHelper. Common code can't reference it, since the class does not exist in commonMain.

Choosing a pattern​

SituationPattern
Same constructor on every platformexpect/actual class
Different constructor parameters per platformInterface in common code, implementation in the platform module
Platform library built with a function (driver, engine)Lambda in the platform module
Class that exists on one platform onlyPlatform module, empty on other platforms
Android Context needed by shared codePlatform wrapper, see KMP Platform Integration

Limits​

  • Manual wiring: if a provider function receives a Koin Scope and calls scope.get() itself, that call is dynamic and not covered by compile-time safety. Prefer constructor parameters.
  • Tests: shared modules can be tested in commonTest. See Testing.
  • KMP Setup: dependencies and the shared initKoin function.
  • KMP Platform Integration: Android context in shared code, Swift interop, desktop and web.
  • Modules: includes(), @Module, @ComponentScan, @Configuration.
  • Definitions: binding, provider functions, classes you don't own.
  • ViewModel: ViewModels declared in commonMain.