iT邦幫忙

2026 iThome 鐵人賽

DAY 2
0
自我挑戰組

愛安豬系列 第 2

# meow mew

  • 分享至 

  • xImage
  •  

Day 2:建立 KMP Reader Project

今天目標:commonMain 裡的 App() 同時出現在 Android emulator 和 iOS simulator 上。
沒用 Ktor、沒用有 Room、沒用 Repository。
先讓 toolchain 跑過。

Day 1 作業

使用 Day 1 prompt,AI 給的答案大致分三類:說對的、說得太籠統的、以及版本過時的。

對的:ContextSharedPreferencesLogjava.io.File 在 commonMain 全部不存在。

籠統的:它說「用 expect/actual 解決」,但沒講什麼時候該用 expect/actual、什麼時候該用 interface。

過時的:它推薦的 library 版本和 Gradle 寫法,跟今天 wizard 產出來的專案已經不一樣。
Lesson:問 AI 「怎麼設定專案」是最容易被誤導的問題,因為 KMP 的 Gradle DSL 每半年就變一次。 專案骨架要用官方工具產生,AI 只負責解釋產出來的東西。AI 常常搞錯.

1. 用 wizard 產生專案

KMP 官方提供 web wizard(kmp.jetbrains.com),Android Studio / IntelliJ 裝 Kotlin Multiplatform plugin 後也有同樣的 New Project 精靈。

選項:

  • Project name:KMPReader
  • Targets:Android、iOS
  • iOS UI:Share UI(Compose Multiplatform),不是 SwiftUI
  • 不勾 Desktop、Web、Server

手刻 build.gradle.kts 在 KMP 很麻煩。Kotlin plugin、Compose compiler plugin、AGP 三者的版本組合有相容矩陣,wizard 產出的組合是 JetBrains 測過的;自己配很容易踩到「某個 alpha 的 CMP 不支援某版 AGP」這種跟學習無關的坑。

寫這篇時 wizard 給我的版本是 Kotlin 2.4.x、Compose Multiplatform 1.11.x、AGP 9.x。你看到這篇時大概又不一樣了,這正是我不把版本號寫進正文的原因——請以 gradle/libs.versions.toml 為準。

2. 產出來的東西長什麼樣

KMPReader/
├── composeApp/
│   ├── build.gradle.kts
│   └── src/
│       ├── commonMain/kotlin/   ← App() 在這裡
│       ├── androidMain/kotlin/  ← MainActivity
│       └── iosMain/kotlin/      ← MainViewController
├── iosApp/                       ← Xcode project,Swift 只有幾行
├── gradle/libs.versions.toml
├── build.gradle.kts
└── settings.gradle.kts

第一次看到有幾個地方跟 Android 專案直覺不同:

只有一個 module 叫 composeApp,沒有 app
它同時是 Android application 和 iOS framework 的來源。
iOS 那邊是 iosApp 這個 Xcode project 去 link 這個 module 編出來的 framework。

androidMain 裡的 MainActivity 只做一件事:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { App() }
    }
}

iosMain 裡的對應物是一個 function,不是 class:

fun MainViewController() = ComposeUIViewController { App() }

Swift 端 iosApp/iosApp/ContentView.swift 把它包成 UIViewControllerRepresentable。整個 iOS app 的 Swift code 加起來不到 30 行。

App() 本身在 commonMain

@Composable
fun App() {
    MaterialTheme {
        // wizard 給的 demo UI
    }
}

寫 Android 的人看到這個結構第一個反應:所以 Activity 只是個 host?
對。這是 KMP 專案的基本姿勢
——平台 entry point 薄到只剩一行,所有東西往 commonMain 推。

3. Android 先跑

composeApp configuration,emulator 跑起來,看到 wizard 的 demo 畫面。

值得注意的是 composeApp/build.gradle.ktsandroid { } block 和以前的 app module 幾乎一樣,namespace、compileSdk、defaultConfig 都在。KMP 沒有換掉 Android 的 build 流程,只是把它變成眾多 target 之一。

4. iOS

我的 Mac 之前沒有做 iOS 開發,所以這段花的時間比 Android 多很多。步驟:

  1. 裝 Xcode(App Store,很大,先開始下載)。
  2. 開一次 Xcode 接受 license、裝 iOS simulator runtime。
  3. kdoctorbrew install kdoctor),它會檢查 Xcode、CocoaPods、JDK、Kotlin plugin 是否就位。先跑這個再開 IDE,可以省掉一半的莫名錯誤。
  4. 回 Android Studio,run configuration 選 iosApp,target 選一台 simulator。

第一次 iOS build 會很慢,因為 Kotlin/Native 要把 commonMain 編成 native framework,還要下載 Kotlin/Native toolchain。我這裡等了幾分鐘。之後 incremental build 會快很多。

跑起來之後,同一個 demo 畫面出現在 iPhone simulator 上。Android 那邊的 Material 元件、字型、ripple,在 iOS 上長得一模一樣——這既是 CMP 的賣點,也是之後 Phase 4 要處理的問題:iOS 使用者不一定想看到 Material。

5. 問 AI

專案已經跑起來了,開始問 AI 。我把 composeApp/build.gradle.kts 整段貼給它:

This is the build.gradle.kts generated by the KMP wizard for an Android + iOS
Compose Multiplatform app. 
Explain it block by block. In particular:
- why are there three iOS targets (iosX64, iosArm64, iosSimulatorArm64)?
- what does `binaries.framework { isStatic = true }` do and when would I set it false?
- which dependencies are in commonMain vs androidMain, and why?
Do not suggest changes yet.

三個 iOS target 的解釋它講得不錯:x64 是 Intel Mac simulator、simulatorArm64 是 Apple Silicon simulator、arm64 是實機。static framework 的部分它說得對但不完整,我回頭查了官方文件補上 dynamic framework 在 CocoaPods 整合時的差異。

這就是昨天訂的規則:AI 解釋、我對照官方文件、compiler 決定誰是對的。

6. Commit

git add .
git commit -m "feat: KMP Reader skeleton from wizard, runs on Android + iOS (AI: none)"

Day 2 完成條件

  • [x] Wizard 產生 Android + iOS + CMP 專案
  • [x] Android emulator 跑起來
  • [x] Xcode / kdoctor / simulator 就位
  • [x] iOS simulator 跑起來,畫面與 Android 一致
  • [x] 讀懂 composeApp/build.gradle.kts 的每個 block
  • [x] Commit

明天

Day 3 要把今天「看懂」的東西拆開來講:KMP 的 source set 是怎麼組織的、commonMain / androidMain / iosMain 之間的依賴方向、為什麼 iosMain 可以同時服務三個 iOS target。這是後面所有「這個東西屬於誰」判斷的基礎。


上一篇
#meow
下一篇
# meow meow
系列文
愛安豬4
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言