Kotlin Multiplatform (KMP) allows you to share code between Android, iOS, Web, and Desktop. It emphasizes sharing logic (Business, Data, Networking) while allowing for native or shared (Compose Multiplatform) UIs.
When to Use
- Cross-Platform Shared Logic: Sharing core business logic, data models, networking, and validation across Android and iOS apps.
- Native UI Independence: Retaining 100% native UI performance (SwiftUI on iOS, Jetpack Compose on Android) while sharing domain code.
- Compose Multiplatform: Building shared user interfaces across Android, iOS, Desktop (JVM), and Web using Kotlin Compose.
- SDK & Library Development: Authoring multiplatform mobile libraries published to Maven Central and CocoaPods/Swift Package Manager.
Quick Start
// commonMain/kotlin/Platform.kt
interface Platform {
val name: String
}
expect fun getPlatform(): Platform
// androidMain/kotlin/Platform.kt actual fun getPlatform(): Platform = object : Platform { override val name = "Android ${android.os.Build.VERSION.SDK_INT}" }
// iosMain/kotlin/Platform.kt import platform.UIKit.UIDevice actual fun getPlatform(): Platform = object : Platform { override val name = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion }
// commonMain/kotlin/Greeting.kt class Greeting { fun greet(): String = "Hello, ${getPlatform().name}!" }
## Core Concepts
### Source Sets Hierarchy (commonMain vs Platform Sets)
Shared code lives in `commonMain`; platform-specific source sets bridge native OS features:
```text
shared/src/
├── commonMain/kotlin/ # Shared business logic, Ktor, SQLDelight
├── androidMain/kotlin/ # Android-specific APIs & Context
└── iosMain/kotlin/ # iOS Objective-C / Swift interop
expect / actual Platform Declarations
Defines a common contract that must be implemented by each target platform:
// commonMain: Contract declaration
expect class PlatformSecureStorage() {
fun store(key: String, value: String)
fun retrieve(key: String): String?
}
// iosMain: Actual implementation using iOS Keychain
actual class PlatformSecureStorage actual constructor() {
actual fun store(key: String, value: String) { /* iOS SecItemAdd */ }
actual fun retrieve(key: String): String? { /* iOS SecItemCopyMatching */ return null }
}
Shared Persistence with SQLDelight / Room KMP
Compiles SQL queries into type-safe Kotlin data classes shared across platforms:
// commonMain: Shared Database driver setup
class DatabaseDriverFactory(private val driver: SqlDriver) {
fun createDatabase(): AppDatabase = AppDatabase(driver)
}
// Android uses AndroidSqliteDriver; iOS uses NativeSqliteDriver
Common Patterns
Shared Ktor Client Across iOS and Android
Problem: Duplicating HTTP request logic and deserialization schemas across platforms.
Solution: Define shared Ktor HttpClient in commonMain.
// commonMain/src/ApiClient.kt
class ApiClient(engine: HttpClientEngine) {
private val client = HttpClient(engine) {
install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
}
suspend fun getItems(): List<Item> = client.get("https://api.example.com/items").body()
}
// androidMain: ApiClient(OkHttp.create())
// iosMain: ApiClient(Darwin.create())
Best Practices
Do:
- Export Swift-Friendly Frameworks: Configure
shared.podspecor Swift Package export with transitive dependencies enabled. - Use SKIE for Coroutines & Flow Interop: Generate native Swift
async/awaitand@Observablewrappers for Kotlin coroutines. - Keep UI Frameworks Decoupled: Share domain and network logic in
commonMainwhile letting iOS teams use native SwiftUI idioms. - Automate Multiplatform CI/CD: Run Gradle builds on macOS runners to validate both Android and iOS targets in CI pipelines.
Don't:
- Leak Android
ContextintocommonMain: Keep domain logic pure and dependency-injected. - Expose raw Kotlin coroutine Job types to Swift: Wrap shared flows with SKIE or custom cancellation tokens.
- Ignore iOS memory management (ARC): Be mindful of reference cycles when sharing objects across the Kotlin/Native boundary.
Troubleshooting
| Error | Cause | Solution |
|---|---|---|
Unresolved reference in commonMain |
Using platform-specific API (java.util, android.*). | Use KMP-compatible libraries (kotlinx-datetime). |
C-interop build errors |
iOS build configuration or missing headers. | Check cocoapods or framework config in build.gradle.kts. |
Memory Leaks on iOS |
Circular references or freezing (mostly solved in new memory model). | Ensure newer Kotlin version (1.9+) is used. |