Kotlin Multiplatform ปี 2026: แชร์โค้ด Android และ iOS

วิธีที่ Kotlin Multiplatform แชร์ตรรกะทางธุรกิจระหว่าง Android และ iOS ในปี 2026 พร้อมการตั้งค่า Gradle, expect/actual, เครือข่าย Ktor, UI Compose Multiplatform และคำถามสัมภาษณ์ KMP

Kotlin Multiplatform 2026 แชร์โค้ดระหว่างแอป Android และ iOS

Kotlin Multiplatform (KMP) ช่วยให้โค้ด Kotlin ชุดเดียวขับเคลื่อนตรรกะทางธุรกิจของแอป Android และ iOS ได้พร้อมกัน ในขณะที่แต่ละแพลตฟอร์มยังคงมี UI แบบเนทีฟ ประสิทธิภาพ และการเข้าถึง SDK ของตนได้อย่างเต็มที่ ในปี 2026 KMP ไม่ใช่การทดลองอีกต่อไป: เทคโนโลยีนี้เสถียรแล้ว ได้รับการสนับสนุนอย่างเป็นทางการจาก Google และใช้งานจริงในโปรดักชันที่ 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

shared/build.gradle.ktskotlin
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

การเขียนโค้ดเฉพาะแพลตฟอร์มด้วย expect และ actual

ตรรกะบางส่วนไม่สามารถอยู่ใน commonMain ได้เพราะต้องใช้ API ของแพลตฟอร์ม กลไก expect/actual ประกาศรูปร่างในโค้ด common และจัดหาการนำไปใช้ที่เป็นรูปธรรมต่อเป้าหมาย โดยแก้ไขตอนคอมไพล์ด้วยต้นทุนรันไทม์เป็นศูนย์

commonMain/kotlin/Platform.ktkotlin
// 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 จึงไม่เคยรู้ว่ากำลังคุยกับตัวไหน

androidMain/kotlin/Platform.ktkotlin
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
}
iosMain/kotlin/Platform.ktkotlin
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 เพียงตัวเดียวบวกกับ kotlinx.serialization แทนที่การนำไปใช้แบบขนานสองชุด ฟังก์ชัน suspend พึ่งพา Kotlin coroutines ซึ่งแมปเข้ากับ async/await ได้อย่างเรียบร้อยทั้งสองฝั่ง

commonMain/kotlin/data/InterviewRepository.ktkotlin
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 นี้ ตรรกะการลองใหม่ และโมเดลข้อมูลของมันถูกเขียนเพียงครั้งเดียว บั๊กที่แก้ใน parser จะได้รับการแก้ทั้งบน Android และ iOS พร้อมกัน และนั่นคือจุดที่ KMP คืนทุนค่าตั้งค่า รูปแบบเดียวกันนี้ขยายไปยังฐานข้อมูลที่ใช้ร่วมกันด้วย SQLDelight ที่เก็บ key-value ที่ใช้ร่วมกัน และ dependency injection ที่ใช้ร่วมกันด้วย Koin ดังนั้นชั้นข้อมูลทั้งหมดจึงอยู่ใน commonMain หลังอินเทอร์เฟซที่ UI พึ่งพาได้

ความผิดพลาดที่พบบ่อยคือการพยายามแชร์มากเกินไปเร็วเกินไป เส้นทางที่ปฏิบัติได้จริงคือเริ่มจาก repository เดียว พิสูจน์ build และไปป์ไลน์ CI บนทั้งสองแพลตฟอร์ม แล้วค่อยย้ายฟีเจอร์ทีละอย่างเมื่อความมั่นใจเพิ่มขึ้น

พร้อมที่จะพิชิตการสัมภาษณ์ Android แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

การสร้าง UI ที่ใช้ร่วมกันด้วย Compose Multiplatform

Compose Multiplatform เข้าสู่สถานะเสถียรบน iOS ในเดือนพฤษภาคม 2025 (เวอร์ชัน 1.8.0) และไปถึง 1.11.0 ในกลางปี 2026 พร้อมการป้อนข้อความแบบเนทีฟและการเรนเดอร์แบบขนานที่เปิดใช้งานโดยค่าเริ่มต้น @Composable ใน commonMain เรนเดอร์บนทั้งสองแพลตฟอร์ม และ view model ที่ใช้ร่วมกันเข้ากันได้ตามธรรมชาติกับ สถาปัตยกรรม MVVM หรือ MVI

commonMain/kotlin/ui/QuestionList.ktkotlin
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))
            }
        }
    }
}

การแชร์ UI เป็นทางเลือกและทำทีละหน้าจอ ทีมสามารถแชร์อินเทอร์เฟซทั้งหมดด้วย Compose Multiplatform เก็บ SwiftUI ไว้ทั้งแอปแล้วแชร์เฉพาะตรรกะ หรือผสมทั้งสองอย่าง หน้าจอที่พึ่งพา UX เฉพาะแพลตฟอร์ม เช่น วิดเจ็ตหรือ App Clips มักถูกเก็บไว้เป็นเนทีฟ

การใช้โค้ดที่ใช้ร่วมกันจาก Swift บน iOS

โมดูลที่ใช้ร่วมกันถูกคอมไพล์เป็น framework ที่ Swift import ได้เหมือนไลบรารีทั่วไป อุปสรรคในอดีตคือฟังก์ชัน suspend ของ Kotlin และ Flow ที่ปรากฏออกมาเป็น API แบบ callback ที่ใช้งานยาก SKIE จาก Touchlab แก้ปัญหานี้ด้วยการสร้าง Swift ที่เป็นสำนวนธรรมชาติ: suspend กลายเป็น async/await แบบเนทีฟ และ Flow กลายเป็น AsyncSequence

iosApp/ContentView.swiftswift
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 ในวงกว้าง:

  • 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 แบบเนทีฟ

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

แท็ก

#Kotlin Multiplatform
#KMP
#Android
#iOS
#Cross-platform

แชร์

บทความที่เกี่ยวข้อง