AndroidX ViewModel funge da ponte, stabilendo un contratto chiaro tra la logica di business condivisa e i componenti 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 ciascuna 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.
Configurare le dipendenze
Per configurare il ViewModel KMP nel tuo progetto, definisci la dipendenza nel file
libs.versions.toml
:
[versions]
androidx-viewmodel = 2.9.3
[libraries]
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-viewmodel" }
e poi aggiungi l'artefatto al file build.gradle.kts
per il 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)
}
Esportare le API ViewModel per l'accesso da Swift
Per impostazione predefinita, qualsiasi libreria che aggiungi al tuo codebase non verrà esportata 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 origini 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 ulteriori informazioni sull'esportazione delle dipendenze, consulta la sezione 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
è associata a
Dispatchers.Main.immediate
, che potrebbe non essere disponibile sul 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 Dispatchers.Main
.
Utilizza ViewModel da commonMain
o androidMain
Non esiste alcun requisito specifico per l'utilizzo della classe ViewModel in commonMain
condiviso, né dal sourceSet androidMain
. L'unico aspetto da considerare è 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 eseguendo l'astrazione.
Per ulteriori informazioni su come utilizzare il codice specifico della piattaforma, visita la pagina Codice specifico della piattaforma in Kotlin Multiplatform.
Ad esempio, nel seguente snippet è 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 }
Utilizzare ViewModel da SwiftUI
Su Android, il ciclo di vita di ViewModel viene gestito e limitato automaticamente 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 del codice di configurazione.
Crea una funzione per semplificare l'utilizzo dei generici
L'istanza di una ViewModel generica utilizza una funzionalità di reflection del riferimento alla classe su Android. Poiché i generici Objective-C non supportano tutte le funzionalità di Kotlin o Swift, non puoi recuperare direttamente una ViewModel di un tipo generico da Swift.
Per risolvere questo problema, puoi creare una funzione helper che utilizzi
ObjCClass
anziché il tipo generico e poi utilizzare getOriginalKotlinClass
per recuperare la classe ViewModel da istanziare:
// 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
.
Collega 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
di 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
. Per scoprire di più
sulla deinizializzazione, consulta la
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 ViewModels con l'annotazione @HiltViewModel
nel
sourceSet commonMain
. 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à.