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

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
mobile
kmp
kotlin
vibe-coding
language applicable_to
Kotlin
iOS
Android
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 {
    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.

🔗 관련 문서