
اگه یه توسعهدهنده اندروید هستید که دلتون میخواد کدهاتون رو با iOS به اشتراک بذارید، یا میخواید بدونید KMP دقیقاً چطور کار میکنه، این مقاله برای شماست! 🎯
💼 چرا این مقاله رو نوشتیم؟ ما در تیم موبایلت تصمیم گرفتیم پروژهمون رو ریفکتور کنیم و قدم به قدم ببریم روی KMP تا بتونیم از یک کدبیس، خروجیهای مختلف داشته باشیم. این مقاله حاصل تحقیقات و آمادهسازی ما برای این مهاجرته.
توی این مقاله قراره عمیق بریم زیر پوست Kotlin Multiplatform و ببینیم این تکنولوژی چطور جادوش رو انجام میده - از معماری گرفته تا فرآیند کامپایل.
بزن بریم! 👇
۱. کاتلین مولتیپلتفرم (KMP) چیه؟
۲. چرا KMP؟ مقایسه با بقیه راهحلها
۳. معماری KMP
۴. ساختار پروژه و Source Set ها
۵. فرآیند کامپایل: از کاتلین تا باینری
۷. کتابخونههای مولتیپلتفرم
۸. Compose Multiplatform: لایه UI روی KMP
۹. راهنمای مهاجرت
کاتلین مولتیپلتفرم یه تکنولوژی از جتبرینز هست که بهتون اجازه میده یک بار کد بنویسید و روی چند پلتفرم اجراش کنید.
🤖 اندروید - از طریق Kotlin/JVM
🍎 iOS - از طریق Kotlin/Native
🖥️ دسکتاپ - ویندوز، مک، لینوکس
🌐 وب - از طریق Kotlin/JS و Kotlin/WASM
⚙️ سرور - بکاند با Ktor
💡 نکته مهم: KMP فقط برای موبایل نیست! میتونید منطق برنامه رو بین اپ موبایل، وبسایت و سرور به اشتراک بذارید.

فلسفه KMP: به جای اینکه همه چیز رو از صفر بسازه، KMP بهتون اجازه میده منطق برنامه (business logic) رو به اشتراک بذارید و UI رو بومی نگه دارید.
۱. اشتراکگذاری انتخابی 🎚️ خودتون تصمیم میگیرید چقدر کد به اشتراک بذارید. از ۱۰٪ تا ۹۰٪ دست شماست!
۲. UI کاملاً بومی 📱 رابط کاربری اندروید با Jetpack Compose و iOS با SwiftUI یا UIKit. کاربر اصلاً متوجه نمیشه!
۳. دسترسی کامل به API های بومی 🔧 هر وقت لازم باشه میتونید مستقیم به API های هر پلتفرم دسترسی داشته باشید.
۴. مهاجرت تدریجی 🚶♂️ لازم نیست پروژه رو از صفر بنویسید. میتونید کمکم کدها رو به KMP منتقل کنید.
۵. پشتیبانی گوگل 🏢 گوگل رسماً از KMP پشتیبانی میکنه و کتابخونههای Jetpack رو مولتیپلتفرم کرده.
منحنی یادگیری برای توسعهدهندههای iOS
ابزارها هنوز در حال بالغ شدن هستن
سایز باینری iOS ممکنه بزرگتر باشه
دیباگ کردن گاهی پیچیده میشه
┌─────────────────────────────────────────────────────────┐ │ اپلیکیشن شما │ ├─────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ UI اندروید │ │ UI آیاواس │ │ │ │ Jetpack Compose │ │ SwiftUI/UIKit │ │ │ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ └───────────┬───────────────┘ │ │ │ │ │ ┌───────────▼───────────┐ │ │ │ Shared Module │ │ │ │ (کد مشترک کاتلین) │ │ │ │ │ │ │ │ • ViewModels │ │ │ │ • Repositories │ │ │ │ • Use Cases │ │ │ │ • Data Models │ │ │ │ • Network Layer │ │ │ │ • Database │ │ │ └───────────────────────┘ │ │ │ ├──────────────────┬──────────────────┬──────────────────┤ │ Kotlin/JVM │ Kotlin/Native │ Kotlin/JS │ │ 🤖 اندروید │ 🍎 iOS │ 🌐 وب │ └──────────────────┴──────────────────┴──────────────────┘

