ViewModel ของ AndroidX ทำหน้าที่เป็นตัวเชื่อม โดยสร้างสัญญาที่ชัดเจนระหว่าง ตรรกะทางธุรกิจที่แชร์กับคอมโพเนนต์ UI รูปแบบนี้ช่วยให้มั่นใจได้ว่าข้อมูลจะสอดคล้องกันในทุกแพลตฟอร์ม ขณะเดียวกันก็ช่วยให้ปรับแต่ง UI ให้มีลักษณะเฉพาะของแต่ละแพลตฟอร์มได้ คุณสามารถพัฒนา UI ต่อไปได้ด้วย Jetpack Compose ใน Android และ SwiftUI ใน iOS
อ่านเพิ่มเติมเกี่ยวกับประโยชน์ของการใช้ ViewModel และฟีเจอร์ทั้งหมดในเอกสารประกอบหลัก สำหรับ ViewModel
ตั้งค่าทรัพยากร Dependency
หากต้องการตั้งค่า KMP ViewModel ในโปรเจ็กต์ ให้กำหนดทรัพยากร Dependency ในไฟล์
libs.versions.toml
ดังนี้
[versions]
androidx-viewmodel = 2.9.3
[libraries]
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-viewmodel" }
จากนั้นเพิ่มอาร์ติแฟกต์ลงในไฟล์ build.gradle.kts
สำหรับโมดูล KMP
และประกาศทรัพยากร Dependency เป็น api
เนื่องจากระบบจะส่งออกทรัพยากร Dependency นี้ไปยัง
เฟรมเวิร์กไบนารี
// You need the "api" dependency declaration here if you want better access to the classes from Swift code.
commonMain.dependencies {
api(libs.androidx.lifecycle.viewmodel)
}
ส่งออก ViewModel API เพื่อให้เข้าถึงได้จาก Swift
โดยค่าเริ่มต้น ระบบจะไม่ส่งออกไลบรารีที่คุณเพิ่มลงในโค้ดเบสไปยังเฟรมเวิร์กไบนารีโดยอัตโนมัติ
หากไม่ได้ส่งออก API จะใช้ได้จากเฟรมเวิร์กไบนารีเท่านั้นในกรณีที่คุณใช้ API ในโค้ดที่ใช้ร่วมกัน (จากชุดแหล่งที่มา iosMain
หรือ commonMain
) ในกรณีดังกล่าว API จะมีคำนำหน้าของแพ็กเกจ เช่น คลาส ViewModel
จะพร้อมใช้งานเป็นคลาส Lifecycle_viewmodelViewModel
ดูข้อมูลเพิ่มเติมเกี่ยวกับการส่งออกการอ้างอิงได้ที่การส่งออกการอ้างอิงไปยัง
ไบนารี
หากต้องการปรับปรุงประสบการณ์การใช้งาน คุณสามารถส่งออกการอ้างอิง ViewModel ไปยังเฟรมเวิร์กไบนารีได้
โดยใช้export
การตั้งค่าในไฟล์ build.gradle.kts
ซึ่งคุณจะกำหนดเฟรมเวิร์กไบนารีของ iOS ได้ ซึ่งจะทำให้เข้าถึง ViewModel API ได้
โดยตรงจากโค้ด Swift เช่นเดียวกับจากโค้ด Kotlin
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64(),
).forEach {
it.binaries.framework {
// Add this line to all the targets you want to export this dependency
export(libs.androidx.lifecycle.viewmodel)
baseName = "shared"
}
}
(ไม่บังคับ) การใช้ viewModelScope
ใน JVM Desktop
เมื่อเรียกใช้โครูทีนใน ViewModel พร็อพเพอร์ตี้ viewModelScope
จะเชื่อมโยงกับ
Dispatchers.Main.immediate
ซึ่งอาจไม่พร้อมใช้งานในเดสก์ท็อปโดย
ค่าเริ่มต้น หากต้องการให้ทำงานได้อย่างถูกต้อง ให้เพิ่มkotlinx-coroutines-swing
การอ้างอิงลงในโปรเจ็กต์
// Optional if you use JVM Desktop
desktopMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:[KotlinX Coroutines version]")
}
ดูรายละเอียดเพิ่มเติมในเอกสารประกอบDispatchers.Main
ใช้ ViewModel จาก commonMain
หรือ androidMain
ไม่มีข้อกำหนดที่เฉพาะเจาะจงสำหรับการใช้คลาส ViewModel ใน shared
commonMain
หรือจาก sourceSet ของ androidMain
ข้อควรพิจารณามีเพียงข้อเดียวคือ
คุณไม่สามารถใช้ API เฉพาะแพลตฟอร์มใดๆ และต้องแยก API เหล่านั้นออก ตัวอย่างเช่น หากคุณใช้ Android Application
เป็นพารามิเตอร์ของตัวสร้าง ViewModel
คุณจะต้องย้ายข้อมูลออกจาก API นี้โดยการแยกข้อมูล
ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีใช้โค้ดเฉพาะแพลตฟอร์มได้ที่ โค้ดเฉพาะแพลตฟอร์มใน Kotlin Multiplatform
ตัวอย่างเช่น ข้อมูลโค้ดต่อไปนี้เป็นคลาส ViewModel ที่มี Factory ของคลาส ซึ่งกำหนดไว้ใน commonMain
// commonMain/MainViewModel.kt class MainViewModel( private val repository: DataRepository, ) : ViewModel() { /* some logic */ } // ViewModelFactory that retrieves the data repository for your app. val mainViewModelFactory = viewModelFactory { initializer { MainViewModel(repository = getDataRepository()) } } fun getDataRepository(): DataRepository = DataRepository()
จากนั้นในโค้ด UI คุณจะเรียกข้อมูล ViewModel ได้ตามปกติ
// androidApp/ui/MainScreen.kt @Composable fun MainScreen( viewModel: MainViewModel = viewModel( factory = mainViewModelFactory, ), ) { // observe the viewModel state }
ใช้ ViewModel จาก SwiftUI
ใน Android ระบบจะจัดการวงจรของ ViewModel โดยอัตโนมัติและกำหนดขอบเขตเป็น
ComponentActivity
, Fragment
, NavBackStackEntry
(Navigation 2) หรือ
rememberViewModelStoreNavEntryDecorator
(Navigation 3) อย่างไรก็ตาม SwiftUI ใน iOS
ไม่มี ViewModel ของ AndroidX ที่เทียบเท่าในตัว
หากต้องการแชร์ ViewModel กับแอป SwiftUI คุณต้องเพิ่มโค้ดการตั้งค่าบางอย่าง
สร้างฟังก์ชันเพื่อช่วยจัดการกับ Generics
การสร้างอินสแตนซ์ ViewModel ทั่วไปจะใช้ฟีเจอร์การสะท้อนการอ้างอิงคลาสใน Android เนื่องจาก Objective-C generics ไม่รองรับฟีเจอร์ทั้งหมดของ Kotlin หรือ Swift คุณจึงไม่สามารถเรียกข้อมูล ViewModel ของประเภททั่วไปจาก Swift ได้โดยตรง
หากต้องการช่วยแก้ปัญหานี้ คุณสามารถสร้างฟังก์ชันตัวช่วยที่จะใช้
ObjCClass
แทนประเภททั่วไป แล้วใช้ getOriginalKotlinClass
เพื่อเรียกคลาส ViewModel มาสร้างอินสแตนซ์ได้
// iosMain/ViewModelResolver.ios.kt /** * This function allows retrieving any ViewModel from Swift Code with generics. We only get * [ObjCClass] type for the [modelClass], because the interop between Kotlin and Swift code * doesn't preserve the generic class, but we can retrieve the original KClass in Kotlin. */ @BetaInteropApi @Throws(IllegalArgumentException::class) fun ViewModelStore.resolveViewModel( modelClass: ObjCClass, factory: ViewModelProvider.Factory, key: String?, extras: CreationExtras? = null, ): ViewModel { @Suppress("UNCHECKED_CAST") val vmClass = getOriginalKotlinClass(modelClass) as? KClass<ViewModel> require(vmClass != null) { "The modelClass parameter must be a ViewModel type." } val provider = ViewModelProvider.Companion.create(this, factory, extras ?: CreationExtras.Empty) return key?.let { provider[key, vmClass] } ?: provider[vmClass] }
จากนั้นเมื่อต้องการเรียกใช้ฟังก์ชันจาก Swift คุณจะเขียนฟังก์ชันทั่วไป
ประเภท T : ViewModel
และใช้ T.self
ซึ่งส่ง ObjCClass
ไปยังฟังก์ชัน resolveViewModel
ได้
เชื่อมต่อขอบเขต ViewModel กับวงจรของ SwiftUI
ขั้นตอนถัดไปคือการสร้าง IosViewModelStoreOwner
ที่ใช้
อินเทอร์เฟซ (โปรโตคอล) ObservableObject
และ ViewModelStoreOwner
เหตุผลที่ต้องมี ObservableObject
ก็คือเพื่อให้ใช้คลาสนี้เป็น @StateObject
ในโค้ด SwiftUI ได้
// iosApp/IosViewModelStoreOwner.swift class IosViewModelStoreOwner: ObservableObject, ViewModelStoreOwner { let viewModelStore = ViewModelStore() /// This function allows retrieving the androidx ViewModel from the store. /// It uses the utilify function to pass the generic type T to shared code func viewModel<T: ViewModel>( key: String? = nil, factory: ViewModelProviderFactory, extras: CreationExtras? = nil ) -> T { do { return try viewModelStore.resolveViewModel( modelClass: T.self, factory: factory, key: key, extras: extras ) as! T } catch { fatalError("Failed to create ViewModel of type \(T.self)") } } /// This is called when this class is used as a `@StateObject` deinit { viewModelStore.clear() } }
เจ้าของนี้อนุญาตให้ดึงข้อมูล ViewModel หลายประเภทได้เช่นเดียวกับใน Android
ระบบจะล้างวงจรของ ViewModel เหล่านั้นเมื่อหน้าจอที่ใช้
IosViewModelStoreOwner
ได้รับการยกเลิกการเริ่มต้นและเรียกใช้ deinit
ดูข้อมูลเพิ่มเติมเกี่ยวกับการยกเลิกการเริ่มต้นได้ที่เอกสารอย่างเป็นทางการ
ในตอนนี้ คุณเพียงแค่สร้างอินสแตนซ์ IosViewModelStoreOwner
เป็น
@StateObject
ใน SwiftUI View และเรียกฟังก์ชัน viewModel
เพื่อดึงข้อมูล ViewModel
// iosApp/ContentView.swift struct ContentView: View { /// Use the store owner as a StateObject to allow retrieving ViewModels and scoping it to this screen. @StateObject private var viewModelStoreOwner = IosViewModelStoreOwner() var body: some View { /// Retrieves the `MainViewModel` instance using the `viewModelStoreOwner`. /// The `MainViewModel.Factory` and `creationExtras` are provided to enable dependency injection /// and proper initialization of the ViewModel with its required `AppContainer`. let mainViewModel: MainViewModel = viewModelStoreOwner.viewModel( factory: MainViewModelKt.mainViewModelFactory ) // ... // .. the rest of the SwiftUI code } }
ไม่พร้อมใช้งานใน Kotlin Multiplatform
API บางรายการที่พร้อมใช้งานใน Android จะไม่พร้อมใช้งานใน Kotlin Multiplatform
การผสานรวมกับ Hilt
เนื่องจาก Hilt ไม่พร้อมใช้งานสำหรับโปรเจ็กต์ Kotlin Multiplatform
คุณจึงใช้ ViewModel กับคำอธิบายประกอบ @HiltViewModel
ใน
commonMain
sourceSet โดยตรงไม่ได้ ในกรณีนี้ คุณต้องใช้เฟรมเวิร์ก DI
อื่น เช่น Koin
kotlin-inject, Metro หรือ
Kodein คุณดูเฟรมเวิร์ก DI ทั้งหมดที่ใช้ได้กับ
Kotlin Multiplatform ได้ที่ klibs.io
สังเกตโฟลว์ใน SwiftUI
SwiftUI ไม่รองรับการสังเกตการณ์ Coroutine Flow โดยตรง อย่างไรก็ตาม คุณสามารถใช้ไลบรารี KMP-NativeCoroutines หรือ SKIE เพื่ออนุญาตฟีเจอร์นี้ได้