--- id: mobile-kmp-deep title: Kotlin Multiplatform — shared business logic category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [mobile, kmp, kotlin, vibe-coding] tech_stack: { language: "Kotlin", applicable_to: ["iOS", "Android"] } applied_in: [] aliases: [KMP, Kotlin Multiplatform, KMM, Compose Multiplatform, expect/actual, shared module] --- # Kotlin Multiplatform Deep > Kotlin 가 iOS + Android + Web + Desktop 동시. **Shared business logic + native UI**. KMM 가 mobile, Compose Multiplatform 가 UI 도. JetBrains official. ## 📖 핵심 개념 - Common code: Kotlin (shared). - Platform-specific: expect / actual. - iOS = Kotlin → Native (LLVM). - Android = Kotlin → JVM (default). ## 💻 코드 패턴 ### Project structure ``` my-app/ ├── shared/ # Shared module │ ├── commonMain/ # Common Kotlin │ ├── androidMain/ # Android-only │ ├── iosMain/ # iOS-only │ └── jsMain/ # Web (optional) ├── androidApp/ # Android app ├── iosApp/ # iOS Xcode project └── build.gradle.kts ``` ### shared/build.gradle.kts ```kotlin kotlin { androidTarget() iosX64() iosArm64() iosSimulatorArm64() cocoapods { summary = "Shared module" homepage = "..." ios.deploymentTarget = "16.0" framework { baseName = "shared" isStatic = true } } sourceSets { commonMain.dependencies { implementation("io.ktor:ktor-client-core:2.3.7") implementation("io.ktor:ktor-client-content-negotiation:2.3.7") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") } androidMain.dependencies { implementation("io.ktor:ktor-client-okhttp:2.3.7") } iosMain.dependencies { implementation("io.ktor:ktor-client-darwin:2.3.7") } } } ``` ### Shared model ```kotlin // commonMain/User.kt @Serializable data class User( val id: String, val name: String, val email: String, ) ``` ### Shared API client ```kotlin // commonMain/UserRepository.kt class UserRepository(private val client: HttpClient) { suspend fun fetchUser(id: String): User { return client.get("https://api.example.com/users/$id").body() } } ``` ### expect / actual ```kotlin // commonMain/Platform.kt expect fun platform(): String // androidMain/Platform.kt actual fun platform(): String = "Android ${android.os.Build.VERSION.SDK_INT}" // iosMain/Platform.kt import platform.UIKit.UIDevice actual fun platform(): String = "iOS ${UIDevice.currentDevice.systemVersion}" ``` → Common 가 abstract, platform 가 concrete. ### Network (Ktor) ```kotlin // commonMain val client = HttpClient { install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) } install(Logging) { level = LogLevel.HEADERS } } suspend fun fetchUsers(): List { return client.get("https://...").body() } ``` → HTTP / JSON / serialization 가 common. ### DB (SQLDelight) ```kotlin // shared/build.gradle.kts plugins { id("app.cash.sqldelight") version "2.0.0" } sqldelight { databases { create("AppDb") { packageName.set("com.example.shared.db") } } } ``` ```sql -- shared/src/commonMain/sqldelight/com/example/User.sq CREATE TABLE User ( id TEXT PRIMARY KEY, name TEXT NOT NULL ); selectAll: SELECT * FROM User; insert: INSERT OR REPLACE INTO User VALUES (?, ?); ``` ```kotlin // commonMain val driver: SqlDriver = ... // platform 별 val db = AppDb(driver) db.userQueries.insert("1", "Alice") val users = db.userQueries.selectAll().executeAsList() ``` → Type-safe SQL. iOS / Android 둘 다. ### Coroutines + Flow ```kotlin // commonMain class UserViewModel(private val repo: UserRepository) { val users: Flow> = flow { emit(repo.fetchUsers()) } } ``` → Flow 가 iOS 에서 도 사용 (KMP-NativeCoroutines). ### iOS 사용 (Swift) ```swift // iosApp import shared class UserListVM: ObservableObject { @Published var users: [User] = [] private let repo = UserRepository(client: ...) func load() async { do { let result = try await repo.fetchUsers() await MainActor.run { self.users = result } } catch { print(error) } } } ``` → Kotlin class 가 Swift 에서 native 처럼. ### Async on iOS (KMP-NativeCoroutines) ```kotlin // commonMain @NativeCoroutines suspend fun fetchUsers(): List = ... ``` ```swift // iOS import KMPNativeCoroutinesAsync let users = try await asyncFunction(for: repo.fetchUsersNative()) ``` → Suspend → Swift async/await. ### Compose Multiplatform (UI도) ```kotlin // commonMain (Compose UI) @Composable fun UserCard(user: User) { Card { Column(Modifier.padding(16.dp)) { Text(user.name, style = MaterialTheme.typography.h6) Text(user.email) } } } ``` → iOS + Android 가 같은 UI. → iOS Compose 가 stable (2024+). ### vs SwiftUI / Jetpack Compose only ``` KMP shared logic + native UI: - Native 친화 (각 platform UI) - 작은 cost. Compose Multiplatform: - Single UI codebase - iOS 도 Compose - "iOS 답지" 의 약간 → Logic 만 = KMP. UI 도 = Compose Multiplatform. ``` ### vs Flutter / React Native ``` Flutter / RN: - Single 언어 (Dart / JS) - Single UI codebase - Native bridge KMP: - Native 언어 (Kotlin + Swift) - Native UI (보통) - Logic 만 share → KMP 가 native-friendly. RN/Flutter 가 빠른 dev. ``` ### Pros ``` - Native iOS UX. - Logic share = bug 1번 fix. - Type-safe (Kotlin). - Coroutines (async). - Native interop. ``` ### Cons ``` - iOS dev 가 Kotlin 이해. - Library 가 작음 (vs RN). - Build time (LLVM iOS = 느린). - Tooling 가 RN/Flutter 보다 약함. ``` ### Real-world - **Cash App** (Square): 큰 KMP user. - **Netflix**: 일부 mobile. - **Philips**: hue lighting. - **VMware**: Workspace ONE. - **Touchlab**: KMM consultancy. ### LiveData / StateFlow → Swift ```kotlin // commonMain class ViewModel { val state: StateFlow = ... } ``` ```swift // iOS ViewModel().state.collect { state in // ... } ``` → StateFlow 가 Combine publisher 처럼. ### Image loading (Coil 3 multiplatform) ```kotlin // Compose Multiplatform AsyncImage( model = "https://...", contentDescription = null, ) ``` ### Settings (Multiplatform) ```kotlin // com.russhwolf:multiplatform-settings val settings: Settings = ... settings.putString("theme", "dark") val theme = settings.getString("theme", "light") ``` ### Test ```kotlin // commonTest class UserRepoTest { @Test fun fetchesUser() = runTest { val user = repo.fetchUser("1") assertEquals("Alice", user.name) } } ``` → Common test 가 모든 platform. ### Migration to KMP ``` 1. Existing Android app. 2. shared module 추가. 3. 1 logic (e.g. API client) 이동. 4. iOS 가 framework 사용. 5. 점진 — 1 module 씩. ``` → Strangler fig 식. ### Build ```bash # Gradle (Android) ./gradlew :androidApp:installDebug # iOS cd iosApp && pod install xed iosApp.xcworkspace # Xcode build & run. # Release framework ./gradlew :shared:linkReleaseFrameworkIosArm64 ``` ### Future ``` - Compose Multiplatform iOS 가 stable + 인기 ↑. - KMP 의 toolchain 빠름. - More libraries multiplatform. - "Kotlin everywhere". ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | Native UX 중요 + share logic | KMP (Kotlin shared, Swift/Kotlin UI) | | Single UI codebase | Compose Multiplatform | | 빠른 prototype | RN / Flutter | | 큰 회사 + Android 강 | KMP | | iOS dev 가 Swift 만 | KMP shared logic only | | Web 도 share | KMP / RN Web | | Performance critical | Native or KMP | ## ❌ 안티패턴 - **모든 거 share (UI 까지)**: native UX 잃음. - **Platform-specific 가 common**: leak. - **Coroutines + Swift 직접**: KMP-NativeCoroutines 사용. - **iOS framework 가 큰**: bundle 폭발 — minimize. - **Only 1 platform test**: 다른 platform 깨짐. - **Big bang migration**: gradual. ## 🤖 LLM 활용 힌트 - expect / actual 가 KMP 의 핵심. - Ktor / SQLDelight / kotlinx 가 multiplatform-ready. - Compose Multiplatform = UI 까지 share. - KMP-NativeCoroutines 가 Swift async/await. ## 🔗 관련 문서 - [[Mobile_KMP_Compose]] - [[Mobile_Flutter_Patterns]] - [[Android_Kotlin_Coroutines_Scopes]]