Source Set روش گریدل برای گروهبندی کدها با وابستگیهای مخصوص خودشونه. توی KMP چند نوع source set داریم:
my-kmp-project/ │ ├── shared/ # 📦 ماژول مشترک │ ├── src/ │ │ ├── commonMain/ # 🌍 کد مشترک همه پلتفرمها │ │ │ └── kotlin/ │ │ │ ├── data/ │ │ │ │ ├── models/ │ │ │ │ ├── repository/ │ │ │ │ └── network/ │ │ │ ├── domain/ │ │ │ │ └── usecase/ │ │ │ └── Platform.kt # expect declarations │ │ │ │ │ ├── commonTest/ # 🧪 تستهای مشترک │ │ │ │ │ ├── androidMain/ # 🤖 کد مخصوص اندروید │ │ │ └── kotlin/ │ │ │ └── Platform.android.kt │ │ │ │ │ ├── iosMain/ # 🍎 کد مخصوص iOS │ │ │ └── kotlin/ │ │ │ └── Platform.ios.kt │ │ │ │ │ ├── iosArm64Main/ # 📱 دستگاه واقعی iOS │ │ ├── iosX64Main/ # 💻 شبیهساز مک اینتل │ │ └── iosSimulatorArm64Main/ # 💻 شبیهساز Apple Silicon │ │ │ └── build.gradle.kts │ ├── androidApp/ # 🤖 اپلیکیشن اندروید │ ├── src/main/ │ └── build.gradle.kts │ ├── iosApp/ # 🍎 اپلیکیشن iOS (Xcode) │ └── iosApp.xcodeproj │ └── build.gradle.kts
commonMain │ ┌────────────┼────────────┐ │ │ │ ▼ ▼ ▼ androidMain iosMain jsMain │ ┌────────────┼────────────┐ │ │ │ ▼ ▼ ▼ iosArm64Main iosX64Main iosSimulatorArm64Main
💡 نکته: میتونید source set های میانی هم بسازید. مثلاً
mobileMainکه بینandroidMainوiosMainمشترک باشه!
اینجاست که جادوی واقعی KMP اتفاق میفته! 🪄
کامپایلر کاتلین یه معماری چند بکاندی داره:
سورس کاتلین (.kt) │ ▼ ┌─────────────────┐ │ Frontend │ │ (Parser, etc) │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Kotlin IR │ ← نمایش میانی مشترک │ (Intermediate │ │ Representation) │ └────────┬────────┘ │ ┌────────────────┼────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────┐ ┌──────────┐ ┌──────────┐ │ Kotlin/ │ │ Kotlin/ │ │ Kotlin/ │ │ JVM │ │ Native │ │ JS │ │ Backend │ │ Backend │ │ Backend │ └────┬────┘ └────┬─────┘ └────┬─────┘ │ │ │ ▼ ▼ ▼ JVM Bytecode LLVM IR JavaScript (.class) │ (.js) │ ▼ │ Native Binary │ (.framework) ▼ DEX Bytecode (.dex)
Kotlin Source (.kt) │ ▼ Kotlin Compiler Frontend Kotlin IR │ ▼ Kotlin/JVM Backend JVM Bytecode (.class) │ ▼ D8/R8 Compiler DEX Bytecode (.dex) │ ▼ Android Runtime (ART) Native Code
توضیح مراحل:
1️⃣ Kotlin به IR: کامپایلر کاتلین سورس کد رو به یه نمایش میانی (IR) تبدیل میکنه.
2️⃣ IR به Bytecode: بکاند JVM این IR رو به بایتکد جاوا تبدیل میکنه.
3️⃣ Bytecode به DEX: کامپایلر D8/R8 بایتکد رو به فرمت DEX اندروید تبدیل میکنه.
4️⃣ DEX به Native: رانتایم ART با AOT و JIT کد بومی تولید میکنه.
Kotlin Source (.kt) │ ▼ Kotlin Compiler Frontend Kotlin IR │ ▼ Kotlin/Native Backend (Konan) LLVM IR │ ▼ LLVM Optimization Passes Optimized LLVM IR │ ▼ LLVM Backend Native Binary (.framework) │ ▼ Xcode Linker iOS App (.ipa)
توضیح مراحل:
1️⃣ Kotlin به IR: مثل اندروید، اول IR تولید میشه.
2️⃣ IR به LLVM IR: بکاند Kotlin/Native (با اسم رمز Konan) کد رو به LLVM IR تبدیل میکنه.
3️⃣ بهینهسازی LLVM: پاسهای بهینهسازی LLVM اعمال میشن (حذف کد مرده، اینلاین کردن، و...).
4️⃣ تولید باینری: LLVM باینری ARM64 تولید میکنه.
5️⃣ لینک با Xcode: فریمورک تولید شده با پروژه iOS لینک میشه.

