8.4 KiB
8.4 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| mobile-kmp-deep | Kotlin Multiplatform — shared business logic | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
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 {
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
// commonMain/User.kt
@Serializable
data class User(
val id: String,
val name: String,
val email: String,
)
Shared API client
// 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
// 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)
// 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)
// shared/build.gradle.kts
plugins {
id("app.cash.sqldelight") version "2.0.0"
}
sqldelight {
databases {
create("AppDb") {
packageName.set("com.example.shared.db")
}
}
}
-- 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 (?, ?);
// 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
// commonMain
class UserViewModel(private val repo: UserRepository) {
val users: Flow<List<User>> = flow {
emit(repo.fetchUsers())
}
}
→ Flow 가 iOS 에서 도 사용 (KMP-NativeCoroutines).
iOS 사용 (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)
// commonMain
@NativeCoroutines
suspend fun fetchUsers(): List<User> = ...
// iOS
import KMPNativeCoroutinesAsync
let users = try await asyncFunction(for: repo.fetchUsersNative())
→ Suspend → Swift async/await.
Compose Multiplatform (UI도)
// 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
// commonMain
class ViewModel {
val state: StateFlow<UserState> = ...
}
// iOS
ViewModel().state.collect { state in
// ...
}
→ StateFlow 가 Combine publisher 처럼.
Image loading (Coil 3 multiplatform)
// Compose Multiplatform
AsyncImage(
model = "https://...",
contentDescription = null,
)
Settings (Multiplatform)
// com.russhwolf:multiplatform-settings
val settings: Settings = ...
settings.putString("theme", "dark")
val theme = settings.getString("theme", "light")
Test
// 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
# 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.