O ViewModel do AndroidX serve como uma ponte, estabelecendo um contrato claro entre sua lógica de negócios compartilhada e seus componentes de IU. Esse padrão ajuda a garantir a consistência dos dados em todas as plataformas, além de permitir que as interfaces sejam personalizadas para a aparência distinta de cada plataforma. Você pode continuar desenvolvendo sua interface com o Jetpack Compose no Android e o SwiftUI no iOS.
Leia mais sobre os benefícios de usar o ViewModel e todos os recursos na documentação principal do ViewModel.
Configurar dependências
Para configurar o ViewModel do KMP no projeto, defina a dependência no
arquivo libs.versions.toml:
[versions]
androidx-viewmodel = 2.11.0
[libraries]
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-viewmodel" }
Em seguida, adicione o artefato ao arquivo build.gradle.kts do módulo KMP
e declare a dependência como api, porque ela será exportada para
o framework binário:
// 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)
}
Exportar APIs do ViewModel para acesso pelo Swift
Por padrão, as bibliotecas adicionadas à base de código não são exportadas automaticamente
para o framework binário. Se as APIs não forem exportadas, elas vão estar
disponíveis no framework binário apenas se você as usar no código compartilhado
(do conjunto de origem iosMain ou commonMain). Nesse caso, as APIs conteriam o prefixo do pacote. Por exemplo, uma classe ViewModel estaria disponível como uma classe Lifecycle_viewmodelViewModel. Confira como exportar dependências para
binários para mais informações sobre como exportar
dependências.
Para melhorar a experiência, exporte a dependência do ViewModel para o framework
binário usando a configuração export no arquivo build.gradle.kts em que você
define o framework binário do iOS. Isso torna as APIs do ViewModel acessíveis
diretamente do código Swift, assim como no 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) Como usar viewModelScope na JVM para computador
Ao executar corrotinas em um ViewModel, a propriedade viewModelScope é vinculada ao
Dispatchers.Main.immediate, que pode não estar disponível no computador por
padrão. Para que funcione corretamente, adicione a dependência kotlinx-coroutines-swing
ao projeto:
// Optional if you use JVM Desktop
desktopMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:[KotlinX Coroutines version]")
}
Consulte a documentação do Dispatchers.Main para saber mais.
Usar o ViewModel de commonMain ou androidMain
Não há um requisito específico para usar a classe ViewModel no commonMain
compartilhado nem no sourceSet androidMain. A única consideração é
que não é possível usar APIs específicas da plataforma, e você precisa abstraí-las. Por
exemplo, se você estiver usando um Application do Android como um parâmetro de construtor do ViewModel,
será necessário migrar dessa API, abstraindo-a.
Para mais informações sobre como usar código específico da plataforma, consulte Código específico da plataforma no Kotlin Multiplatform.
Por exemplo, no snippet abaixo, há uma classe ViewModel com a fábrica dela,
definida em 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()
Em seguida, no código da interface, recupere o ViewModel como de costume:
// androidApp/ui/MainScreen.kt @Composable fun MainScreen( viewModel: MainViewModel = viewModel( factory = mainViewModelFactory, ), ) { // observe the viewModel state }
Usar o ViewModel do SwiftUI
No Android, o ciclo de vida do ViewModel é processado automaticamente e tem como escopo um
ComponentActivity, Fragment, NavBackStackEntry (Navigation 2) ou
rememberViewModelStoreNavEntryDecorator (Navigation 3). No entanto, o SwiftUI no iOS
não tem um equivalente integrado para o ViewModel do AndroidX.
Para compartilhar o ViewModel com o app SwiftUI, é necessário adicionar um código de configuração.
Criar uma função para ajudar com genéricos
A instanciação de uma instância genérica do ViewModel usa um recurso de reflexão de referência de classe no Android. Como os genéricos do Objective-C não são compatíveis com todos os recursos do Kotlin ou do Swift, não é possível recuperar diretamente um ViewModel de um tipo genérico do Swift.
Para ajudar com esse problema, crie uma função auxiliar que use
ObjCClass em vez do tipo genérico e, em seguida, use getOriginalKotlinClass
para recuperar a classe ViewModel a ser instanciada:
// 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] }
Em seguida, quando quiser chamar a função do Swift, escreva uma função genérica
do tipo T : ViewModel e use T.self, que pode transmitir o
ObjCClass para a função resolveViewModel.
Conectar o escopo do ViewModel ao ciclo de vida do SwiftUI
A próxima etapa é criar um IosViewModelStoreOwner que implemente as
interfaces (protocolos) ObservableObject e ViewModelStoreOwner. O motivo
do ObservableObject é poder usar essa classe como um @StateObject
no código do 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() } }
Esse proprietário permite recuperar vários tipos de ViewModel, de forma semelhante ao Android.
O ciclo de vida desses ViewModels é limpo quando a tela que usa o
IosViewModelStoreOwner é desinicializada e chama deinit. Saiba mais sobre a desinicialização na documentação oficial.
Neste ponto, basta instanciar o IosViewModelStoreOwner como um
@StateObject em uma visualização do SwiftUI e chamar a função viewModel para recuperar um
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 } }
Indisponível no Kotlin Multiplatform
Algumas das APIs disponíveis no Android não estão disponíveis no Kotlin Multiplatform.
Integração com o Hilt
Como o Hilt não está disponível para projetos Kotlin Multiplatform,
não é possível usar diretamente ViewModels com a anotação @HiltViewModel no
sourceSet commonMain. Nesse caso, você precisa usar um framework de DI alternativo,
como Koin,
kotlin-inject, Metro ou
Kodein. Encontre todos os frameworks de DI que funcionam com
o Kotlin Multiplatform em klibs.io (link em inglês).
Observar fluxos no SwiftUI
Não há suporte direto para observar fluxos de corrotinas no SwiftUI. No entanto, é possível usar a biblioteca KMP-NativeCoroutines ou SKIE para permitir esse recurso.