🧠 نکته مهم: iOS هیچ ماشین مجازی نداره! کد کاتلین مستقیم به باینری ARM تبدیل میشه.
این قلب KMP برای مدیریت کدهای مخصوص پلتفرمه!
expect: یه قرارداد توی commonMain تعریف میکنید
actual: پیادهسازی واقعی توی هر پلتفرم
// 📁 commonMain/kotlin/Platform.kt expect class Platform() { val name: String val version: String } expect fun getDeviceId(): String
// 📁 androidMain/kotlin/Platform.android.kt actual class Platform actual constructor() { actual val name: String = "Android" actual val version: String = Build.VERSION.SDK_INT.toString() } actual fun getDeviceId(): String { return Settings.Secure.getString( context.contentResolver, Settings.Secure.ANDROID_ID ) }
// 📁 iosMain/kotlin/Platform.ios.kt actual class Platform actual constructor() { actual val name: String = UIDevice.currentDevice.systemName() actual val version: String = UIDevice.currentDevice.systemVersion } actual fun getDeviceId(): String { return UIDevice.currentDevice.identifierForVendor?.UUIDString ?: "" }
// 📁 commonMain expect fun getAppDirectory(): String // 📁 androidMain actual fun getAppDirectory(): String { return context.filesDir.absolutePath } // 📁 iosMain actual fun getAppDirectory(): String { return NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, true ).first() as String }
// 📁 commonMain expect fun createHttpClient(): HttpClient // 📁 androidMain actual fun createHttpClient(): HttpClient { return HttpClient(OkHttp) { install(ContentNegotiation) { json() } } } // 📁 iosMain actual fun createHttpClient(): HttpClient { return HttpClient(Darwin) { install(ContentNegotiation) { json() } } }
// 📁 commonMain expect class SecureStorage() { fun save(key: String, value: String) fun get(key: String): String? fun delete(key: String) } // 📁 androidMain actual class SecureStorage actual constructor() { private val prefs = EncryptedSharedPreferences.create(...) actual fun save(key: String, value: String) { prefs.edit().putString(key, value).apply() } actual fun get(key: String): String? = prefs.getString(key, null) actual fun delete(key: String) = prefs.edit().remove(key).apply() } // 📁 iosMain actual class SecureStorage actual constructor() { actual fun save(key: String, value: String) { // استفاده از Keychain } actual fun get(key: String): String? { ... } actual fun delete(key: String) { ... } }
✅ اسم و پکیج باید یکسان باشه
✅ signature باید دقیقاً match کنه
✅ هر expect باید actual داشته باشه (وگرنه کامپایل خطا میده)
✅ میتونید برای کلاس، تابع، پراپرتی، interface و annotation استفاده کنید
💡 نکته طلایی: تا جایی که ممکنه از expect/actual کمتر استفاده کنید. اول دنبال کتابخونه مولتیپلتفرم بگردید!

