# 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. - Published: 2026-06-26 - Updated: 2026-07-06 - Author: SharpSkill - Tags: Kotlin Multiplatform, KMP, Android, iOS, Cross-platform - Reading time: 10 min --- 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](https://developer.android.com/kotlin/multiplatform) 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. > **KMP thực sự chia sẻ điều gì** > > 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`. ```kotlin // shared/build.gradle.kts 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](https://kotlinlang.org/docs/multiplatform.html). ## 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. ```kotlin // commonMain/kotlin/Platform.kt // 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. ```kotlin // androidMain/kotlin/Platform.kt 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 } ``` ```kotlin // iosMain/kotlin/Platform.kt 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](https://ktor.io/docs/) 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](/blog/android/mastering-kotlin-coroutines), vốn ánh xạ gọn gàng sang `async`/`await` ở cả hai phía. ```kotlin // commonMain/kotlin/data/InterviewRepository.kt 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 = 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. ## 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](/blog/android/mvvm-vs-mvi-architecture). ```kotlin // commonMain/kotlin/ui/QuestionList.kt 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) { 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](https://github.com/JetBrains/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](https://skie.touchlab.co/) 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`. ```swift // iosApp/ContentView.swift 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](/technologies/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. > **Bộ công cụ năm 2026** > > 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`/`actual` cho 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 `suspend` và `Flow` như `async`/`await` và `AsyncSequence` native. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/vi/blog/android/kotlin-multiplatform-2026