可惜了,不過,繼續
Android 的 build.gradle.kts 大部分時間只改 dependencies { },其他 block 少有動過。
KMP 不一樣。它的 build 檔同時描述多個 compile target,依賴的宣告位置直接決定 code 能不能看見那個 library。放錯 source set,錯誤訊息不會說「你放錯地方」,只會說 Unresolved reference,跟 Day 3 的 probe 一模一樣。所以今天的目的是:以後看到 unresolved reference 時,第一個懷疑的是依賴位置,不是拼字。
kotlin {
androidTarget { ... }
iosX64()
iosArm64()
iosSimulatorArm64()
sourceSets {
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.lifecycle.runtime.compose)
}
androidMain.dependencies {
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
}
// iosMain.dependencies 目前是空的
}
}
三個 點:
androidx.lifecycle.viewmodel 在 commonMain。 這不是筆誤。Jetpack 的 lifecycle、viewmodel、navigation 這幾個 library 從 2024 開始發 KMP artifact,可以在 commonMain 用。Day 1 說「Android 那套 stack 大部分能搬」指的就是這件事。
activity.compose 在 androidMain。 因為 ComponentActivity 是 Android 的東西,iOS 沒有 Activity。
compose.runtime 用的是 compose. 前綴,不是 libs.。 這是 Compose Multiplatform Gradle plugin 提供的 accessor,它會自動對到跟 plugin 版本相容的 artifact,省掉自己對版本。Android 那邊的 androidx.compose.* 不用另外宣告,CMP plugin 在 Android target 會自動換成 Google 發的 artifact。
跟 Day 3 的可見性表是同一件事,只是換成 library:
| library 的性質 | 放在 | 例子 |
|---|---|---|
| 有發 KMP artifact,邏輯要共用 | commonMain.dependencies |
Ktor client core、kotlinx.serialization、Room KMP、Koin |
| 只有 JVM/Android 版 | androidMain.dependencies |
OkHttp、Ktor 的 OkHttp engine、Activity Compose |
| 只有 iOS 版 | iosMain.dependencies |
Ktor 的 Darwin engine |
| 兩邊實作不同但介面相同 | common 放介面 artifact,各平台放 engine | Ktor:core 在 common,engine 分平台 |
Ktor 是最典型的例子,它的 ktor-client-core 是 multiplatform,但底層 HTTP engine 每個平台不同:Android 用 OkHttp,iOS 用 Darwin(NSURLSession)。這正是 Day 3 講的 dependsOn 結構在 library 層的反映。
這是我今天最想解決的問題,因為 AI 在這件事上特別不可靠——它會很有信心地說某個 library「支援 KMP」,但那可能是三個版本前的狀態,或者根本是幻覺。
三個可靠的方法:
看 Maven Central 的 artifact 列表。 KMP library 會為每個 target 發一個 artifact。以 Ktor 為例,搜 io.ktor:ktor-client-core,會看到:
ktor-client-core ← Gradle metadata 的入口
ktor-client-core-jvm
ktor-client-core-iosarm64
ktor-client-core-iossimulatorarm64
ktor-client-core-iosx64
ktor-client-core-js
...
有 -iosarm64 就代表它能進 commonMain 並且在 iOS 上編。沒有的話,就只能放 androidMain。
看 klibs.io。 JetBrains 做的 KMP library 索引,可以直接篩選 target。比 GitHub README 可信,因為它是從實際發布的 artifact 掃出來的。
讓 Gradle 告訴你。 把 library 放進 commonMain,sync。如果它沒有 iOS artifact,錯誤訊息會直接說:
Could not resolve io.some:library:1.0.
No matching variant of io.some:library:1.0 was found.
The consumer was configured to find ... 'org.jetbrains.kotlin.platform.type' with value 'native' ...
看到 platform.type 和 native 這兩個字,就是「這個 library 沒有 iOS 版」。
gradle/libs.versions.toml 跟 Android 一樣,但我做了一個分組習慣,讓日後一眼看出哪些是 common、哪些是平台專屬:
[versions]
kotlin = "2.4.x"
compose-multiplatform = "1.11.x"
agp = "9.x"
ktor = "3.x"
# 以你 sync 時 wizard 給的為準
[libraries]
# --- common (KMP) ---
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
# --- android only ---
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
# --- ios only ---
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
用註解分三區。不是 Gradle 要求,是給三週後的自己看的。
另外,Kotlin plugin 和 Compose compiler plugin 的版本必須一致——這不是 catalog 裡兩行寫一樣就好,而是要用同一個 version.ref:
[plugins]
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
wizard 已經幫你這樣寫了,升級時不要手動改成兩個不同的數字。
今天的實驗是測 AI 的「幻覺率」。我列了五個 Android developer 常用的 library,問它哪些支援 KMP:
For each of these libraries, tell me whether the current version publishes
Kotlin Multiplatform artifacts for iOS (iosArm64), and if not, what the
KMP-compatible alternative is:
1. Retrofit
2. Moshi
3. Room
4. Coil
5. Timber
它的答案對照 Maven Central 之後:
| library | AI 說 | 實際 |
|---|---|---|
| Retrofit | 不支援,改用 Ktor | ✅ 正確 |
| Moshi | 不支援,改用 kotlinx.serialization | ✅ 正確 |
| Room | 支援(2.7+) | ✅ 正確 |
| Coil | 支援(3.x) | ✅ 正確 |
| Timber | 不支援,改用 Napier 或 Kermit | ✅ 正確,但沒提 Kermit 和 Napier 的維護狀態差異 |
這幾個 library 的 KMP 狀態在 2024–2025 就穩定了。AI 在「已經穩定一年以上的事實」上可信度高,在「最近半年變動的東西」上要查。 這條放進 Phase 5。
git commit -am "chore: reorganize version catalog by common/android/ios (AI: none)"
四天下來寫的 Kotlin code 是零行,但拿到了三個以後每天都會用的工具:
-iosarm64 才能進 commonMain。再加上一個對 AI 的校準:專案設定和最新版本的問題要查,穩定超過一年的事實可以信,「官方推薦」四個字要特別警覺。
Phase 2 開始。Day 5 正面處理 Day 3 留下的問題:expect/actual 和 interface 到底什麼時候用哪個。 會用 KMP Reader 真正需要的第一個平台能力——log——當例子,兩種寫法都做一次,然後看哪一種在寫 test 時比較不痛苦。