گوگل داره کتابخونههای Jetpack رو مولتیپلتفرم میکنه:
✅ Annotations
✅ Collections
✅ DataStore
✅ Lifecycle
✅ ViewModel
✅ Room (در حال توسعه)
✅ Paging
// 📁 commonMain/kotlin/data/network/ApiService.kt class ApiService(private val client: HttpClient) { suspend fun getUsers(): List<User> { return client.get("https://api.example.com/users").body() } suspend fun getUser(id: Int): User { return client.get("https://api.example.com/users/$id").body() } suspend fun createUser(user: User): User { return client.post("https://api.example.com/users") { contentType(ContentType.Application.Json) setBody(user) }.body() } }
-- 📁 commonMain/sqldelight/com/example/db/User.sq CREATE TABLE User ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, created_at INTEGER NOT NULL ); selectAll: SELECT * FROM User; selectById: SELECT * FROM User WHERE id = ?; insert: INSERT INTO User(name, email, created_at) VALUES (?, ?, ?); deleteById: DELETE FROM User WHERE id = ?;
// استفاده توی کد class UserRepository(private val database: AppDatabase) { fun getAllUsers(): Flow<List<User>> { return database.userQueries.selectAll().asFlow().mapToList() } suspend fun insertUser(name: String, email: String) { database.userQueries.insert( name = name, email = email, created_at = Clock.System.now().toEpochMilliseconds() ) } }
┌─────────────────────────────────────────┐ │ Compose Multiplatform (UI) │ ← لایه رابط کاربری ├─────────────────────────────────────────┤ │ Kotlin Multiplatform (Logic) │ ← لایه منطق └─────────────────────────────────────────┘
KMP: زیرساخت اشتراکگذاری کد (منطق برنامه) CMP: امکان اشتراکگذاری UI روی این زیرساخت
┌──────────────────┐ ┌──────────────────┐ │ Jetpack Compose │ │ SwiftUI/UIKit │ │ (اندروید) │ │ (iOS) │ └────────┬─────────┘ └────────┬─────────┘ │ │ └───────────┬───────────┘ │ ┌───────────▼───────────┐ │ Shared KMP Module │ │ (کد مشترک کاتلین) │ └───────────────────────┘
این رویکرد خیلی از تیمها ترجیح میدن چون:
UI کاملاً بومی میمونه
تیم iOS میتونه با SwiftUI کار کنه
ریسک کمتری داره
┌─────────────────────────────────────────┐ │ Compose Multiplatform │ │ (UI مشترک) │ ├─────────────────────────────────────────┤ │ Shared KMP Module │ │ (منطق مشترک) │ └─────────────────────────────────────────┘
Composable → Compose Runtime → Skiko/Skia → Metal API → صفحه نمایش
Skia کتابخونه گرافیکی گوگله که کروم، فلاتر و اندروید ازش استفاده میکنن. CMP از Skia برای رندر کردن UI روی iOS استفاده میکنه.
با یه چیز ساده شروع کنید:
مدلهای داده
کلاسهای utility
ثابتها
// 📁 shared/commonMain/kotlin/models/User.kt @Serializable data class User( val id: Int, val name: String, val email: String, val avatarUrl: String? )
Retrofit رو با Ktor جایگزین کنید:
// 📁 shared/commonMain/kotlin/data/network/ApiClient.kt class ApiClient { private val client = HttpClient { install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true prettyPrint = true }) } install(Logging) { level = LogLevel.BODY } } suspend fun fetchUsers(): List<User> { return client.get("https://api.example.com/users").body() } }
Room رو با SQLDelight جایگزین کنید.
// 📁 shared/commonMain/kotlin/data/repository/UserRepository.kt class UserRepository( private val apiClient: ApiClient, private val database: AppDatabase ) { fun getUsers(): Flow<List<User>> { return database.userQueries.selectAll().asFlow().mapToList() } suspend fun refreshUsers() { val users = apiClient.fetchUsers() users.forEach { user -> database.userQueries.insert(user) } } }
// 📁 shared/commonMain/kotlin/presentation/UserListViewModel.kt class UserListViewModel( private val repository: UserRepository ) : ViewModel() { private val _users = MutableStateFlow<List<User>>(emptyList()) val users: StateFlow<List<User>> = _users.asStateFlow() private val _isLoading = MutableStateFlow(false) val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow() fun loadUsers() { viewModelScope.launch { _isLoading.value = true repository.refreshUsers() repository.getUsers().collect { _users.value = it } _isLoading.value = false } } }
اگه خواستید میتونید UI رو هم با Compose Multiplatform به اشتراک بذارید.

کاتلین مولتیپلتفرم یه تکنولوژی قدرتمنده که بهتون اجازه میده با یه کدبیس، برای چند پلتفرم توسعه بدید. برخلاف فریمورکهایی مثل Flutter که همه چیز رو از صفر میسازن، KMP بهتون اجازه میده:
✅ منطق برنامه رو به اشتراک بذارید
✅ UI بومی داشته باشید
✅ گام به گام مهاجرت کنید
✅ به API های بومی دسترسی داشته باشید
۱. KMP = اشتراکگذاری منطق - UI میتونه بومی بمونه
۲. کامپایل متفاوت - اندروید از JVM، iOS از LLVM استفاده میکنه
۳. expect/actual - راهحل KMP برای کد مخصوص پلتفرم
۴. کتابخونههای آماده - Ktor، SQLDelight، Koin و...
۵. CMP اختیاریه - میتونید فقط از KMP بدون اشتراکگذاری UI استفاده کنید
۶. مهاجرت تدریجی - لازم نیست همه چیز رو یهویی عوض کنید
📖 مستندات رسمی https://kotlinlang.org/docs/multiplatform.html
🛠️ ابزار ساخت پروژه: https://kmp.jetbrains.com/
📦 لیست کتابخونهها: https://github.com/terrakok/kmp-awesome
💬 اسلک کاتلین: https://kotlinlang.slack.com/
🎓 کورس رسمی: https://www.jetbrains.com/help/kotlin-multiplatform-dev/
اگه این مقاله براتون مفید بود، لایک کنید و دنبال کنید! 👏
برچسبها: کاتلین، کاتلین مولتیپلتفرم، KMP، برنامهنویسی اندروید، برنامهنویسی آیاواس، کراس پلتفرم، توسعه موبایل، Kotlin Native، Kotlin Multiplatform