El ViewModel de AndroidX sirve como puente, ya que establece un contrato claro entre tu lógica empresarial compartida y tus componentes de la IU. Este patrón ayuda a garantizar la coherencia de los datos en todas las plataformas y, al mismo tiempo, permite personalizar las IU para la apariencia distintiva de cada plataforma. Puedes seguir desarrollando tu IU con Jetpack Compose en Android y SwiftUI en iOS.
Obtén más información sobre los beneficios de usar ViewModel y todas las funciones en la documentación principal de ViewModel.
Configura dependencias
Para configurar el ViewModel de KMP en tu proyecto, define la dependencia en el archivo libs.versions.toml
:
[versions]
androidx-viewmodel = 2.9.3
[libraries]
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-viewmodel" }
Luego, agrega el artefacto al archivo build.gradle.kts
de tu módulo de KMP y declara la dependencia como api
, ya que esta dependencia se exportará al 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)
}
Exporta APIs de ViewModel para acceder desde Swift
De forma predeterminada, ninguna biblioteca que agregues a tu código base se exportará automáticamente al framework binario. Si las APIs no se exportan, estarán disponibles desde el framework binario solo si las usas en el código compartido (desde el conjunto de fuentes iosMain
o commonMain
). En ese caso, las APIs contendrían el prefijo del paquete; por ejemplo, una clase ViewModel
estaría disponible como clase Lifecycle_viewmodelViewModel
. Consulta cómo exportar dependencias a archivos binarios para obtener más información sobre la exportación de dependencias.
Para mejorar la experiencia, puedes exportar la dependencia de ViewModel al framework binario con la configuración de export
en el archivo build.gradle.kts
en el que defines el framework binario de iOS, lo que hace que las APIs de ViewModel sean accesibles directamente desde el código Swift de la misma manera que desde el código 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"
}
}
(Opcional) Cómo usar viewModelScope
en JVM Desktop
Cuando se ejecutan corrutinas en un ViewModel, la propiedad viewModelScope
está vinculada a Dispatchers.Main.immediate
, que podría no estar disponible en computadoras de escritorio de forma predeterminada. Para que funcione correctamente, agrega la dependencia kotlinx-coroutines-swing
a tu proyecto:
// Optional if you use JVM Desktop
desktopMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:[KotlinX Coroutines version]")
}
Consulta la documentación de Dispatchers.Main
para obtener más detalles.
Usa ViewModel desde commonMain
o androidMain
No hay ningún requisito específico para usar la clase ViewModel en el commonMain
compartido ni en el androidMain
sourceSet. La única consideración es que no puedes usar ninguna API específica de la plataforma y debes abstraerlas. Por ejemplo, si usas un Application
de Android como parámetro del constructor de ViewModel, debes migrar de esta API abstrayéndola.
En Código específico de la plataforma en Kotlin Multiplatform, encontrarás más información para usar código específico de la plataforma.
Por ejemplo, en el siguiente fragmento, se muestra una clase ViewModel con su fábrica, definida en 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()
Luego, en el código de la IU, puedes recuperar el ViewModel como de costumbre:
// androidApp/ui/MainScreen.kt @Composable fun MainScreen( viewModel: MainViewModel = viewModel( factory = mainViewModelFactory, ), ) { // observe the viewModel state }
Cómo usar ViewModel desde SwiftUI
En Android, el ciclo de vida de ViewModel se controla automáticamente y se limita a un ComponentActivity
, Fragment
, NavBackStackEntry
(Navigation 2) o rememberViewModelStoreNavEntryDecorator
(Navigation 3). Sin embargo, SwiftUI en iOS no tiene un equivalente integrado para el ViewModel de AndroidX.
Para compartir el ViewModel con tu app para SwiftUI, debes agregar un código de configuración.
Crea una función para ayudar con los genéricos
La creación de una instancia de ViewModel genérica usa una función de reflexión de referencia de clase en Android. Dado que los genéricos de Objective-C no admiten todas las funciones de Kotlin ni de Swift, no puedes recuperar directamente un ViewModel de un tipo genérico desde Swift.
Para solucionar este problema, puedes crear una función de ayuda que use ObjCClass
en lugar del tipo genérico y, luego, usar getOriginalKotlinClass
para recuperar la clase ViewModel que se creará como instancia:
// 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] }
Luego, cuando quieras llamar a la función desde Swift, puedes escribir una función genérica de tipo T : ViewModel
y usar T.self
, que puede pasar el ObjCClass
a la función resolveViewModel
.
Conecta el alcance de ViewModel al ciclo de vida de SwiftUI
El siguiente paso es crear un IosViewModelStoreOwner
que implemente las interfaces (protocolos) ObservableObject
y ViewModelStoreOwner
. El motivo de ObservableObject
es poder usar esta clase como @StateObject
en el código de 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() } }
Este propietario permite recuperar varios tipos de ViewModel, de manera similar a Android.
El ciclo de vida de esos ViewModels se borra cuando la pantalla que usa IosViewModelStoreOwner
se desinicializa y llama a deinit
. Puedes obtener más información sobre la desinicialización en la documentación oficial.
En este punto, puedes crear una instancia de IosViewModelStoreOwner
como @StateObject
en una vista de SwiftUI y llamar a la función viewModel
para recuperar 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 } }
No disponible en Kotlin Multiplatform
Algunas de las APIs disponibles en Android no están disponibles en Kotlin Multiplatform.
Integración con Hilt
Como Hilt no está disponible para los proyectos multiplataforma de Kotlin, no puedes usar ViewModels directamente con la anotación @HiltViewModel
en el commonMain
sourceSet. En ese caso, debes usar algún framework de DI alternativo, como Koin, kotlin-inject, Metro o Kodein. Puedes encontrar todos los frameworks de DI que funcionan con Kotlin Multiplatform en klibs.io.
Cómo observar flujos en SwiftUI
No se admite la observación de corrutinas de Flows en SwiftUI de forma directa. Sin embargo, puedes usar la biblioteca KMP-NativeCoroutines o SKIE para permitir esta función.