AndroidX ViewModel funge da ponte, stabilendo un contratto chiaro tra la logica di business condivisa e i componenti della UI. Questo pattern contribuisce a garantire la coerenza dei dati tra le piattaforme, consentendo al contempo di personalizzare le UI per l'aspetto distinto di ogni piattaforma. Puoi continuare a sviluppare la tua UI con Jetpack Compose su Android e SwiftUI su iOS.
Scopri di più sui vantaggi dell'utilizzo di ViewModel e su tutte le funzionalità nella documentazione principale di ViewModel.
Configura le dipendenze
Per configurare ViewModel KMP nel tuo progetto, definisci la dipendenza nel file libs.versions.toml:
[versions]
androidx-viewmodel = 2.11.0
[libraries]
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-viewmodel" }
Quindi aggiungi l'artefatto al file build.gradle.kts per il tuo modulo KMP e dichiara la dipendenza come api, perché questa dipendenza verrà esportata nel framework binario:
// 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)
}
Esporta le API ViewModel per l'accesso da Swift
Per impostazione predefinita, le librerie che aggiungi al codebase non vengono esportate automaticamente nel framework binario. Se le API non vengono esportate, sono disponibili dal framework binario solo se le utilizzi nel codice condiviso (dal set di risorse iosMain o commonMain). In questo caso, le API conterranno il prefisso del pacchetto, ad esempio una classe ViewModel sarà disponibile come classe Lifecycle_viewmodelViewModel. Per saperne di più sull'esportazione delle dipendenze, consulta Esportazione delle dipendenze in file binari.
Per migliorare l'esperienza, puoi esportare la dipendenza ViewModel nel framework binario utilizzando la configurazione export nel file build.gradle.kts in cui definisci il framework binario iOS, che rende le API ViewModel accessibili direttamente dal codice Swift, come dal codice 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"
}
}
(Facoltativo) Utilizzo di viewModelScope su JVM Desktop
Quando esegui coroutine in un ViewModel, la proprietà viewModelScope è legata a Dispatchers.Main.immediate, che potrebbe non essere disponibile su computer per impostazione predefinita. Per farlo funzionare correttamente, aggiungi la dipendenza kotlinx-coroutines-swing al tuo progetto:
// Optional if you use JVM Desktop
desktopMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:[KotlinX Coroutines version]")
}
Per ulteriori dettagli, consulta la documentazione di Dispatchers.Main.
Utilizza ViewModel da commonMain o androidMain
Non esiste un requisito specifico per l'utilizzo della classe ViewModel nel modulo commonMain condiviso, né dal sourceSet androidMain. L'unica considerazione è che non puoi utilizzare API specifiche della piattaforma e devi astrarle. Ad esempio, se utilizzi un Application Android come parametro del costruttore ViewModel, devi eseguire la migrazione da questa API astrandola.
Per saperne di più su come utilizzare il codice specifico per la piattaforma, consulta Codice specifico per la piattaforma in Kotlin Multiplatform.
Ad esempio, nello snippet seguente è presente una classe ViewModel con la relativa factory, definita 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()
Poi, nel codice della UI, puoi recuperare il ViewModel come di consueto:
// androidApp/ui/MainScreen.kt @Composable fun MainScreen( viewModel: MainViewModel = viewModel( factory = mainViewModelFactory, ), ) { // observe the viewModel state }
Utilizza ViewModel da SwiftUI
Su Android, il ciclo di vita di ViewModel viene gestito automaticamente e limitato a un
ComponentActivity, Fragment, NavBackStackEntry (Navigation 2) o
rememberViewModelStoreNavEntryDecorator (Navigation 3). SwiftUI su iOS,
tuttavia, non ha un equivalente integrato per AndroidX ViewModel.
Per condividere il ViewModel con la tua app SwiftUI, devi aggiungere un codice di configurazione.
Crea una funzione per facilitare l'uso dei tipi generici
La creazione di un'istanza ViewModel generica utilizza una funzionalità di reflection del riferimento alla classe su Android. Poiché i generici di Objective-C non supportano tutte le funzionalità di Kotlin o Swift, non puoi recuperare direttamente un ViewModel di un tipo generico da Swift.
Per risolvere questo problema, puoi creare una funzione helper che utilizzerà
ObjCClass anziché il tipo di generici e poi utilizzare getOriginalKotlinClass
per recuperare la classe ViewModel di cui creare un'istanza:
// 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] }
Poi, quando vuoi chiamare la funzione da Swift, puoi scrivere una funzione generica di tipo T : ViewModel e utilizzare T.self, che può passare ObjCClass alla funzione resolveViewModel.
Connetti l'ambito ViewModel al ciclo di vita di SwiftUI
Il passaggio successivo consiste nel creare un IosViewModelStoreOwner che implementi le interfacce (protocolli) ObservableObject e ViewModelStoreOwner. Il motivo
per cui utilizziamo ObservableObject è poter utilizzare questa classe come @StateObject
nel codice 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() } }
Questo proprietario consente di recuperare più tipi di ViewModel, in modo simile ad Android.
Il ciclo di vita di questi ViewModel viene cancellato quando lo schermo che utilizza IosViewModelStoreOwner viene deinizializzato e chiama deinit. Puoi scoprire di più sulla deinizializzazione nella documentazione ufficiale.
A questo punto, puoi semplicemente creare un'istanza di IosViewModelStoreOwner come
@StateObject in una visualizzazione SwiftUI e chiamare la funzione viewModel per recuperare 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 disponibile in Kotlin Multiplatform
Alcune delle API disponibili su Android non sono disponibili in Kotlin Multiplatform.
Integrazione con Hilt
Poiché Hilt non è disponibile per i progetti Kotlin Multiplatform,
non puoi utilizzare direttamente i ViewModel con l'annotazione @HiltViewModel in
commonMain sourceSet. In questo caso, devi utilizzare un framework DI alternativo, ad esempio Koin, kotlin-inject, Metro o Kodein. Puoi trovare tutti i framework DI che funzionano con Kotlin Multiplatform su klibs.io.
Osserva i flussi in SwiftUI
L'osservazione dei flussi di coroutine in SwiftUI non è supportata direttamente. Tuttavia, puoi utilizzare la libreria KMP-NativeCoroutines o SKIE per consentire questa funzionalità.