Files
2nd/10_Wiki/Topics/Coding/Mobile_KMP_Deep.md
T
2026-05-10 22:08:15 +09:00

399 lines
8.4 KiB
Markdown

---
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<User> {
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<List<User>> = 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<User> = ...
```
```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<UserState> = ...
}
```
```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]]