Kotlin Multiplatform KMP in Practice: 5 Core Patterns for Building Cross-Platform Apps with Compose

编程语言

Kotlin Multiplatform: One Codebase, All Platforms

iOS uses Swift, Android uses Kotlin, Web uses TypeScript — cross-platform development costs remain high. Kotlin Multiplatform (KMP) lets you share business logic with the same Kotlin code, and share UI with Compose Multiplatform, writing once to run on Android, iOS, Desktop, and Web. In 2026, KMP has become one of the optimal solutions for cross-platform development.

This article covers 5 core patterns, guiding you through the full-chain implementation of expect/actual mechanism → shared business logic → Compose UI → platform APIs → production builds.


Core Concepts

Concept Description
KMP Kotlin Multiplatform Platform, Kotlin's cross-platform solution
expect/actual KMP platform difference declaration and implementation mechanism
Compose Multiplatform Cross-platform UI framework based on Jetpack Compose
Shared Logic Business logic code shared across platforms
Platform-Specific Implementation code targeting specific platforms
Kotlin/Native Kotlin backend compiling to native binaries
Kotlin/JS Kotlin backend compiling to JavaScript
Interop Interaction with platform native code (Swift/JS/C)

Problem Analysis: 5 Major KMP Challenges

  1. expect/actual learning curve: The declaration-implementation separation pattern requires adaptation
  2. Compose Multiplatform maturity: iOS is still in Beta stage
  3. Platform difference handling: Significant API differences between iOS/Android/Desktop
  4. Poor debugging experience: Complex cross-platform debugging chain
  5. Complex build configuration: Steep learning curve for Gradle multiplatform configuration

Step-by-Step: 5 KMP Implementation Patterns

Pattern 1: KMP Project Structure and expect/actual

// commonMain/src/commonMain/kotlin/Platform.kt
expect class Platform() {
    val name: String
}

fun getPlatform(): Platform {
    return Platform()
}
// androidMain/src/androidMain/kotlin/Platform.kt
actual class Platform actual constructor() {
    actual val name: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}
// iosMain/src/iosMain/kotlin/Platform.kt
actual class Platform actual constructor() {
    actual val name: String = "iOS ${platform.Foundation.NSProcessInfo.processInfo.operatingSystemVersionString}"
}

Pattern 2: Shared Business Logic Layer

// commonMain
class GreetingRepository {
    private val platform = getPlatform()

    fun greet(): String {
        return "Hello from ${platform.name}!"
    }
}

class UserViewModel {
    private val repository = GreetingRepository()
    private val _greeting = MutableStateFlow("")
    val greeting: StateFlow<String> = _greeting.asStateFlow()

    fun loadGreeting() {
        _greeting.value = repository.greet()
    }
}

Pattern 3: Compose Multiplatform UI

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun App() {
    var greeting by remember { mutableStateOf("Loading...") }

    MaterialTheme {
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colorScheme.background
        ) {
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Text(
                        text = greeting,
                        style = MaterialTheme.typography.headlineMedium
                    )
                    Spacer(modifier = Modifier.height(16.dp))
                    Button(onClick = { greeting = GreetingRepository().greet() }) {
                        Text("Load Greeting")
                    }
                }
            }
        }
    }
}

Pattern 4: Platform-Specific API Calls

// commonMain
expect class DateTimeFormatter() {
    fun format(timestamp: Long): String
}

// androidMain
actual class DateTimeFormatter actual constructor() {
    private val sdf = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
    actual fun format(timestamp: Long): String = sdf.format(Date(timestamp))
}

// iosMain
actual class DateTimeFormatter actual constructor() {
    actual fun format(timestamp: Long): String {
        val date = NSDate(timeIntervalSince1970 = timestamp / 1000.0)
        val formatter = NSDateFormatter().apply {
            dateFormat = "yyyy-MM-dd HH:mm:ss"
        }
        return formatter.stringFromDate(date)
    }
}

Pattern 5: Production Build and Release

// build.gradle.kts
kotlin {
    androidTarget()
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    jvm("desktop")

    js(IR) {
        browser()
    }

    sourceSets {
        commonMain.dependencies {
            implementation(compose.runtime)
            implementation(compose.foundation)
            implementation(compose.material3)
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
            implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
        }
        androidMain.dependencies {
            implementation("androidx.activity:activity-compose:1.9.0")
        }
    }
}

Pitfall Guide

Pitfall 1: Referencing platform-specific APIs in commonMain

// ❌ Wrong: directly using Android API in commonMain
fun getPath() = android.os.Environment.getDataDirectory().path

// ✅ Correct: declare through expect/actual
// commonMain
expect fun getDataPath(): String
// androidMain
actual fun getDataPath() = android.os.Environment.getDataDirectory().path

Pitfall 2: Compose iOS memory leaks

// ❌ Wrong: not releasing iOS native references
class IOSBridge {
    val delegate = object : NSURLSessionDelegate() { ... }
}

// ✅ Correct: use WeakReference or manual cleanup
class IOSBridge {
    private var delegateRef: WeakReference<NSURLSessionDelegate>? = null
    fun cleanup() { delegateRef = null }
}

Pitfall 3: Kotlin/Native concurrency restrictions

// ❌ Wrong: sharing mutable state across threads
var counter = 0 // crashes when accessed from different threads

// ✅ Correct: use AtomicReference
val counter = AtomicReference(0)

Pitfall 4: Overly complex Gradle configuration

// ❌ Wrong: all configuration in one build.gradle.kts
// 300+ line Gradle file

// ✅ Correct: split using convention plugins
// build-logic/convention/src/main/kotlin/KmpConventionPlugin.kt
class KmpConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        target.kotlin { /* standard KMP configuration */ }
    }
}

Pitfall 5: Not handling platform font differences

// ❌ Wrong: using platform-specific font names
Text(fontFamily = FontFamily("SF Pro"))

// ✅ Correct: use Compose built-in fonts or conditional logic
val fontFamily = if (Platform.isIOS) FontFamily("SF Pro") else FontFamily("Roboto")

Error Troubleshooting

# Error Cause Solution
1 Unresolved reference: expect KMP plugin not enabled Add kotlin("multiplatform") plugin
2 Actual class has no corresponding expect expect/actual mismatch Check commonMain and platform Main declarations
3 Linker error iOS Kotlin/Native C interop issue Check cinterop configuration and .def files
4 Compose compiler error Compose version incompatibility Unify compose plugin and compiler versions
5 Gradle sync failed Multiplatform dependency conflict Use version catalog for unified versions
6 JS compilation error Kotlin/JS doesn't support reflection Avoid reflection APIs in commonMain
7 iOS build timeout Slow Kotlin/Native compilation Enable incremental compilation and caching
8 ClassNotFoundException desktop JVM module misconfigured Check jvm() target and mainClass
9 Module not found sourceSets path error Verify src/commonMain/kotlin directory structure
10 SDK not found iOS SDK not installed Install Xcode and Command Line Tools

Comparison

Dimension KMP Flutter React Native .NET MAUI
Language Kotlin Dart JavaScript/TS C#
UI Sharing Compose Widget JSX XAML
Native Performance ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐
Ecosystem Richness ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐
Learning Curve ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐
Native Interop ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
iOS Maturity ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐

Summary: Kotlin Multiplatform offers unique flexibility in cross-platform development through its expect/actual mechanism and Compose Multiplatform. KMP suits teams already in the Kotlin/Android ecosystem, enabling incremental code sharing rather than full rewrites. With Compose Multiplatform iOS maturing in 2026, KMP is becoming the pragmatic choice for cross-platform development.


Online Tools

Try these browser-local tools — no sign-up required →

#Kotlin多平台#KMP#跨平台开发#Compose Multiplatform#2026#编程语言