Claude
Skills
Sign in
Back

kotlin-multiplatform

Included with Lifetime
$97 forever

Kotlin Multiplatform (KMP) — share Kotlin code across Android, iOS, JVM Desktop, and JS/Wasm. Covers Gradle setup, source set hierarchy, expect/actual, target configuration, kotlinx-serialization, ktor-client, SQLDelight, multiplatform resources, and iOS framework export. USE WHEN: user mentions "Kotlin Multiplatform", "KMP", "expect/actual", "shared module", "cross-platform Kotlin", "iOSMain", "commonMain", "Kotlin/Native", "kotlinx-serialization", "SQLDelight" DO NOT USE FOR: Compose UI cross-platform - use `frontend-frameworks/compose-multiplatform` DO NOT USE FOR: Pure Kotlin language features - use `languages/kotlin` DO NOT USE FOR: Jetpack Compose Android-only - use `mobile/jetpack-compose` DO NOT USE FOR: Rust ↔ KMP bindings - use `languages/uniffi`

Design

What this skill does

# Kotlin Multiplatform

> **References**: [gradle.md](quick-ref/gradle.md) for full Gradle config, source set hierarchy, target setup. [ios-integration.md](quick-ref/ios-integration.md) for iOS framework export, CocoaPods, SwiftPM, Xcode integration. [libraries.md](quick-ref/libraries.md) for ktor, kotlinx-serialization, SQLDelight, Koin patterns in KMP.
>
> **Deep Knowledge**: Use `mcp__documentation__fetch_docs` with technology: `kotlin-multiplatform`.

## What KMP Solves

Share business logic (networking, persistence, domain, view models) across platforms. Each platform retains native UI:

```
shared/                                        # commonMain — Kotlin code shared everywhere
├── domain                                     # Models, use cases
├── data                                       # Repositories, DTOs, mappers
├── network                                    # Ktor client
└── persistence                                # SQLDelight queries

apps/
├── android (Kotlin + Jetpack Compose)         # uses shared
├── ios (Swift + SwiftUI)                      # uses shared via XCFramework
└── desktop (Kotlin + Compose Desktop)         # uses shared
```

KMP is **not** "write once run anywhere" — UI stays native (or use Compose Multiplatform for shared UI).

## Module Structure

```
shared/
├── build.gradle.kts
└── src/
    ├── commonMain/kotlin/                     # platform-agnostic code
    │   └── com/example/Wallet.kt
    ├── commonTest/kotlin/                     # shared tests
    │
    ├── androidMain/kotlin/                    # Android-specific
    │   └── com/example/AndroidPlatform.kt
    ├── androidUnitTest/kotlin/
    │
    ├── iosMain/kotlin/                        # iOS-specific (all iOS targets)
    │   └── com/example/IosPlatform.kt
    ├── iosTest/kotlin/
    │
    ├── desktopMain/kotlin/                    # JVM Desktop
    └── jsMain/kotlin/                         # Browser/Node (optional)
```

## expect / actual

The cross-platform mechanism. `expect` declares an API in `commonMain`; each target provides `actual` implementation.

### Functions

```kotlin
// commonMain
expect fun platformName(): String
expect fun openUrl(url: String)
```

```kotlin
// androidMain
import android.content.Intent
import android.net.Uri

actual fun platformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}"

actual fun openUrl(url: String) {
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    appContext.startActivity(intent)
}
```

```kotlin
// iosMain
import platform.UIKit.UIApplication
import platform.Foundation.NSURL

actual fun platformName(): String = "iOS ${UIDevice.currentDevice.systemVersion}"

actual fun openUrl(url: String) {
    NSURL.URLWithString(url)?.let {
        UIApplication.sharedApplication.openURL(it)
    }
}
```

### Classes

```kotlin
// commonMain
expect class SecureStorage {
    fun put(key: String, value: ByteArray)
    fun get(key: String): ByteArray?
    fun delete(key: String)
}
```

```kotlin
// androidMain — Android Keystore-backed
actual class SecureStorage(private val context: Context) {
    actual fun put(key: String, value: ByteArray) { /* AndroidX EncryptedSharedPreferences */ }
    actual fun get(key: String): ByteArray? { /* ... */ }
    actual fun delete(key: String) { /* ... */ }
}
```

