Kotlin Multiplatform (KMP): Giải “Lời Nguyền” Viết Code Hai Lần Cho Android & iOS

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Khi “Double Work” trở thành gánh nặng lúc 2 giờ sáng

2 giờ sáng, màn hình vẫn sáng trưng. Mình vừa fix xong một bug logic tính toán khuyến mãi cực kỳ lắt léo trên Android. Vừa định thở phào thì sực nhớ ra: “Chết tiệt, còn bản iOS nữa!”. Lại phải mở Xcode, tìm đúng đoạn logic đó bằng Swift, rồi cầu nguyện mình không gõ nhầm dấu phẩy nào. Việc duy trì hai codebase song song cho cùng một logic nghiệp vụ thực sự là một cơn ác mộng.

Cảnh ngộ này chắc chắn không của riêng ai. Viết đi viết lại một logic xử lý dữ liệu trên hai nền tảng vừa tốn thời gian, vừa dễ gây sai lệch dữ liệu. Mình từng cầm trịch đợt refactor một codebase hơn 50.000 dòng cho app fintech. Bài học xương máu: nếu logic tính toán bị phân mảnh, sớm muộn gì bạn cũng gặp tình trạng “Android chạy một kiểu, iOS chạy một kiểu” dù spec giống hệt nhau.

Kotlin Multiplatform (KMP) thực sự là gì?

KMP không giống Flutter hay React Native. Nó không cố vẽ lại giao diện (UI) theo cách riêng. Thay vào đó, KMP cho phép giữ nguyên UI Native (Jetpack Compose cho Android và SwiftUI cho iOS). Bạn chỉ tập trung chia sẻ phần logic nghiệp vụ như Data Layer, Domain Layer hoặc API Call.

Về mặt kỹ thuật, KMP biên dịch code Kotlin thành bytecode trên Android và framework native trên iOS. Hiệu năng gần như không đổi so với viết native thuần túy. Bạn vẫn có thể chạm tay vào các API đặc thù của hệ điều hành bất cứ lúc nào.

Cơ chế Expect/Actual – Chìa khóa vạn năng

Có những thứ không thể dùng chung hoàn toàn, chẳng hạn như cách lưu UUID hoặc truy cập Keychain. Đây là lúc bộ đôi expectactual tỏa sáng. Bạn khai báo hàm expect ở module chung (common), sau đó triển khai actual riêng biệt cho từng nền tảng. Rất tường minh và chặt chẽ.

Xây dựng Module Shared đầu tiên

Để bắt đầu, hãy cài đặt plugin Kotlin Multiplatform trên Android Studio. Cách nhanh nhất là dùng KMP Wizard để tạo cấu trúc dự án chuẩn chỉ.

1. Cấu trúc thư mục project

Một dự án KMP điển hình thường chia làm các phần:

  • composeApp/androidMain: Nơi chứa UI và logic chỉ dành riêng cho Android.
  • iosApp: Project Xcode chứa code SwiftUI nguyên bản.
  • shared/commonMain: “Trái tim” của dự án, nơi chứa logic nghiệp vụ dùng chung.
  • shared/iosMain & shared/androidMain: Nơi hiện thực hóa các phần code đặc thù cho từng OS.

2. Viết Logic chia sẻ

Giả sử cần kiểm tra định dạng Email. Thay vì viết ở hai nơi, mình chỉ viết một lần duy nhất tại shared/src/commonMain/kotlin/Validator.kt:

class Validator {
    fun isValidEmail(email: String): Boolean {
        val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$"
        return email.matches(emailRegex.toRegex())
    }
}

Cả Android và iOS giờ đây đều dùng chung một nguồn sự thật (single source of truth). Nếu sếp yêu cầu đổi regex, bạn chỉ cần sửa đúng một dòng duy nhất. Tiết kiệm ít nhất 50% thời gian bảo trì cho những thay đổi nhỏ như thế này.

3. Xử lý phần đặc thù

Muốn lấy phiên bản hệ điều hành? Ở commonMain, bạn khai báo:

// Trong commonMain
expect fun getPlatformName(): String

Tại androidMain, bạn triển khai:

actual fun getPlatformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}"

Và tại iosMain:

import platform.UIKit.UIDevice
actual fun getPlatformName(): String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion

Hệ sinh thái KMP: Những công cụ “phải có”

Để code KMP hiệu quả, bạn cần các thư viện hỗ trợ đa nền tảng thay vì thư viện Java thuần túy. Cộng đồng hiện nay đã cung cấp những bộ tool rất mạnh:

  • Ktor: Xử lý Networking (thay thế hoàn hảo cho Retrofit).
  • SQLDelight: Quản trị Database (mạnh mẽ và an toàn hơn Room trong môi trường KMP).
  • Kotlinx.serialization: Parse JSON tốc độ cao.
  • Koin: Dependency Injection siêu nhẹ.

Kinh nghiệm của mình: đừng cố chuyển đổi 100% logic ngay lập tức. Hãy bắt đầu từ các module nhỏ, độc lập như Validator hay Data Models. Khi đã làm chủ được Gradle, hãy tiến dần lên Repository và API.

Bài học xương máu: Đừng quên Test Coverage

Nhiều người lầm tưởng chia sẻ logic là xong. Thực tế, code chạy trên các runtime khác nhau (JVM trên Android và Native trên iOS) có thể phát sinh hành vi khác biệt. Dù Kotlin/Native hiện đã có New Memory Manager rất ổn định, việc cẩn trọng vẫn không thừa.

Trước khi refactor, mình luôn viết Unit Test kỹ lưỡng ở commonTest. Chỉ khi test pass trên cả hai môi trường, mình mới tự tin deploy. Đừng để đến lúc người dùng báo lỗi mới tá hỏa đi debug trên iOS, cực kỳ tốn sức!

Lời kết

KMP không phải là phép màu cho mọi dự án, nhưng nó là giải pháp cân bằng nhất giữa hiệu suất Native và tốc độ phát triển. Thay vì duy trì hai đội ngũ làm đi làm lại một việc, bạn có thể tập trung tối ưu trải nghiệm người dùng.

Nếu bạn đang ấp ủ một dự án mobile mới, hãy thử KMP ngay. Nó sẽ giúp bạn tránh được những đêm thức trắng vô nghĩa chỉ để copy-paste logic giữa hai nền tảng. Chúc các bạn có những trải nghiệm build app mượt mà!

Share: