Das AndroidX-ViewModel dient als Brücke und stellt einen klaren Vertrag zwischen Ihrer gemeinsamen Geschäftslogik und Ihren UI-Komponenten her. Dieses Muster trägt dazu bei, dass die Daten auf allen Plattformen einheitlich sind. Gleichzeitig können die Benutzeroberflächen an das jeweilige Erscheinungsbild der einzelnen Plattformen angepasst werden. Sie können Ihre Benutzeroberfläche weiterhin mit Jetpack Compose unter Android und SwiftUI unter iOS entwickeln.
Weitere Informationen zu den Vorteilen der Verwendung von ViewModel und zu allen Funktionen finden Sie in der primären Dokumentation für ViewModel.
Abhängigkeiten einrichten
Um das KMP-ViewModel in Ihrem Projekt einzurichten, definieren Sie die Abhängigkeit in der Datei libs.versions.toml
:
[versions]
androidx-viewmodel = 2.9.3
[libraries]
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-viewmodel" }
Fügen Sie das Artefakt dann der Datei build.gradle.kts
für Ihr KMP-Modul hinzu und deklarieren Sie die Abhängigkeit als api
, da diese Abhängigkeit in das binäre Framework exportiert wird:
// 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-APIs für den Zugriff über Swift exportieren
Standardmäßig wird jede Bibliothek, die Sie Ihrem Code hinzufügen, nicht automatisch in das binäre Framework exportiert. Wenn die APIs nicht exportiert werden, sind sie nur über das binäre Framework verfügbar, wenn Sie sie im freigegebenen Code (aus dem Quellsatz iosMain
oder commonMain
) verwenden. In diesem Fall enthalten die APIs das Paketpräfix. So wäre beispielsweise eine ViewModel
-Klasse als Lifecycle_viewmodelViewModel
-Klasse verfügbar. Weitere Informationen zum Exportieren von Abhängigkeiten finden Sie unter Abhängigkeiten in Binärdateien exportieren.
Um die Nutzerfreundlichkeit zu verbessern, können Sie die ViewModel-Abhängigkeit mit der export
-Einrichtung in der build.gradle.kts
-Datei in das binäre Framework exportieren, in der Sie das binäre iOS-Framework definieren. Dadurch sind die ViewModel-APIs direkt über den Swift-Code zugänglich, genau wie über den Kotlin-Code:
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"
}
}
(Optional) viewModelScope
auf JVM Desktop verwenden
Wenn Sie Coroutinen in einem ViewModel ausführen, ist die viewModelScope
-Property an die Dispatchers.Main.immediate
gebunden, die auf dem Computer möglicherweise nicht standardmäßig verfügbar ist. Damit es richtig funktioniert, fügen Sie Ihrem Projekt die kotlinx-coroutines-swing
-Abhängigkeit hinzu:
// Optional if you use JVM Desktop
desktopMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:[KotlinX Coroutines version]")
}
Weitere Informationen finden Sie in der Dokumentation zu Dispatchers.Main
.
ViewModel aus commonMain
oder androidMain
verwenden
Für die Verwendung der ViewModel-Klasse im freigegebenen commonMain
oder im androidMain
-Quellset gibt es keine besonderen Anforderungen. Sie dürfen keine plattformspezifischen APIs verwenden, sondern müssen sie abstrahieren. Wenn Sie beispielsweise ein Android-Application
als ViewModel-Konstruktorparameter verwenden, müssen Sie diese API abstrahieren, um sie nicht mehr zu verwenden.
Weitere Informationen zur Verwendung plattformspezifischen Codes finden Sie unter Plattformspezifischer Code in Kotlin Multiplatform.
Im folgenden Snippet ist beispielsweise eine ViewModel-Klasse mit ihrer Factory definiert in 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()
Anschließend können Sie das ViewModel wie gewohnt in Ihrem UI-Code abrufen:
// androidApp/ui/MainScreen.kt @Composable fun MainScreen( viewModel: MainViewModel = viewModel( factory = mainViewModelFactory, ), ) { // observe the viewModel state }
ViewModel in SwiftUI verwenden
Unter Android wird der ViewModel-Lebenszyklus automatisch verwaltet und auf ein ComponentActivity
, Fragment
, NavBackStackEntry
(Navigation 2) oder rememberViewModelStoreNavEntryDecorator
(Navigation 3) beschränkt. SwiftUI für iOS hat jedoch kein integriertes Äquivalent für das AndroidX-ViewModel.
Damit Sie das ViewModel für Ihre SwiftUI-App freigeben können, müssen Sie etwas Einrichtungscode hinzufügen.
Funktion erstellen, die bei Generics hilft
Beim Instanziieren einer generischen ViewModel-Instanz wird eine Klassenreferenz-Reflektionsfunktion unter Android verwendet. Da generische Objective-C-Typen nicht alle Funktionen von Kotlin oder Swift unterstützen, können Sie ein ViewModel eines generischen Typs nicht direkt aus Swift abrufen.
Um dieses Problem zu beheben, können Sie eine Hilfsfunktion erstellen, die ObjCClass
anstelle des generischen Typs verwendet, und dann getOriginalKotlinClass
verwenden, um die ViewModel-Klasse zum Instanziieren abzurufen:
// 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] }
Wenn Sie die Funktion dann in Swift aufrufen möchten, können Sie eine generische Funktion vom Typ T : ViewModel
schreiben und T.self
verwenden, um ObjCClass
an die Funktion resolveViewModel
zu übergeben.
ViewModel-Bereich mit SwiftUI-Lebenszyklus verbinden
Als Nächstes erstellen Sie eine IosViewModelStoreOwner
, die die Schnittstellen (Protokolle) ObservableObject
und ViewModelStoreOwner
implementiert. Der Grund für die ObservableObject
ist, dass diese Klasse als @StateObject
im SwiftUI-Code verwendet werden kann:
// 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() } }
Mit diesem Inhaber können mehrere ViewModel-Typen abgerufen werden, ähnlich wie bei Android.
Der Lebenszyklus dieser ViewModels wird beendet, wenn der Bildschirm, auf dem IosViewModelStoreOwner
verwendet wird, deinitialisiert wird und deinit
aufgerufen wird. Weitere Informationen zur Deinitialisierung finden Sie in der offiziellen Dokumentation.
An diesem Punkt können Sie IosViewModelStoreOwner
einfach als @StateObject
in einer SwiftUI-Ansicht instanziieren und die Funktion viewModel
aufrufen, um ein ViewModel abzurufen:
// 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 } }
Nicht in Kotlin Multiplatform verfügbar
Einige der APIs, die unter Android verfügbar sind, sind in Kotlin Multiplatform nicht verfügbar.
Integration mit Hilt
Da Hilt für Kotlin Multiplatform-Projekte nicht verfügbar ist, können Sie ViewModels mit der Annotation @HiltViewModel
nicht direkt im commonMain
-Quellsatz verwenden. In diesem Fall müssen Sie ein alternatives DI-Framework verwenden, z. B. Koin, kotlin-inject, Metro oder Kodein. Alle DI-Frameworks, die mit Kotlin Multiplatform funktionieren, finden Sie unter klibs.io.
Flows in SwiftUI beobachten
Das Beobachten von Coroutine-Flows in SwiftUI wird nicht direkt unterstützt. Sie können jedoch entweder KMP-NativeCoroutines oder die SKIE-Bibliothek verwenden, um diese Funktion zu ermöglichen.