```kotlin
// iosMain — Keychain-backed
import platform.Security.*

actual class SecureStorage {
    actual fun put(key: String, value: ByteArray) { /* SecItemAdd */ }
    actual fun get(key: String): ByteArray? { /* SecItemCopyMatching */ }
    actual fun delete(key: String) { /* SecItemDelete */ }
}
```

### Type Aliases (lightweight expect)

For simple wrappers around platform types:

```kotlin
// commonMain
expect class UUID

// androidMain
actual typealias UUID = java.util.UUID

// iosMain
actual typealias UUID = platform.Foundation.NSUUID
```

## Source Set Hierarchy (Default)

KMP 1.9+ uses a default hierarchy template:

```
commonMain
├── androidMain
├── jvmMain        (desktop)
├── jsMain
├── nativeMain
│   ├── linuxMain
│   ├── mingwMain  (Windows)
│   ├── appleMain
│   │   ├── iosMain
│   │   │   ├── iosX64Main
│   │   │   ├── iosArm64Main
│   │   │   └── iosSimulatorArm64Main
│   │   ├── macosMain
│   │   ├── tvosMain
│   │   └── watchosMain
```

Code in `appleMain` is shared across all Apple targets. Code in `iosMain` only across iOS targets. Useful for Apple-wide APIs (Keychain, NSURLSession) vs iOS-specific (UIKit).

```kotlin
// Custom intermediate source set (rare)
kotlin {
    sourceSets {
        val mobileMain by creating {
            dependsOn(getByName("commonMain"))
        }
        getByName("androidMain").dependsOn(mobileMain)
        getByName("iosMain").dependsOn(mobileMain)
    }
}
```

## Minimal Gradle Setup

```kotlin
// shared/build.gradle.kts
plugins {
    kotlin("multiplatform") version "2.2.0"
    id("com.android.library") version "8.7.0"
    kotlin("plugin.serialization") version "2.2.0"
}

kotlin {
    androidTarget {
        compilations.all {
            kotlinOptions { jvmTarget = "17" }
        }
    }

    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach { target ->
        target.binaries.framework {
            baseName = "Shared"
            isStatic = true
        }
    }

    jvm("desktop")

    sourceSets {
        commonMain.dependencies {
            implementation("io.ktor:ktor-client-core:3.0.0")
            implementation("io.ktor:ktor-client-content-negotiation:3.0.0")
            implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.0")
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0")
            implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
            implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
        }
        androidMain.dependencies {
            implementation("io.ktor:ktor-client-okhttp:3.0.0")
        }
        iosMain.dependencies {
            implementation("io.ktor:ktor-client-darwin:3.0.0")
        }
        commonTest.dependencies {
            implementation(kotlin("test"))
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.0")
        }
    }
}

android {
    namespace = "com.example.shared"
    compileSdk = 35
    defaultConfig {
        minSdk = 26
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
}
```

See [gradle.md](quick-ref/gradle.md) for advanced setup (custom intermediate source sets, version catalogs, Compose KMP, multiplatform resources).

## iOS Framework Export

```kotlin
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach { target ->
    target.binaries.framework {
        baseName = "Shared"
        isStatic = true     // recommended — smaller bundle, faster link
        embedBitcode("disable")
    }
}
```

Build:

```bash
./gradlew :shared:linkReleaseFrameworkIosArm64
./gradlew :shared:linkReleaseFrameworkIosSimulatorArm64
```

To create a single XCFramework consumable by Xcode:

```kotlin
val xcf = XCFramework("Shared")
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach {
    it.binaries.framework {
        baseName = "Shared"
        xcf.add(this)
    }
}

// Task: ./gradlew :shared:assembleSharedXCFramework
```

For SwiftPM consumption, see [ios-integration.md](quick-ref/ios-integration.md).

## Networking — Ktor Client

```kotlin
// commonMain
import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json

@Serializable
data class User(val id: Long, val name: String)

class UserApi(private val client: HttpClient) {
    suspend fun getUser(id: Long): User =
        client.get("https://api.example.com/users/$id").body()
}

// httpClient construction with platform engine
expect fun createHttpClient(): HttpClient

//

Related in Design