--- id: mobile-kmp-compose title: Kotlin Multiplatform / Compose Multiplatform category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [mobile, kotlin, kmp, compose, vibe-coding] tech_stack: { language: "Kotlin", applicable_to: ["iOS", "Android", "Desktop", "Web"] } applied_in: [] aliases: [KMP, Kotlin Multiplatform, Compose Multiplatform, expect/actual, KMM] --- # Kotlin Multiplatform (KMP) + Compose Multiplatform > Android / iOS / Desktop / Web 같은 비즈니스 로직 (KMP). UI 도 공유 가능 (Compose Multiplatform). **Native UI 가 strict 면 KMP 만, fast share 면 둘 다**. ## 📖 핵심 개념 - KMP: 비즈니스 로직 (data, network, repository) 공유. - Compose Multiplatform: UI 도 같은 코드. - expect/actual: platform-specific. - iOS = framework / Cocoapods 으로 import. ## 💻 코드 패턴 ### 폴더 (gradle) ``` shared/ src/ commonMain/ # 모든 platform androidMain/ # Android only iosMain/ # iOS only desktopMain/ # JVM desktop jsMain/ # Web androidApp/ iosApp/ desktopApp/ ``` ### shared module (build.gradle.kts) ```kotlin plugins { kotlin("multiplatform") id("com.android.library") } kotlin { androidTarget() iosX64() iosArm64() iosSimulatorArm64() jvm("desktop") cocoapods { version = "1.0" summary = "Shared module" homepage = "..." ios.deploymentTarget = "17.0" framework { baseName = "Shared" isStatic = true } } sourceSets { commonMain.dependencies { implementation("io.ktor:ktor-client-core:2.3.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0") } androidMain.dependencies { implementation("io.ktor:ktor-client-okhttp:2.3.0") } iosMain.dependencies { implementation("io.ktor:ktor-client-darwin:2.3.0") } } } ``` ### Common code ```kotlin // shared/commonMain/Repository.kt class UserRepository(private val client: HttpClient) { suspend fun fetchUser(id: String): User { return client.get("$BASE/users/$id").body() } } @Serializable data class User(val id: String, val email: String, val name: String) ``` ### expect/actual (platform 별 구현) ```kotlin // commonMain expect class Platform() { val name: String } // androidMain actual class Platform actual constructor() { actual val name: String = "Android ${android.os.Build.VERSION.SDK_INT}" } // iosMain import platform.UIKit.UIDevice actual class Platform actual constructor() { actual val name: String = "iOS ${UIDevice.currentDevice.systemVersion}" } ``` ### Android 사용 ```kotlin // androidApp val repo = UserRepository(httpClient) val user = lifecycleScope.launch { repo.fetchUser("u1") } ``` ### iOS 사용 — Swift ```swift import Shared let repo = UserRepository(client: ...) Task { let user = try await repo.fetchUser(id: "u1") } // Kotlin suspend → Swift async/await 자동 (KMP 1.9+) ``` ### Compose Multiplatform (UI 공유) ```kotlin // shared/commonMain/App.kt import androidx.compose.runtime.* import androidx.compose.material3.* @Composable fun App(repo: UserRepository) { var user by remember { mutableStateOf(null) } LaunchedEffect(Unit) { user = repo.fetchUser("u1") } Scaffold { padding -> Text(user?.name ?? "Loading", modifier = Modifier.padding(padding)) } } ``` ```swift // iosApp/iOSApp.swift import SwiftUI import Shared struct ContentView: View { var body: some View { ComposeView() .ignoresSafeArea(.all, edges: .bottom) } } struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { return Main_iosKt.MainViewController() } func updateUIViewController(_ vc: UIViewController, context: Context) {} } ``` ### Network — Ktor Client ```kotlin val client = HttpClient { install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) } defaultRequest { url(BASE_URL) header("Authorization", "Bearer $token") } } ``` ### Database — SQLDelight ```kotlin // shared/commonMain/sqldelight/com/acme/AppDatabase.sq CREATE TABLE User ( id TEXT PRIMARY KEY NOT NULL, email TEXT NOT NULL ); selectAll: SELECT * FROM User; insert: INSERT OR REPLACE INTO User VALUES (?, ?); ``` ```kotlin val db = AppDatabase(driver) db.userQueries.insert("u1", "a@b.com") val users = db.userQueries.selectAll().executeAsList() ``` ### State management ```kotlin class UserViewModel(private val repo: UserRepository) { private val _state = MutableStateFlow(UiState.Loading) val state: StateFlow = _state suspend fun load(id: String) { _state.value = UiState.Loading try { _state.value = UiState.Success(repo.fetchUser(id)) } catch (e: Exception) { _state.value = UiState.Error(e.message ?: "") } } } sealed class UiState { object Loading : UiState() data class Success(val user: User) : UiState() data class Error(val msg: String) : UiState() } ``` ### iOS 호출 — coroutines 변환 ``` // KMP 1.9+ 자동 async/await. // 또는 NativeCoroutines 라이브러리 — Combine / async stream. ``` ### Test (commonTest) ```kotlin class UserRepositoryTest { @Test fun fetchUser() = runTest { val mock = MockEngine { ... } val repo = UserRepository(HttpClient(mock)) val user = repo.fetchUser("u1") assertEquals("u1", user.id) } } ``` → JVM 에서 실행 (commonTest), iOS / Android target 에 자동 적용. ### Trade-offs ``` KMP 비즈니스 로직만: + Native UI 강력 / 잘 쓰면 빠름 + iOS / Android 가 Swift / Kotlin 그대로 - UI 코드 두 번 Compose Multiplatform: + UI 도 한 코드 - iOS UI 가 Material — Apple Human Interface 와 차이 - 일부 native 기능 어려움 (camera, notification) ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | Logic 만 공유 | KMP only | | Logic + UI 같이 | Compose Multiplatform | | Native UX 강 critical | KMP only | | Internal tool / B2B | Compose MP | | Consumer app | KMP + native UI | | Cross-platform alternative | React Native / Flutter | ## ❌ 안티패턴 - **Compose iOS = Material design 그대로**: HIG 위반. 별 styling. - **Common code 안 platform 의존**: 빌드 깨짐. expect/actual. - **iOS 의 Combine / SwiftUI 통합 무시**: NativeCoroutines. - **Cocoapods + SPM 혼합**: 한 가지 선택. - **빌드 시간 무시**: KMP 가 처음 빌드 길음. - **Native API + KMP 의존**: 양쪽 의존. Either / Or. - **Web target 가정 prod ready**: Compose Web 은 아직 alpha. ## 🤖 LLM 활용 힌트 - 비즈니스 로직 = KMP, UI = native (대부분 case). - Ktor + SQLDelight + kotlinx-serialization 3종 표준. - expect/actual = platform-specific. ## 🔗 관련 문서 - [[Android_Compose_State_Hoisting]] - [[iOS_SwiftUI_State_Property_Wrappers]] - [[React_Native_Bridge_Performance]]