Kotlin Multiplatform năm 2026: Chia sẻ mã Android và iOS
Cách Kotlin Multiplatform chia sẻ logic nghiệp vụ giữa Android và iOS năm 2026, với cấu hình Gradle, expect/actual, mạng Ktor, giao diện Compose Multiplatform và các câu hỏi phỏng vấn KMP.

Kotlin Multiplatform (KMP) cho phép một mã nguồn Kotlin duy nhất vận hành logic nghiệp vụ cho cả ứng dụng Android lẫn iOS, trong khi mỗi nền tảng vẫn giữ giao diện native, hiệu năng và quyền truy cập đầy đủ vào SDK của mình. Đến năm 2026, KMP không còn là một thử nghiệm: công nghệ này đã ổn định, được Google hậu thuẫn chính thức và đang chạy trong môi trường sản xuất tại Netflix, McDonald's, Forbes và Cash App. Bài phân tích chuyên sâu này trình bày cách việc chia sẻ mã thực sự hoạt động, cách cấu hình một module dùng chung, và những đánh đổi đáng nắm rõ trước một buổi phỏng vấn KMP.
Kotlin Multiplatform chia sẻ logic đã được biên dịch, không phải một runtime. Kotlin common được biên dịch thành bytecode JVM cho Android và thành mã nhị phân native (thông qua LLVM) cho iOS. Không có cầu nối JavaScript, không có webview nhúng, và không có lớp reflection, nên mã dùng chung chạy ở tốc độ native trên cả hai nền tảng.
Cách Kotlin Multiplatform Chia Sẻ Mã Mà Không Hy Sinh Hiệu Năng Native
Cơ chế cốt lõi là một hệ phân cấp các source set. commonMain chứa Kotlin không phụ thuộc nền tảng: mạng, serialization, quy tắc nghiệp vụ và view model. Các source set nền tảng như androidMain và iosMain chứa mã chạm tới API nền tảng. Trình biên dịch tạo ra một artifact JVM từ commonMain cộng androidMain cho Android, và một framework native từ commonMain cộng iosMain cho iOS.
Kotlin Multiplatform năm 2026 ưu tiên chia sẻ những gì thực sự hưởng lợi từ một nguồn chân lý duy nhất: model, client API, caching và validation. Giao diện có thể được chia sẻ bằng Compose Multiplatform hoặc để hoàn toàn native với SwiftUI, theo từng màn hình. Mô hình "chia sẻ logic, chọn giao diện" này là lý do các nhóm áp dụng KMP mà không viết lại ứng dụng hiện có.
Kotlin Multiplatform là một công nghệ Kotlin biên dịch một module dùng chung sang nhiều target (Android trên JVM, iOS ở dạng native, cộng thêm desktop và web) để logic nghiệp vụ được viết một lần trong khi mỗi nền tảng vẫn giữ giao diện native và quyền truy cập SDK đầy đủ. Google hiện hỗ trợ nó như một lựa chọn hạng nhất, với các thư viện Jetpack như Room, DataStore, ViewModel và Paging cung cấp artifact KMP.
Quyết định thứ gì thuộc về module dùng chung là lựa chọn kiến trúc quan trọng nhất trong một dự án KMP. Bảng dưới đây phản ánh cách hầu hết các nhóm sản xuất vạch ranh giới vào năm 2026: mọi thứ nằm dưới lớp trình bày đều là ứng viên mạnh để chia sẻ, còn bất cứ thứ gì chạm tới quy ước UX của nền tảng thường được giữ native.
| Lớp | Chia sẻ trong commonMain? | Lý do | |-------|---------------------|--------| | Model dữ liệu và DTO | Có | Hợp đồng API giống hệt trên cả hai nền tảng | | Mạng và caching | Có | Một ngăn xếp HTTP, một bề mặt lỗi | | Quy tắc nghiệp vụ và validation | Có | Miền nghiệp vụ không khác nhau theo OS | | View model và state | Thường | Compose và SwiftUI đều dùng state chung | | Kết xuất giao diện | Tùy chọn | Chia sẻ với Compose, hoặc native theo từng màn hình | | API nền tảng (camera, sinh trắc học) | Không | Thay vào đó phơi bày qua expect/actual |
Lợi ích tăng theo mức độ một ứng dụng sở hữu nửa dưới của bảng đó. Các sản phẩm nặng dữ liệu với giao diện mỏng chia sẻ nhiều nhất; các ứng dụng đậm chất riêng, thiên về hoạt ảnh chia sẻ ít nhất. Không cực đoan nào là sai, và đó là lý do KMP phù hợp với việc áp dụng dần thay vì viết lại toàn bộ.
Cấu Hình Module Dùng Chung Trong Gradle
Một module KMP khai báo các target và source set của nó trong Kotlin DSL. Tệp build.gradle.kts sau đây thiết lập Android và ba kiến trúc iOS (thiết bị vật lý, simulator Intel, simulator Apple Silicon) và phơi bày đầu ra iOS dưới dạng một framework tên là Shared.
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
id("com.android.library")
}
kotlin {
// Android target compiles commonMain + androidMain to JVM bytecode
androidTarget()
// Three iOS targets: device, Intel sim, Apple Silicon sim
listOf(iosArm64(), iosX64(), iosSimulatorArm64()).forEach { target ->
target.binaries.framework {
baseName = "Shared" // becomes "import Shared" in Swift
isStatic = true
}
}
sourceSets {
commonMain.dependencies {
implementation("io.ktor:ktor-client-core:3.3.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
}
androidMain.dependencies {
implementation("io.ktor:ktor-client-okhttp:3.3.0")
}
iosMain.dependencies {
implementation("io.ktor:ktor-client-darwin:3.3.0")
}
}
}Mỗi target kéo về một engine Ktor riêng cho nền tảng (okhttp trên Android, darwin trên iOS) trong khi mã dùng chung chỉ phụ thuộc vào API chung ktor-client-core. Toàn bộ tập tùy chọn nằm trong tài liệu Kotlin Multiplatform.
Viết Mã Riêng Cho Nền Tảng Với expect và actual
Một số logic không thể nằm trong commonMain vì nó cần một API nền tảng. Cơ chế expect/actual khai báo hình dạng trong mã common và cung cấp một hiện thực cụ thể cho mỗi target, được phân giải lúc biên dịch với chi phí runtime bằng không.
// commonMain declares what it needs but cannot implement here
expect class Platform() {
val name: String
val osVersion: String
}
// Shared logic can now depend on Platform freely
fun analyticsUserAgent(): String {
val platform = Platform()
return "SharpSkill/" + platform.name + " " + platform.osVersion
}Hiện thực Android đọc từ lớp Build, còn hiện thực iOS đọc từ UIDevice. Cả hai thỏa mãn cùng một hợp đồng, nên commonMain không bao giờ biết nó đang nói chuyện với bên nào.
import android.os.Build
// actual supplies the Android-specific implementation
actual class Platform actual constructor() {
actual val name: String = "Android"
actual val osVersion: String = Build.VERSION.RELEASE
}import platform.UIKit.UIDevice
// actual uses UIKit directly, no Objective-C bridging code required
actual class Platform actual constructor() {
actual val name: String = "iOS"
actual val osVersion: String = UIDevice.currentDevice.systemVersion
}Kotlin/Native đi kèm các binding có kiểu cho toàn bộ SDK iOS, nên UIDevice, NSUserDefaults và các kiểu Foundation có thể được gọi từ Kotlin mà không cần mã kết dính viết tay.
Chia Sẻ Logic Mạng và Serialization
Mạng là đoạn mã có giá trị chia sẻ cao nhất vì hợp đồng API giống hệt nhau trên cả hai nền tảng. Một client Ktor cộng kotlinx.serialization thay thế cho hai hiện thực song song. Các hàm suspend dựa vào Kotlin coroutines, vốn ánh xạ gọn gàng sang async/await ở cả hai phía.
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.Serializable
// One serializable model, zero duplication across platforms
@Serializable
data class Question(val id: String, val prompt: String, val difficulty: String)
// One HttpClient, one JSON parser, one repository for both apps
class InterviewRepository {
private val client = HttpClient {
install(ContentNegotiation) { json() }
}
suspend fun fetchQuestions(tech: String): List<Question> =
client.get("https://api.sharpskill.dev/questions/" + tech).body()
}Repository này, logic thử lại của nó, và các model dữ liệu của nó được viết một lần. Một lỗi được sửa trong parser được sửa trên Android và iOS đồng thời, và đó chính là nơi KMP hoàn vốn chi phí thiết lập. Cùng khuôn mẫu đó mở rộng sang một cơ sở dữ liệu dùng chung với SQLDelight, lưu trữ key-value dùng chung, và dependency injection dùng chung với Koin, nên toàn bộ lớp dữ liệu có thể nằm trong commonMain phía sau các interface mà giao diện phụ thuộc vào.
Một sai lầm phổ biến là cố chia sẻ quá nhiều quá sớm. Lối đi thực dụng là bắt đầu với một repository, chứng minh bản build và pipeline CI trên cả hai nền tảng, rồi chuyển từng tính năng một khi sự tự tin tăng lên.
Sẵn sàng chinh phục phỏng vấn Android?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Xây Dựng Giao Diện Dùng Chung Với Compose Multiplatform
Compose Multiplatform đạt trạng thái ổn định trên iOS vào tháng 5 năm 2025 (phiên bản 1.8.0) và đạt 1.11.0 vào giữa năm 2026 với nhập liệu văn bản native và kết xuất đồng thời bật mặc định. Một @Composable trong commonMain kết xuất trên cả hai nền tảng, và các view model dùng chung kết hợp tự nhiên với kiến trúc MVVM hoặc MVI.
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.padding
// This list renders identically on Android and iOS
@Composable
fun QuestionList(questions: List<Question>) {
LazyColumn {
items(questions) { question ->
Card(modifier = Modifier.padding(8.dp)) {
Text(text = question.prompt, modifier = Modifier.padding(16.dp))
}
}
}
}Chia sẻ giao diện là tùy chọn và theo từng màn hình. Một nhóm có thể chia sẻ toàn bộ giao diện với Compose Multiplatform, giữ SwiftUI cho toàn bộ ứng dụng và chỉ chia sẻ logic, hoặc kết hợp cả hai. Các màn hình phụ thuộc vào UX riêng của nền tảng, chẳng hạn như widget hoặc App Clips, thường được giữ native.
Sử Dụng Mã Dùng Chung Từ Swift Trên iOS
Module dùng chung được biên dịch thành một framework mà Swift import như bất kỳ thư viện nào. Điểm vướng trong lịch sử là các hàm suspend của Kotlin và Flow xuất hiện dưới dạng các API callback vụng về. SKIE từ Touchlab giải quyết điều này bằng cách sinh ra Swift đúng phong cách: suspend trở thành async/await native và Flow trở thành AsyncSequence.
import SwiftUI
import Shared // the KMP framework, baseName = "Shared"
struct ContentView: View {
@State private var questions: [Question] = []
private let repository = InterviewRepository()
var body: some View {
List(questions, id: \.id) { question in
Text(question.prompt)
}
.task {
// With SKIE, the Kotlin suspend fun is a Swift async fun
questions = (try? await repository.fetchQuestions(tech: "android")) ?? []
}
}
}Nhóm iOS viết Swift và SwiftUI thông thường. Họ dùng các model và repository dùng chung y hệt như một Swift package native, giữ cho KMP vô hình đối với phần lớn mã nguồn iOS.
Hai điểm gồ ghề vẫn đáng nêu trong một buổi phỏng vấn. Thứ nhất, thời gian build: biên dịch Kotlin/Native chậm hơn một bản build Swift thuần, dù cache build của Gradle và việc phân phối XCFramework dựng sẵn làm dịu chi phí hằng ngày. Thứ hai, gỡ lỗi băng qua ranh giới Swift sang Kotlin đang cải thiện nhưng chưa mượt bằng việc ở lại trong một ngôn ngữ, đó là một lý do khác để các nhóm giữ bề mặt dùng chung có chủ đích thay vì phình to.
Các Câu Hỏi Phỏng Vấn Kotlin Multiplatform Cần Chuẩn Bị
Vì các câu hỏi phỏng vấn KMP ngày càng xuất hiện trong các vị trí Android và mobile, một vài câu lặp lại đáng để luyện tập cùng với phần chuẩn bị phỏng vấn Android rộng hơn:
- expect/actual khác interface như thế nào? expect/actual được phân giải lúc biên dịch cho mỗi target mà không cần dispatch runtime và có thể đứng sau một lớp, hàm, thuộc tính hoặc typealias. Một interface được phân giải động lúc runtime. Dùng expect/actual cho các API nền tảng và interface cho tính đa hình bên trong mã dùng chung.
- Kotlin/Native xử lý bộ nhớ và luồng ra sao? Kể từ Kotlin 1.7.20, trình quản lý bộ nhớ mới đã loại bỏ mô hình đóng băng đối tượng cũ, nên state khả biến dùng chung và coroutine hoạt động xuyên luồng gần như trên JVM.
- Một ứng dụng hiện có có thể áp dụng KMP dần dần không? Có. Module dùng chung được xuất bản dưới dạng AAR cho Android và XCFramework cho iOS, nên một repository hoặc tính năng có thể di chuyển từng phần một mà không cần viết lại.
- Khi nào giao diện nên giữ native thay vì dùng Compose Multiplatform? Khi một màn hình dựa vào UX riêng của nền tảng hoặc khi nhóm iOS sở hữu và muốn kiểm soát lớp trình bày.
Nhắm tới Kotlin 2.3 với trình biên dịch K2, Ktor 3.x cho mạng, SQLDelight cho cơ sở dữ liệu dùng chung, và Koin cho dependency injection. Về interop iOS, hãy thêm SKIE sớm; lắp nó vào sau khi lớp Swift đã được viết sẽ đau đớn hơn nhiều.
Kết Luận
Kotlin Multiplatform năm 2026 là một cách thực dụng để cắt bỏ logic trùng lặp giữa Android và iOS mà không từ bỏ giao diện hay hiệu năng native:
- Chia sẻ phần logic có một nguồn chân lý duy nhất (model, mạng, validation) và chọn giao diện native hoặc Compose theo từng màn hình.
- Dùng
expect/actualcho các API nền tảng; nó được phân giải lúc biên dịch mà không có chi phí runtime. - Viết phần mạng một lần với Ktor và
kotlinx.serializationđể các bản sửa lỗi đáp xuống cả hai nền tảng cùng lúc. - Áp dụng dần qua một AAR và XCFramework thay vì cam kết viết lại.
- Thêm SKIE ngay từ đầu để Swift dùng
suspendvàFlownhưasync/awaitvàAsyncSequencenative.
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Thẻ
Chia sẻ
Bài viết liên quan

Kotlin 2.3 cho Android: Name-Based Destructuring, KMP và Câu Hỏi Phỏng Vấn 2026
Câu hỏi phỏng vấn Kotlin 2.3 dành cho lập trình viên Android năm 2026. Name-based destructuring, KMP, context parameters, Flow và coroutines kèm ví dụ code.

Android 16 trong năm 2026: Các API mới, Desktop Mode và câu hỏi phỏng vấn kỹ thuật
Tìm hiểu chi tiết về Android 16 (API 36): chế độ edge-to-edge bắt buộc, Desktop Mode, ProgressStyle Notifications, Predictive Back và các câu hỏi phỏng vấn thường gặp cho lập trình viên Android.

Kotlin Flow vs StateFlow vs SharedFlow: Câu hỏi phỏng vấn Android năm 2026
Những câu hỏi Kotlin Flow vs StateFlow vs SharedFlow mà nhà tuyển dụng Android hỏi năm 2026, kèm câu trả lời rõ ràng, một bảng so sánh và mã nguồn sẵn sàng cho production.