O ViewModel do AndroidX serve como uma ponte, estabelecendo um contrato claro entre sua lógica de negócios compartilhada e os componentes de UI. Esse padrão ajuda a garantir a consistência dos dados em todas as plataformas, permitindo 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 a ViewModel do KMP no seu projeto, defina a dependência no
arquivo libs.versions.toml
:
[versions]
androidx-viewmodel = 2.9.3
[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 ViewModel para acesso do Swift
Por padrão, qualquer biblioteca adicionada à sua base de código não é exportada automaticamente para o framework binário. Se as APIs não forem exportadas, elas estarão
disponíveis no framework binário somente 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 classe Lifecycle_viewmodelViewModel
. Confira 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 do 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) Usar viewModelScope
na área de trabalho da JVM
Ao executar corrotinas em um ViewModel, a propriedade viewModelScope
é vinculada ao
Dispatchers.Main.immediate
, que pode não estar disponível na área de trabalho por
padrão. Para que ele 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 mais detalhes.
Usar 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 você não pode usar APIs específicas da plataforma e precisa abstraí-las. Por
exemplo, se você estiver usando um Application
do Android como um parâmetro
do construtor do ViewModel, será necessário migrar dessa API abstraindo-a.
Para mais informações sobre como usar código específico da plataforma, acesse código específico da plataforma no Kotlin Multiplatform.
Por exemplo, no snippet a seguir, 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 normalmente:
// androidApp/ui/MainScreen.kt @Composable fun MainScreen( viewModel: MainViewModel = viewModel( factory = mainViewModelFactory, ), ) { // observe the viewModel state }
Usar ViewModel do SwiftUI
No Android, o ciclo de vida do ViewModel é processado automaticamente e definido como escopo para um
ComponentActivity
, Fragment
, NavBackStackEntry
(Navegação 2) ou
rememberViewModelStoreNavEntryDecorator
(Navegação 3). O SwiftUI no iOS, no entanto, não tem um equivalente integrado para o ViewModel do AndroidX.
Para compartilhar a ViewModel com seu app SwiftUI, adicione um código de configuração.
Criar uma função para ajudar com genéricos
A criação de uma instância genérica de ViewModel usa um recurso de reflexão de referência de classe no Android. Como os tipos 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 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
para o ObservableObject
é poder usar essa classe como um @StateObject
no código 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, assim como no 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 } }
Não disponí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 ViewModels diretamente com a anotação @HiltViewModel
no
sourceSet commonMain
. Nesse caso, use uma estrutura de DI alternativa, como Koin, kotlin-inject, Metro ou Kodein. Encontre todos os frameworks de DI que funcionam com
Kotlin Multiplatform em klibs.io.
Observar fluxos no SwiftUI
Não há suporte direto para observar fluxos de corrotinas no SwiftUI. No entanto, você pode usar a biblioteca KMP-NativeCoroutines ou SKIE para permitir esse recurso.