تعمل ViewModel في AndroidX كجسر، حيث تنشئ عقدًا واضحًا بين منطق النشاط التجاري المشترَك ومكوّنات واجهة المستخدم. يساعد هذا النمط في ضمان اتساق البيانات على جميع المنصات، مع إتاحة تخصيص واجهات المستخدم لتناسب المظهر المميز لكل منصة. يمكنك مواصلة تطوير واجهة المستخدم باستخدام Jetpack Compose على Android وSwiftUI على iOS.
يمكنك الاطّلاع على مزيد من المعلومات حول مزايا استخدام ViewModel وجميع الميزات في المستندات الأساسية الخاصة بـ ViewModel.
إعداد التبعيات
لإعداد KMP ViewModel في مشروعك، حدِّد التبعية في ملف 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
وحدِّد التبعية على أنّها api
، لأنّه سيتم تصدير هذه التبعية إلى
إطار العمل الثنائي:
// 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 للوصول إليها من Swift
بشكلٍ تلقائي، لن يتم تلقائيًا تصدير أي مكتبة تضيفها إلى قاعدة الرموز إلى إطار العمل الثنائي. إذا لم يتم تصدير واجهات برمجة التطبيقات، لن تكون متاحة من إطار العمل الثنائي إلا إذا استخدمتها في الرمز المشترك (من مجموعة المصادر iosMain
أو commonMain
). في هذه الحالة، ستتضمّن واجهات برمجة التطبيقات بادئة الحزمة، على سبيل المثال، ستتوفّر الفئة ViewModel
كفئة Lifecycle_viewmodelViewModel
. لمزيد من المعلومات حول تصدير التبعيات، يُرجى الاطّلاع على مقالة تصدير التبعيات إلى ملفات ثنائية.
لتحسين التجربة، يمكنك تصدير تبعية ViewModel إلى إطار العمل الثنائي باستخدام عملية الإعداد export
في ملف build.gradle.kts
الذي تحدّد فيه إطار عمل iOS الثنائي، ما يتيح الوصول إلى واجهات برمجة تطبيقات ViewModel مباشرةً من رمز 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 في commonMain
أو من androidMain
sourceSet. الاعتبار الوحيد هو أنّه لا يمكنك استخدام أي واجهات برمجة تطبيقات خاصة بمنصة معيّنة، وعليك تجريدها. على سبيل المثال، إذا كنت تستخدم Application
في Android كمعلَمة لإنشاء ViewModel، عليك إيقاف استخدام واجهة برمجة التطبيقات هذه من خلال تجريدها.
يمكنك الاطّلاع على مزيد من المعلومات حول كيفية استخدام التعليمات البرمجية الخاصة بالنظام الأساسي على التعليمات البرمجية الخاصة بالنظام الأساسي في Kotlin Multiplatform.
على سبيل المثال، في المقتطف التالي، هناك فئة ViewModel مع المصنع الخاص بها،
محدّدة في 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()
بعد ذلك، يمكنك استرداد 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 أي مكافئ مضمّن لـ AndroidX ViewModel.
لمشاركة ViewModel مع تطبيق SwiftUI، عليك إضافة بعض رموز الإعداد.
إنشاء دالة للمساعدة في الأنواع العامة
يستخدم إنشاء مثيل عام من ViewModel ميزة انعكاس مرجع الفئة على Android. بما أنّ أنواع البيانات العامة في Objective-C لا تتوافق مع جميع ميزات 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.
تتم إزالة دورة حياة ViewModels هذه عندما تتم إزالة تهيئة الشاشة التي تستخدم
IosViewModelStoreOwner
ويتم استدعاء deinit
. يمكنك الاطّلاع على مزيد من المعلومات حول إلغاء التهيئة في المستندات الرسمية.
في هذه المرحلة، يمكنك إنشاء مثيل IosViewModelStoreOwner
كـ @StateObject
في عرض SwiftUI واستدعاء الدالة 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
بعض واجهات برمجة التطبيقات المتوفّرة على Android غير متاحة في Kotlin Multiplatform.
التكامل مع Hilt
بما أنّ Hilt غير متاح لمشاريع Kotlin Multiplatform،
لا يمكنك استخدام ViewModels مباشرةً مع التعليق التوضيحي @HiltViewModel
في
commonMain
sourceSet. في هذه الحالة، عليك استخدام بعض أُطر عمل بديلة لتوفير التبعية، مثل Koin أو kotlin-inject أو Metro أو Kodein. يمكنك العثور على جميع أُطر عمل إدراج التبعية التي تتوافق مع Kotlin Multiplatform على klibs.io.
مراقبة مسارات العمل في SwiftUI
لا تتوفّر إمكانية مراقبة تدفقات الروتينات المشتركة في SwiftUI مباشرةً. ومع ذلك، يمكنك استخدام مكتبة KMP-NativeCoroutines أو مكتبة SKIE للسماح بهذه الميزة.