Cấu trúc dự án KMP
Hiểu cấu trúc dự án là nền tảng để làm việc hiệu quả với Kotlin Multiplatform. Bài này giải thích chi tiết từng phần.
Tổng quan cấu trúc
MyKmpProject/
├── build.gradle.kts # Root build config
├── settings.gradle.kts # Định nghĩa modules
├── gradle.properties # Gradle settings
├── local.properties # Local config (SDK paths)
│
├── composeApp/ # UI Module (Compose Multiplatform)
│ ├── build.gradle.kts
│ └── src/
│ ├── commonMain/ # UI code chung
│ ├── androidMain/ # UI riêng Android
│ ├── iosMain/ # UI riêng iOS
│ └── desktopMain/ # UI riêng Desktop (optional)
│
├── shared/ # Shared Logic Module
│ ├── build.gradle.kts
│ └── src/
│ ├── commonMain/ # Business logic chung
│ ├── commonTest/ # Tests chung
│ ├── androidMain/ # Logic riêng Android
│ ├── androidUnitTest/ # Android tests
│ ├── iosMain/ # Logic riêng iOS
│ └── iosTest/ # iOS tests
│
└── iosApp/ # iOS Application
├── iosApp/
│ └── ContentView.swift
├── iosApp.xcodeproj
└── PodfileSource Sets - Khái niệm quan trọng nhất
Source Set là tập hợp code được compile cho một hoặc nhiều platforms.
Các Source Sets phổ biến
commonMain
│
┌──────────────┼──────────────┐
│ │ │
androidMain iosMain desktopMain
│ │
│ ┌──────┴──────┐
│ │ │
│ iosArm64 iosX64
│ (iPhone) (Simulator)| Source Set | Compile cho | Ví dụ code |
|---|---|---|
commonMain | Tất cả platforms | Data classes, interfaces |
androidMain | Android | Context, SharedPreferences |
iosMain | iOS (Arm64 + X64) | UIKit, CoreData |
iosArm64Main | iPhone thật | Device-specific code |
iosX64Main | iOS Simulator (Intel Mac) | Simulator-specific |
iosSimulatorArm64Main | iOS Simulator (M1/M2 Mac) | Simulator-specific |
File build.gradle.kts cho shared module
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary)
}
kotlin {
// Target platforms
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "11"
}
}
}
// iOS targets
listOf(
iosX64(), // Intel Mac simulator
iosArm64(), // Real iPhone
iosSimulatorArm64() // M1/M2 Mac simulator
).forEach {
it.binaries.framework {
baseName = "shared"
isStatic = true
}
}
// Dependencies cho từng source set
sourceSets {
commonMain.dependencies {
// Dependencies cho TẤT CẢ platforms
implementation(libs.kotlinx.coroutines.core)
implementation(libs.ktor.client.core)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
androidMain.dependencies {
// Dependencies CHỈ cho Android
implementation(libs.ktor.client.okhttp)
}
iosMain.dependencies {
// Dependencies CHỈ cho iOS
implementation(libs.ktor.client.darwin)
}
}
}
android {
namespace = "com.example.shared"
compileSdk = 34
defaultConfig {
minSdk = 24
}
}Giải thích:
androidTarget(): Compile cho AndroidiosX64(),iosArm64(),iosSimulatorArm64(): Compile cho iOSsourceSets: Định nghĩa dependencies cho từng source setbinaries.framework: Tạo iOS framework để dùng trong Swift
Tổ chức code trong shared module
Pattern khuyến nghị
shared/src/commonMain/kotlin/
├── data/
│ ├── model/ # Data classes
│ │ └── User.kt
│ ├── repository/ # Repository interfaces
│ │ └── UserRepository.kt
│ └── source/
│ ├── local/ # Local data sources
│ └── remote/ # API clients
│
├── domain/
│ ├── model/ # Domain models
│ └── usecase/ # Use cases
│
└── platform/
└── Platform.kt # expect declarationsVí dụ: User data class (chung cho tất cả platforms)
// shared/src/commonMain/kotlin/data/model/User.kt
@Serializable
data class User(
val id: Long,
val name: String,
val email: String
)Ví dụ: Repository interface (chung)
// shared/src/commonMain/kotlin/data/repository/UserRepository.kt
interface UserRepository {
suspend fun getUsers(): List<User>
suspend fun getUserById(id: Long): User?
}expect/actual - Kết nối các platforms
Khi cần code khác nhau cho từng platform, dùng expect và actual:
Bước 1: Declare với expect (commonMain)
// shared/src/commonMain/kotlin/platform/Platform.kt
// expect = "Tôi mong đợi có một implementation"
expect class Platform() {
val name: String
}
expect fun getPlatformName(): StringBước 2: Implement với actual (androidMain)
// shared/src/androidMain/kotlin/platform/Platform.android.kt
actual class Platform actual constructor() {
actual val name: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}
actual fun getPlatformName(): String = "Android"Bước 3: Implement với actual (iosMain)
// shared/src/iosMain/kotlin/platform/Platform.ios.kt
import platform.UIKit.UIDevice
actual class Platform actual constructor() {
actual val name: String = UIDevice.currentDevice.let {
"${it.systemName()} ${it.systemVersion}"
}
}
actual fun getPlatformName(): String = "iOS"Sử dụng
// Ở bất kỳ đâu trong commonMain
fun greet(): String {
val platform = Platform()
return "Hello from ${platform.name}!"
}
// → Android: "Hello from Android 34!"
// → iOS: "Hello from iOS 17.0!"Module composeApp
Module này chứa UI code dùng Compose Multiplatform:
composeApp/src/
├── commonMain/kotlin/
│ ├── App.kt # Root composable
│ ├── navigation/ # Navigation setup
│ ├── ui/
│ │ ├── screen/ # Các màn hình
│ │ ├── component/ # UI components
│ │ └── theme/ # Theme, colors
│ └── di/ # Dependency injection
│
├── androidMain/kotlin/
│ └── MainActivity.kt # Android entry point
│
└── iosMain/kotlin/
└── MainViewController.kt # iOS entry pointiOS App structure
iosApp/
├── iosApp/
│ ├── iOSApp.swift # App entry point
│ └── ContentView.swift # SwiftUI wrapper
├── iosApp.xcodeproj # Xcode project
└── Podfile # CocoaPods configContentView.swift - Dùng Compose trong SwiftUI
import SwiftUI
import ComposeApp
struct ContentView: View {
var body: some View {
ComposeView()
.ignoresSafeArea(.all)
}
}
struct ComposeView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
MainViewControllerKt.MainViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}📝 Tóm tắt
| Khái niệm | Giải thích |
|---|---|
| Source Set | Tập code cho một hoặc nhiều platforms |
| commonMain | Code chung cho tất cả |
| androidMain/iosMain | Code riêng từng platform |
| expect | Khai báo cần implementation |
| actual | Implementation cho từng platform |
| shared module | Chứa business logic |
| composeApp module | Chứa UI code |
Tiếp theo
Tìm hiểu sâu hơn về Expect/Actual Pattern.
Last updated on