# App KMP đầu tiên trên Android và iOS

> Tạo một project Compose Multiplatform tối giản, chạy nó trên máy thật, và đọc được ba source set commonMain / androidMain / iosMain.

- URL: https://dantech.academy/courses/kotlin-mobile-101/00-setup-first-run/lesson-02-first-app-on-device
- Khóa học: [Kotlin Mobile 101](https://dantech.academy/courses/kotlin-mobile-101.md)
- Module: Setup & App Đầu Tiên
- Thời lượng: 13 phút
- Độ khó: beginner
- Truy cập: Miễn phí, đọc toàn bài

## Mục tiêu

- Tạo một project Compose Multiplatform tối giản và chạy nó trên máy thật
- Đọc được ba source set commonMain, androidMain, iosMain và biết code của mình thuộc chỗ nào

---

Máy đã sẵn sàng. Giờ đến phần đáng đợi: một màn hình do bạn viết, hiện lên trên cả điện thoại Android lẫn iPhone, từ **cùng một file Kotlin**.

Bài này dựng một project tối giản của riêng bạn. Nó nhỏ và chỉ có một nhiệm vụ: chứng minh máy bạn build được KMP thật.

## Tạo Project Bằng KMP Wizard

Đừng tạo project bằng template "Empty Activity" của Android Studio: đó là template Android thuần. Dùng wizard chính thức của Kotlin Multiplatform:

1. Mở [kmp.jetbrains.com](https://kmp.jetbrains.com/).
2. **Project name**: `HelloKmp`. **Project ID**: `academy.dantech.hellokmp`.
3. Tick **Android** và **iOS**. Với iOS chọn **Share UI** (dùng Compose Multiplatform cho cả hai, đó là trọng tâm của khóa).
4. Bỏ tick Desktop và Web cho lần đầu. Thêm sau lúc nào cũng được.
5. **Download** rồi giải nén, mở thư mục đó bằng Android Studio.

Lần mở đầu tiên Gradle sync mất 5-15 phút vì phải tải toàn bộ dependency. Thanh tiến trình chạy ở góc dưới bên phải. Đừng bấm gì thêm cho đến khi nó xong.

> **Vì sao dùng wizard thay vì tự tạo?** Wizard sinh ra một bộ phiên bản Kotlin, Gradle, Android Gradle Plugin và Compose Multiplatform đã được kiểm tra hợp nhau. Tự ghép tay là cách nhanh nhất để gặp lỗi "không tương thích" mà người mới không đủ dữ kiện để sửa.

## Ba Source Set: Chỗ Nào Là Chỗ Nào

Nhìn quen cấu trúc này thì mọi project KMP về sau đều dễ đọc.

Wizard tạo ba thư mục chính: `shared/` là **module chia sẻ** chứa toàn bộ code Kotlin của bạn, `androidApp/` là vỏ app Android, và `iosApp/` là project Xcode. Trong `shared/`, ba thư mục sau là thứ cần nhớ:

```
shared/src/
├── commonMain/kotlin/   ← code dùng chung, 95% khóa này nằm ở đây
├── androidMain/kotlin/  ← code chỉ Android mới có
└── iosMain/kotlin/      ← code chỉ iOS mới có
```

| Source set    | Chứa gì                                                            | Thấy được gì                               |
| ------------- | ------------------------------------------------------------------ | ------------------------------------------ |
| `commonMain`  | Model, ViewModel, repository, và **toàn bộ giao diện Compose**     | Chỉ Kotlin và thư viện multiplatform       |
| `androidMain` | Những thứ chỉ Android có: `Context`, `Activity`, quyền hệ thống    | Toàn bộ Android SDK, cộng với `commonMain` |
| `iosMain`     | Những thứ chỉ iOS có: đường dẫn thư mục app, API của UIKit khi cần | Toàn bộ API iOS, cộng với `commonMain`     |

Quy tắc mặc định: **viết vào `commonMain`**. Chỉ xuống `androidMain` hoặc `iosMain` khi trình biên dịch nói bạn phải làm vậy. Chuyện đó hiếm, và mỗi lần xảy ra thì trình biên dịch nói rõ vì sao.

`iosApp/` là project Xcode thật, viết bằng Swift, nhưng bạn gần như không đụng vào. Nó chỉ làm một việc: mở màn hình Compose của bạn ra. Xem như cái vỏ.

## Chạy Trên Android

App Android nằm ở module `androidApp`, không nằm ở `shared`. File zip của wizard không kèm run configuration nào: trong thư mục `.idea/` chỉ có `receipt.json`, không có thư mục `.run/`. Android Studio tự tạo run configuration khi mở project. Trên thanh công cụ trên cùng, chọn run configuration chạy module `androidApp`, chọn thiết bị của bạn, nhấn nút Run (tam giác xanh).

Muốn build không qua IDE thì dùng lệnh mà README của project ghi sẵn:

```bash
./gradlew :androidApp:assembleDebug
```

Build đầu tiên chậm, 2-5 phút. App hiện ra với một nút bấm và một tấm ảnh Compose. Bấm thử để chắc chắn nó phản hồi.

## Chạy Trên iOS

Chỉ làm được trên macOS. Mở thư mục `iosApp` bằng Xcode, chọn một simulator (ví dụ `iPhone 16`), nhấn Run. README của project wizard hướng dẫn đúng cách này.

Build iOS lần đầu chậm hơn Android khá nhiều, có thể 5-10 phút, vì Kotlin phải biên dịch sang native framework cho kiến trúc của simulator. Lần sau nhanh hơn nhiều.

Khi simulator hiện đúng màn hình bạn vừa thấy trên Android, dừng lại một nhịp và nhìn kỹ. **Hai nền tảng, một file Kotlin.**

## Sửa Thử Một Dòng

Mở `shared/src/commonMain/kotlin/.../App.kt`. Tìm dòng `Text(...)` bất kỳ (ví dụ `Text("Click me!")` trên nút bấm), đổi nội dung thành tên bạn:

```kotlin
Text("Xin chào, tôi đang học KMP!")
```

Chạy lại trên Android. Chữ đổi. Chạy lại trên iOS. Chữ cũng đổi. Bạn vừa sửa một chỗ và giao diện của hai nền tảng cùng cập nhật.

> **Live Edit**: trên Android, Android Studio cập nhật composable ngay trên máy đang chạy khi bạn sửa thân hàm, không cần build lại. Bật ở File > Settings > Editor > Live Edit (trên macOS là Android Studio > Settings > Editor > Live Edit). Live Edit cần emulator hoặc máy thật chạy API 30 (Android 11) trở lên. Compose Hot Reload, tính năng tương tự của Compose Multiplatform, chỉ chạy cho app desktop (JVM), mà HelloKmp không có target desktop. Trên iOS bạn bấm Run lại như bình thường.

## libs.versions.toml: Nơi Khai Báo Thư Viện

Mở `gradle/libs.versions.toml`. Đây là **version catalog**, một file duy nhất khai báo mọi thư viện và phiên bản của project. Các file `build.gradle.kts` chỉ tham chiếu tới nó bằng tên.

Cấu trúc gồm ba mục. Ví dụ dưới đây thêm Koin, một thư viện wizard không cài sẵn:

```toml
[versions]
kotlin = "2.4.20"
koin = "4.2.2"

[libraries]
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }

[plugins]
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
```

Rồi trong file `shared/build.gradle.kts`:

```kotlin
commonMain.dependencies {
    implementation(libs.koin.core)   // dấu gạch ngang trong toml thành dấu chấm ở đây
}
```

Lợi ích thực tế: cần nâng phiên bản thì sửa **một dòng** trong `[versions]`, không phải đi lùng khắp các file build.

### Bộ phiên bản của khóa này

App mà khóa này dựng là **TicTac**, một game cờ ca-rô chạy trên cả hai nền tảng. Đây là bộ phiên bản nó dùng:

| Thư viện                      | Phiên bản     |
| ----------------------------- | ------------- |
| Kotlin                        | 2.4.10        |
| Android Gradle Plugin         | 9.0.1         |
| Compose Multiplatform         | 1.11.1        |
| lifecycle-viewmodel           | 2.10.0        |
| Material 3                    | 1.9.0         |
| Koin                          | 4.2.2         |
| kotlinx-serialization         | 1.11.0        |
| multiplatform-settings        | 1.3.0         |
| kotlinx-collections-immutable | 0.5.2         |

Tất cả đều là bản stable, không có bản `alpha` hay `beta` nào. Có một chỗ trông lạ: lifecycle dừng ở `2.10.0` dù `2.11.0` đã ra. Lý do là `2.11.0` đòi Android Gradle Plugin 9.1 và compileSdk 37, trong khi project này dùng AGP 9.0.1 và compileSdk 36. Các phiên bản trong một project phải khớp nhau như một bộ, nên bản mới nhất chưa chắc là bản đúng. Pin đúng bộ trên là cách chắc chắn nhất để code bạn gõ khớp với code bạn đọc.

Project HelloKmp bạn vừa tạo có bộ phiên bản riêng do wizard chọn, và nó mới hơn bộ của TicTac. Lúc viết bài, wizard sinh Kotlin `2.4.20`, AGP `9.1.1`, `compileSdk = 37` và lifecycle `2.11.0`, đúng cặp AGP 9.1 với compileSdk 37 mà lifecycle `2.11.0` đòi. Mỗi project chỉ cần các phiên bản trong bộ của chính nó khớp nhau.

Nếu bạn gặp lỗi lạ sau khi nâng version, hạ về đúng bộ trên là cách nhanh nhất để tách bạch "code sai" khỏi "version không hợp".

`commonMain` là nơi bạn sống. `androidMain` và `iosMain` chỉ để giải quyết những gì thật sự khác nhau giữa hai nền tảng.

## Checklist thực hành

- [ ] Project `HelloKmp` sync xong, không có lỗi đỏ
- [ ] App chạy được trên Android (máy thật hoặc emulator)
- [ ] Nếu có macOS: app chạy được trên iOS simulator
- [ ] Bạn sửa một chuỗi `Text(...)` trong `commonMain`, chạy lại, thấy chữ đổi trên máy
- [ ] Bạn mở được `gradle/libs.versions.toml` và chỉ đúng dòng khai báo phiên bản Kotlin

---

- Bài trước: [Android Studio, JDK và KMP plugin](https://dantech.academy/courses/kotlin-mobile-101/00-setup-first-run/lesson-01-install-android-studio.md)
- Bài tiếp theo: [Clone TicTac, Git checkpoint và rules file cho AI](https://dantech.academy/courses/kotlin-mobile-101/00-setup-first-run/lesson-03-git-checkpoints-ai-pair.md)
