Files
2nd/10_Wiki/Topics/Coding/Mobile_KMP_Compose.md
T
2026-05-09 21:08:02 +09:00

7.1 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-compose Kotlin Multiplatform / Compose Multiplatform Coding draft B conceptual 2026-05-09 2026-05-09
mobile
kotlin
kmp
compose
vibe-coding
language applicable_to
Kotlin
iOS
Android
Desktop
Web
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)

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

// 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 별 구현)

// 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 사용

// androidApp
val repo = UserRepository(httpClient)
val user = lifecycleScope.launch { repo.fetchUser("u1") }

iOS 사용 — 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 공유)

// shared/commonMain/App.kt
import androidx.compose.runtime.*
import androidx.compose.material3.*

@Composable
fun App(repo: UserRepository) {
    var user by remember { mutableStateOf<User?>(null) }
    LaunchedEffect(Unit) { user = repo.fetchUser("u1") }
    
    Scaffold { padding ->
        Text(user?.name ?? "Loading", modifier = Modifier.padding(padding))
    }
}
// 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

val client = HttpClient {
    install(ContentNegotiation) {
        json(Json { ignoreUnknownKeys = true })
    }
    defaultRequest {
        url(BASE_URL)
        header("Authorization", "Bearer $token")
    }
}

Database — SQLDelight

// 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 (?, ?);
val db = AppDatabase(driver)
db.userQueries.insert("u1", "a@b.com")
val users = db.userQueries.selectAll().executeAsList()

State management

class UserViewModel(private val repo: UserRepository) {
    private val _state = MutableStateFlow<UiState>(UiState.Loading)
    val state: StateFlow<UiState> = _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)

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.

🔗 관련 문서