# Kotlin Multiplatform ปี 2026: แชร์โค้ด Android และ iOS > วิธีที่ Kotlin Multiplatform แชร์ตรรกะทางธุรกิจระหว่าง Android และ iOS ในปี 2026 พร้อมการตั้งค่า Gradle, expect/actual, เครือข่าย Ktor, UI Compose Multiplatform และคำถามสัมภาษณ์ 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) ช่วยให้โค้ด Kotlin ชุดเดียวขับเคลื่อนตรรกะทางธุรกิจของแอป Android และ iOS ได้พร้อมกัน ในขณะที่แต่ละแพลตฟอร์มยังคงมี UI แบบเนทีฟ ประสิทธิภาพ และการเข้าถึง SDK ของตนได้อย่างเต็มที่ ในปี 2026 KMP ไม่ใช่การทดลองอีกต่อไป: เทคโนโลยีนี้เสถียรแล้ว ได้รับการสนับสนุนอย่างเป็นทางการจาก [Google](https://developer.android.com/kotlin/multiplatform) และใช้งานจริงในโปรดักชันที่ Netflix, McDonald's, Forbes และ Cash App บทวิเคราะห์เชิงลึกนี้อธิบายว่าการแชร์โค้ดทำงานอย่างไรจริง วิธีตั้งค่าโมดูลที่ใช้ร่วมกัน และข้อแลกเปลี่ยนที่ควรเข้าใจก่อนเข้าสัมภาษณ์ KMP > **สิ่งที่ KMP แชร์จริง ๆ** > > Kotlin Multiplatform แชร์ตรรกะที่คอมไพล์แล้ว ไม่ใช่รันไทม์ โค้ด Kotlin common ถูกคอมไพล์เป็น bytecode ของ JVM สำหรับ Android และเป็นไบนารีเนทีฟ (ผ่าน LLVM) สำหรับ iOS ไม่มีบริดจ์ JavaScript ไม่มี webview ฝังตัว และไม่มีชั้น reflection ดังนั้นโค้ดที่ใช้ร่วมกันจึงทำงานด้วยความเร็วเนทีฟบนทั้งสองแพลตฟอร์ม ## Kotlin Multiplatform แชร์โค้ดอย่างไรโดยไม่เสียประสิทธิภาพเนทีฟ กลไกหลักคือลำดับชั้นของ source set โดย `commonMain` เก็บ Kotlin ที่ไม่ขึ้นกับแพลตฟอร์ม ได้แก่ เครือข่าย การ serialize กฎทางธุรกิจ และ view model ส่วน source set ของแพลตฟอร์มอย่าง `androidMain` และ `iosMain` เก็บโค้ดที่แตะ API ของแพลตฟอร์ม คอมไพเลอร์สร้าง artifact ของ JVM จาก `commonMain` บวก `androidMain` สำหรับ Android และสร้าง framework เนทีฟจาก `commonMain` บวก `iosMain` สำหรับ iOS Kotlin Multiplatform ในปี 2026 เน้นการแชร์สิ่งที่ได้ประโยชน์จริงจากแหล่งความจริงเดียว ได้แก่ โมเดล ไคลเอนต์ API การแคช และการตรวจสอบความถูกต้อง ส่วน UI สามารถแชร์ด้วย Compose Multiplatform หรือปล่อยให้เป็นเนทีฟทั้งหมดด้วย SwiftUI ทีละหน้าจอ โมเดล "แชร์ตรรกะ เลือก UI" นี้เองที่ทำให้ทีมนำ KMP มาใช้โดยไม่ต้องเขียนแอปเดิมใหม่ Kotlin Multiplatform เป็นเทคโนโลยีของ Kotlin ที่คอมไพล์โมดูลที่ใช้ร่วมกันหนึ่งชุดไปยังหลายเป้าหมาย (Android บน JVM, iOS เป็นเนทีฟ รวมถึงเดสก์ท็อปและเว็บ) เพื่อให้เขียนตรรกะทางธุรกิจเพียงครั้งเดียว ในขณะที่แต่ละแพลตฟอร์มยังคง UI เนทีฟและการเข้าถึง SDK อย่างเต็มที่ ปัจจุบัน Google รองรับเป็นตัวเลือกระดับชั้นหนึ่ง โดยมีไลบรารี Jetpack เช่น Room, DataStore, ViewModel และ Paging ที่มี artifact สำหรับ KMP การตัดสินใจว่าอะไรควรอยู่ในโมดูลที่ใช้ร่วมกันคือการตัดสินใจเชิงสถาปัตยกรรมที่สำคัญที่สุดในโปรเจกต์ KMP ตารางด้านล่างสะท้อนวิธีที่ทีมโปรดักชันส่วนใหญ่ขีดเส้นแบ่งในปี 2026: ทุกอย่างที่อยู่ใต้ชั้นการนำเสนอเป็นตัวเลือกที่เหมาะจะแชร์ ส่วนสิ่งใดที่แตะธรรมเนียม UX ของแพลตฟอร์มมักถูกเก็บไว้เป็นเนทีฟ | ชั้น | แชร์ใน commonMain? | เหตุผล | |-------|---------------------|--------| | โมเดลข้อมูลและ DTO | ใช่ | สัญญา API เหมือนกันทั้งสองแพลตฟอร์ม | | เครือข่ายและการแคช | ใช่ | HTTP สแตกเดียว พื้นผิวบั๊กเดียว | | กฎทางธุรกิจและการตรวจสอบ | ใช่ | โดเมนไม่ต่างกันตาม OS | | view model และ state | โดยทั่วไป | ทั้ง Compose และ SwiftUI ใช้ state ร่วมกัน | | การเรนเดอร์ UI | ไม่บังคับ | แชร์ด้วย Compose หรือเนทีฟทีละหน้าจอ | | API ของแพลตฟอร์ม (กล้อง, ไบโอเมตริก) | ไม่ | เปิดใช้ผ่าน expect/actual แทน | ผลตอบแทนเพิ่มขึ้นตามสัดส่วนที่แอปครอบครองครึ่งล่างของตารางนั้น ผลิตภัณฑ์ที่เน้นข้อมูลและมี UI บางแชร์ได้มากที่สุด ส่วนแอปที่ปรับแต่งเฉพาะตัวสูงและขับเคลื่อนด้วยแอนิเมชันแชร์ได้น้อยที่สุด ไม่มีขั้วใดผิด และนั่นคือเหตุผลที่ KMP เหมาะกับการนำมาใช้แบบค่อยเป็นค่อยไปมากกว่าการเขียนใหม่ทั้งหมด ## การตั้งค่าโมดูลที่ใช้ร่วมกันใน Gradle โมดูล KMP ประกาศเป้าหมายและ source set ของตนใน Kotlin DSL ไฟล์ `build.gradle.kts` ต่อไปนี้ตั้งค่า Android และสถาปัตยกรรม iOS สามแบบ (อุปกรณ์จริง, ซิมูเลเตอร์ Intel, ซิมูเลเตอร์ Apple Silicon) และเปิดเผยเอาต์พุตของ iOS เป็น framework ชื่อ `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") } } } ``` แต่ละเป้าหมายดึงเอนจิน Ktor เฉพาะแพลตฟอร์ม (`okhttp` บน Android, `darwin` บน iOS) ในขณะที่โค้ดที่ใช้ร่วมกันพึ่งพาเฉพาะ API กลาง `ktor-client-core` เท่านั้น ชุดตัวเลือกทั้งหมดอยู่ใน [เอกสาร Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) ## การเขียนโค้ดเฉพาะแพลตฟอร์มด้วย expect และ actual ตรรกะบางส่วนไม่สามารถอยู่ใน `commonMain` ได้เพราะต้องใช้ API ของแพลตฟอร์ม กลไก `expect`/`actual` ประกาศรูปร่างในโค้ด common และจัดหาการนำไปใช้ที่เป็นรูปธรรมต่อเป้าหมาย โดยแก้ไขตอนคอมไพล์ด้วยต้นทุนรันไทม์เป็นศูนย์ ```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 } ``` การนำไปใช้ฝั่ง Android อ่านจากคลาส `Build` ส่วนการนำไปใช้ฝั่ง iOS อ่านจาก `UIDevice` ทั้งคู่ทำตามสัญญาเดียวกัน ดังนั้น `commonMain` จึงไม่เคยรู้ว่ากำลังคุยกับตัวไหน ```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 มาพร้อม binding แบบมีชนิดสำหรับ SDK ของ iOS ทั้งหมด ดังนั้น `UIDevice`, `NSUserDefaults` และชนิดของ Foundation จึงเรียกใช้จาก Kotlin ได้โดยไม่ต้องเขียนโค้ดเชื่อมด้วยมือ ## การแชร์ตรรกะเครือข่ายและการ Serialize เครือข่ายเป็นโค้ดที่คุ้มค่าที่สุดในการแชร์ เพราะสัญญา API เหมือนกันทั้งสองแพลตฟอร์ม ไคลเอนต์ [Ktor](https://ktor.io/docs/) เพียงตัวเดียวบวกกับ `kotlinx.serialization` แทนที่การนำไปใช้แบบขนานสองชุด ฟังก์ชัน `suspend` พึ่งพา [Kotlin coroutines](/blog/android/mastering-kotlin-coroutines) ซึ่งแมปเข้ากับ `async`/`await` ได้อย่างเรียบร้อยทั้งสองฝั่ง ```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 นี้ ตรรกะการลองใหม่ และโมเดลข้อมูลของมันถูกเขียนเพียงครั้งเดียว บั๊กที่แก้ใน parser จะได้รับการแก้ทั้งบน Android และ iOS พร้อมกัน และนั่นคือจุดที่ KMP คืนทุนค่าตั้งค่า รูปแบบเดียวกันนี้ขยายไปยังฐานข้อมูลที่ใช้ร่วมกันด้วย SQLDelight ที่เก็บ key-value ที่ใช้ร่วมกัน และ dependency injection ที่ใช้ร่วมกันด้วย Koin ดังนั้นชั้นข้อมูลทั้งหมดจึงอยู่ใน `commonMain` หลังอินเทอร์เฟซที่ UI พึ่งพาได้ ความผิดพลาดที่พบบ่อยคือการพยายามแชร์มากเกินไปเร็วเกินไป เส้นทางที่ปฏิบัติได้จริงคือเริ่มจาก repository เดียว พิสูจน์ build และไปป์ไลน์ CI บนทั้งสองแพลตฟอร์ม แล้วค่อยย้ายฟีเจอร์ทีละอย่างเมื่อความมั่นใจเพิ่มขึ้น ## การสร้าง UI ที่ใช้ร่วมกันด้วย Compose Multiplatform Compose Multiplatform เข้าสู่สถานะเสถียรบน iOS ในเดือนพฤษภาคม 2025 (เวอร์ชัน 1.8.0) และไปถึง 1.11.0 ในกลางปี 2026 พร้อมการป้อนข้อความแบบเนทีฟและการเรนเดอร์แบบขนานที่เปิดใช้งานโดยค่าเริ่มต้น `@Composable` ใน `commonMain` เรนเดอร์บนทั้งสองแพลตฟอร์ม และ view model ที่ใช้ร่วมกันเข้ากันได้ตามธรรมชาติกับ [สถาปัตยกรรม MVVM หรือ 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)) } } } } ``` การแชร์ UI เป็นทางเลือกและทำทีละหน้าจอ ทีมสามารถแชร์อินเทอร์เฟซทั้งหมดด้วย [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform) เก็บ SwiftUI ไว้ทั้งแอปแล้วแชร์เฉพาะตรรกะ หรือผสมทั้งสองอย่าง หน้าจอที่พึ่งพา UX เฉพาะแพลตฟอร์ม เช่น วิดเจ็ตหรือ App Clips มักถูกเก็บไว้เป็นเนทีฟ ## การใช้โค้ดที่ใช้ร่วมกันจาก Swift บน iOS โมดูลที่ใช้ร่วมกันถูกคอมไพล์เป็น framework ที่ Swift import ได้เหมือนไลบรารีทั่วไป อุปสรรคในอดีตคือฟังก์ชัน `suspend` ของ Kotlin และ `Flow` ที่ปรากฏออกมาเป็น API แบบ callback ที่ใช้งานยาก [SKIE](https://skie.touchlab.co/) จาก Touchlab แก้ปัญหานี้ด้วยการสร้าง Swift ที่เป็นสำนวนธรรมชาติ: `suspend` กลายเป็น `async`/`await` แบบเนทีฟ และ `Flow` กลายเป็น `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")) ?? [] } } } ``` ทีม iOS เขียน Swift และ SwiftUI ตามปกติ พวกเขาใช้โมเดลและ repository ที่ใช้ร่วมกันเหมือนกับ Swift package แบบเนทีฟ ซึ่งทำให้ KMP มองไม่เห็นสำหรับโค้ด iOS ส่วนใหญ่ ยังมีจุดขรุขระสองอย่างที่ควรกล่าวถึงในการสัมภาษณ์ อย่างแรกคือเวลาในการ build: การคอมไพล์ Kotlin/Native ช้ากว่าการ build Swift ล้วน แม้ว่าแคช build ของ Gradle และการแจกจ่าย XCFramework ที่สร้างไว้ล่วงหน้าจะช่วยลดต้นทุนรายวัน อย่างที่สองคือการดีบักข้ามพรมแดน Swift ไปยัง Kotlin กำลังดีขึ้น แต่ยังไม่ราบรื่นเท่าการอยู่ในภาษาเดียว ซึ่งเป็นอีกเหตุผลที่ทีมรักษาพื้นผิวที่ใช้ร่วมกันให้ตั้งใจมากกว่าปล่อยให้บานปลาย ## คำถามสัมภาษณ์ Kotlin Multiplatform ที่ควรเตรียม เนื่องจากคำถามสัมภาษณ์ KMP ปรากฏมากขึ้นในตำแหน่ง Android และ mobile คำถามที่พบซ้ำ ๆ ไม่กี่ข้อจึงควรฝึกซ้อมควบคู่กับ [การเตรียมสัมภาษณ์ Android](/technologies/android) ในวงกว้าง: - **expect/actual ต่างจาก interface อย่างไร?** expect/actual แก้ไขตอนคอมไพล์ต่อเป้าหมายโดยไม่มี dispatch ตอนรันไทม์ และรองรับคลาส ฟังก์ชัน property หรือ typealias ได้ ส่วน interface แก้ไขแบบไดนามิกตอนรันไทม์ ใช้ expect/actual สำหรับ API ของแพลตฟอร์ม และใช้ interface สำหรับ polymorphism ภายในโค้ดที่ใช้ร่วมกัน - **Kotlin/Native จัดการหน่วยความจำและเธรดอย่างไร?** ตั้งแต่ Kotlin 1.7.20 ตัวจัดการหน่วยความจำใหม่ได้ลบโมเดลการแช่แข็งอ็อบเจกต์แบบเดิม ดังนั้น state ที่เปลี่ยนแปลงได้ที่ใช้ร่วมกันและ coroutine จึงทำงานข้ามเธรดได้เกือบเหมือนบน JVM - **แอปที่มีอยู่แล้วสามารถนำ KMP มาใช้แบบค่อยเป็นค่อยไปได้ไหม?** ได้ โมดูลที่ใช้ร่วมกันถูกส่งออกเป็น AAR สำหรับ Android และ XCFramework สำหรับ iOS ดังนั้น repository หรือฟีเจอร์หนึ่งจึงย้ายทีละอย่างได้โดยไม่ต้องเขียนใหม่ - **เมื่อใดที่ UI ควรเป็นเนทีฟแทนการใช้ Compose Multiplatform?** เมื่อหน้าจอพึ่งพา UX เฉพาะแพลตฟอร์ม หรือเมื่อทีม iOS เป็นเจ้าของและต้องการควบคุมชั้นการนำเสนอ > **Toolchain ในปี 2026** > > ตั้งเป้าที่ Kotlin 2.3 พร้อมคอมไพเลอร์ K2, Ktor 3.x สำหรับเครือข่าย, SQLDelight สำหรับฐานข้อมูลที่ใช้ร่วมกัน และ Koin สำหรับ dependency injection สำหรับการทำงานร่วมกับ iOS ให้เพิ่ม SKIE ตั้งแต่เนิ่น ๆ การนำมาใส่ภายหลังเมื่อเขียนชั้น Swift ไปแล้วจะเจ็บปวดกว่ามาก ## บทสรุป Kotlin Multiplatform ในปี 2026 เป็นวิธีที่ปฏิบัติได้จริงในการตัดตรรกะซ้ำซ้อนระหว่าง Android และ iOS โดยไม่ต้องยอมสละ UI หรือประสิทธิภาพเนทีฟ: - แชร์ตรรกะที่มีแหล่งความจริงเดียว (โมเดล เครือข่าย การตรวจสอบ) และเลือก UI เนทีฟหรือ Compose ทีละหน้าจอ - ใช้ `expect`/`actual` สำหรับ API ของแพลตฟอร์ม โดยแก้ไขตอนคอมไพล์โดยไม่มี overhead ตอนรันไทม์ - เขียนเครือข่ายเพียงครั้งเดียวด้วย Ktor และ `kotlinx.serialization` เพื่อให้การแก้ไขลงทั้งสองแพลตฟอร์มพร้อมกัน - นำมาใช้แบบค่อยเป็นค่อยไปผ่าน AAR และ XCFramework แทนการผูกมัดกับการเขียนใหม่ - เพิ่ม SKIE ตั้งแต่ต้นเพื่อให้ Swift ใช้ `suspend` และ `Flow` เป็น `async`/`await` และ `AsyncSequence` แบบเนทีฟ --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/android/kotlin-multiplatform-2026