9148c358d0
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를 Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류. - 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로 자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거, 동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거. - 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming, Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business, Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로, 나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는 title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백). 원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지. - 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서. - 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는 지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지. - Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경. - 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
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.