AndroidX ViewModel sert de pont, établissant un contrat clair entre votre logique métier partagée et vos composants d'UI. Ce modèle permet d'assurer la cohérence des données sur toutes les plates-formes, tout en permettant de personnaliser les UI pour l'apparence distincte de chaque plate-forme. Vous pouvez continuer à développer votre UI avec Jetpack Compose sur Android et SwiftUI sur iOS.
Pour en savoir plus sur les avantages de l'utilisation de ViewModel et sur toutes les fonctionnalités, consultez la documentation principale de ViewModel.
Configurer des dépendances
Pour configurer le ViewModel KMP dans votre projet, définissez la dépendance dans le fichier libs.versions.toml
:
[versions]
androidx-viewmodel = 2.9.3
[libraries]
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-viewmodel" }
Ajoutez ensuite l'artefact au fichier build.gradle.kts
de votre module KMP et déclarez la dépendance en tant que api
, car cette dépendance sera exportée vers le framework binaire :
// 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)
}
Exporter les API ViewModel pour y accéder depuis Swift
Par défaut, toute bibliothèque que vous ajoutez à votre code n'est pas automatiquement exportée vers le framework binaire. Si les API ne sont pas exportées, elles ne sont disponibles à partir du framework binaire que si vous les utilisez dans le code partagé (à partir de l'ensemble de sources iosMain
ou commonMain
). Dans ce cas, les API contiennent le préfixe du package. Par exemple, une classe ViewModel
est disponible en tant que classe Lifecycle_viewmodelViewModel
. Pour en savoir plus sur l'exportation des dépendances, consultez la section Exporter les dépendances vers des binaires.
Pour améliorer l'expérience, vous pouvez exporter la dépendance ViewModel vers le framework binaire à l'aide de la configuration export
dans le fichier build.gradle.kts
où vous définissez le framework binaire iOS. Cela rend les API ViewModel accessibles directement à partir du code Swift, comme à partir du code 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"
}
}
(Facultatif) Utiliser viewModelScope
sur JVM Desktop
Lorsque vous exécutez des coroutines dans un ViewModel, la propriété viewModelScope
est liée à Dispatchers.Main.immediate
, qui peut ne pas être disponible sur ordinateur par défaut. Pour que cela fonctionne correctement, ajoutez la dépendance kotlinx-coroutines-swing
à votre projet :
// Optional if you use JVM Desktop
desktopMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:[KotlinX Coroutines version]")
}
Pour en savoir plus, consultez la documentation Dispatchers.Main
.
Utiliser ViewModel à partir de commonMain
ou androidMain
Il n'existe aucune exigence spécifique concernant l'utilisation de la classe ViewModel dans le commonMain
partagé, ni à partir du sourceSet androidMain
. La seule chose à prendre en compte est que vous ne pouvez pas utiliser d'API spécifiques à une plate-forme et que vous devez les abstraire. Par exemple, si vous utilisez un Application
Android comme paramètre de constructeur ViewModel, vous devez migrer depuis cette API en l'abstrayant.
Pour en savoir plus sur l'utilisation de code spécifique à une plate-forme, consultez Code spécifique à une plate-forme dans Kotlin Multiplatform.
Par exemple, l'extrait suivant présente une classe ViewModel avec sa fabrique, définie dans 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()
Ensuite, dans votre code d'UI, vous pouvez récupérer le ViewModel comme d'habitude :
// androidApp/ui/MainScreen.kt @Composable fun MainScreen( viewModel: MainViewModel = viewModel( factory = mainViewModelFactory, ), ) { // observe the viewModel state }
Utiliser ViewModel depuis SwiftUI
Sur Android, le cycle de vie ViewModel est automatiquement géré et limité à un ComponentActivity
, Fragment
, NavBackStackEntry
(Navigation 2) ou rememberViewModelStoreNavEntryDecorator
(Navigation 3). Toutefois, SwiftUI sur iOS ne dispose pas d'équivalent intégré pour AndroidX ViewModel.
Pour partager le ViewModel avec votre application SwiftUI, vous devez ajouter du code de configuration.
Créer une fonction pour faciliter l'utilisation des génériques
L'instanciation d'une instance ViewModel générique utilise une fonctionnalité de réflexion de référence de classe sur Android. Étant donné que les génériques Objective-C ne sont pas compatibles avec toutes les fonctionnalités de Kotlin ou de Swift, vous ne pouvez pas récupérer directement un ViewModel d'un type générique à partir de Swift.
Pour résoudre ce problème, vous pouvez créer une fonction d'assistance qui utilisera ObjCClass
au lieu du type générique, puis utiliser getOriginalKotlinClass
pour récupérer la classe ViewModel à instancier :
// 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] }
Ensuite, lorsque vous souhaitez appeler la fonction depuis Swift, vous pouvez écrire une fonction générique de type T : ViewModel
et utiliser T.self
, qui peut transmettre ObjCClass
à la fonction resolveViewModel
.
Connecter le champ d'application ViewModel au cycle de vie SwiftUI
L'étape suivante consiste à créer un IosViewModelStoreOwner
qui implémente les interfaces (protocoles) ObservableObject
et ViewModelStoreOwner
. La raison de l'utilisation de ObservableObject
est de pouvoir utiliser cette classe comme @StateObject
dans le code 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() } }
Ce propriétaire permet de récupérer plusieurs types de ViewModel, comme sur Android.
Le cycle de vie de ces ViewModels est effacé lorsque l'écran utilisant IosViewModelStoreOwner
est désinitialisé et appelle deinit
. Pour en savoir plus sur la désinitialisation, consultez la documentation officielle.
À ce stade, vous pouvez simplement instancier IosViewModelStoreOwner
en tant que @StateObject
dans une vue SwiftUI et appeler la fonction viewModel
pour récupérer un 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 } }
Non disponible dans Kotlin Multiplatform
Certaines API disponibles sur Android ne le sont pas dans Kotlin Multiplatform.
Intégration à Hilt
Comme Hilt n'est pas disponible pour les projets Kotlin Multiplatform, vous ne pouvez pas utiliser directement les ViewModels avec l'annotation @HiltViewModel
dans le sourceSet commonMain
. Dans ce cas, vous devez utiliser un autre framework d'injection de dépendances, par exemple Koin, kotlin-inject, Metro ou Kodein. Vous trouverez tous les frameworks d'injection de dépendances fonctionnant avec Kotlin Multiplatform sur klibs.io.
Observer les flux dans SwiftUI
L'observation des flux de coroutines dans SwiftUI n'est pas directement prise en charge. Toutefois, vous pouvez utiliser la bibliothèque KMP-NativeCoroutines ou SKIE pour autoriser cette fonctionnalité.