Visão geral do ViewModel Parte do Android Jetpack.
A classe ViewModel é um detentor de estado da tela ou da lógica de
negócios. Ele expõe o estado à interface e encapsula a lógica de negócios correspondente.
A principal vantagem do ViewModel é que ele armazena o estado em cache e mantém essas informações mesmo
após mudanças de configuração. Isso significa que a interface não precisa buscar dados novamente
ao navegar entre diferentes atividades ou implementar mudanças de configuração, como
ao girar a tela.
Para mais informações sobre detentores de estado, consulte as orientações sobre esse assunto. Da mesma forma, para mais informações sobre a camada da interface de modo geral, consulte as respectivas orientações.
Benefícios do ViewModel
Uma alternativa para o uso de ViewModel é implementar uma classe simples contendo os dados mostrados na interface. Isso pode se tornar um problema ao navegar entre diferentes atividades ou destinos de navegação. Isso destrói esses dados se você não os armazenar usando o mecanismo de estado de instância salva. O ViewModel fornece uma API adequada para a persistência de dados que resolve esse problema.
Como alternativa, para armazenadores de estado puros, o Compose oferece recursos retain que
permitem que classes simples sobrevivam a mudanças de configuração sem a infraestrutura
completa de um ViewModel. Embora os dois mecanismos ajudem na retenção de estado, geralmente é mais seguro fornecer um ViewModel a uma instância retida do que o contrário, já que os ciclos de vida e os comportamentos de limpeza são diferentes.
Os principais benefícios da classe ViewModel são essencialmente dois:
- Ele permite manter o estado da interface.
- Oferece acesso à lógica de negócios.
Persistência
O ViewModel permite a persistência dos dados após a mudança do estado e após acionar diferentes operações. Com esse armazenamento em cache, não é necessário buscar dados novamente após mudanças de configuração comuns, como a rotação da tela.
Escopo
Ao instanciar um ViewModel, o sistema transmite um objeto que implementa a interface
ViewModelStoreOwner. Esse objeto pode ser um destino ou
um gráfico de navegação, uma atividade ou qualquer outro tipo que implemente a
interface. Também é possível definir o escopo de um ViewModel diretamente para um elemento combinável usando
a API rememberViewModelStoreOwner.
Em seguida, o Lifecycle do
ViewModelStoreOwner é definido como o escopo do ViewModel. Ele permanece na memória até que o ViewModelStoreOwner
apareça de forma definitiva (como quando o proprietário combinável sai da composição).
Um intervalo de classes é uma subclasse direta ou indireta da
interface ViewModelStoreOwner. As subclasses diretas são
ComponentActivity e NavBackStackEntry.
Para uma lista completa de subclasses indiretas, consulte a
documentação de referência do ViewModelStoreOwner. Para definir o escopo das ViewModels para itens individuais
em um LazyList ou Pager, use rememberViewModelStoreProvider() para elevar
o gerenciamento do proprietário ao elemento pai.
Quando a atividade host passa por uma mudança de configuração, o trabalho assíncrono continua no ViewModel, seja ele definido como escopo da atividade ou de um combinável específico. Essa é a chave para a persistência.
Para mais informações, consulte a seção Ciclo de vida do ViewModel abaixo, APIs de escopo do ViewModel e o guia sobre elevação de estado para o Jetpack Compose.
SavedStateHandle
O SavedStateHandle permite manter os dados não só depois de mudanças de configuração, mas também após o encerramento do processo. Ou seja, com esse elemento, você pode manter o estado da interface mesmo quando o usuário fecha e abre o app novamente.
Para mais informações sobre como salvar o estado da interface, consulte Salvar o estado da interface no Compose.
Acesso à lógica de negócios
Mesmo que a grande maioria das lógicas de negócios estejam presentes na camada de dados, a camada de interface também pode conter uma lógica de negócios. Isso pode ocorrer, por exemplo, ao combinar dados de vários repositórios para criar o estado da interface da tela ou quando um tipo específico de dados não precisa de uma camada de dados.
O ViewModel é o lugar certo para processar a lógica de negócios da camada de interface. Ele também é responsável por processar eventos e os delegar a outras camadas da hierarquia quando a lógica de negócios precisa ser aplicada para modificar dados do app.
Implementar um ViewModel
Confira abaixo um exemplo de implementação de um ViewModel para uma tela que permite que o usuário jogue o dado.
data class DiceUiState(
val firstDieValue: Int? = null,
val secondDieValue: Int? = null,
val numberOfRolls: Int = 0,
)
class DiceRollViewModel : ViewModel() {
// Expose screen UI state
private val _uiState = MutableStateFlow(DiceUiState())
val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()
// Handle business logic
fun rollDice() {
_uiState.update { currentState ->
currentState.copy(
firstDieValue = Random.nextInt(from = 1, until = 7),
secondDieValue = Random.nextInt(from = 1, until = 7),
numberOfRolls = currentState.numberOfRolls + 1,
)
}
}
}
Depois, você pode acessar o ViewModel de um elemento combinável no nível da tela desta maneira:
import androidx.lifecycle.viewmodel.compose.viewModel
// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
viewModel: DiceRollViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// Update UI elements
}
Usar corrotinas com o ViewModel
O ViewModel inclui suporte a corrotinas do Kotlin. Com elas, é possível manter o trabalho assíncrono,
da mesma maneira que mantém o estado da interface.
Para mais informações, consulte Usar corrotinas do Kotlin com Componentes da arquitetura do Android.
O ciclo de vida de um ViewModel
O ciclo de vida de um ViewModel está diretamente vinculado ao escopo dele. Um ViewModel
permanece na memória até que o ViewModelStoreOwner definido como escopo dele
desapareça. Isso pode ocorrer nestes contextos:
- No caso de uma atividade, quando ela é finalizada.
- No caso de uma entrada de navegação, quando ela é removida da backstack.
- No caso de um elemento combinável, quando ele sai da composição.
Você pode usar
rememberViewModelStoreOwnerpara definir o escopo de um ViewModel diretamente em uma parte arbitrária da sua interface (como umPagerouLazyList).
Isso faz com que os ViewModels sejam uma ótima solução para armazenar dados que resistam a mudanças de configuração.
A Figura 1 ilustra os vários estados do ciclo de vida de uma atividade à medida que ela é submetida a
uma rotação e, em seguida, é concluída. Ela também mostra a vida útil do
ViewModel ao lado do ciclo de vida da atividade associada. Este diagrama
específico ilustra os estados de uma atividade.
Geralmente, um ViewModel é solicitado na primeira vez que o sistema chama
o método onCreate() de um objeto de atividade. O sistema pode chamar
onCreate() várias vezes durante a existência de uma atividade, como
quando a tela de um dispositivo é rotacionada. O ViewModel existe do momento em que um
ViewModel é solicitado pela primeira vez até que a atividade seja finalizada e destruída.
Como limpar dependências do ViewModel
O ViewModel chama o método onCleared quando o ViewModelStoreOwner
o destrói ao longo do ciclo de vida. Isso permite que você limpe qualquer trabalho
ou dependência que siga o ciclo de vida do ViewModel.
O exemplo abaixo mostra uma alternativa ao viewModelScope.
O viewModelScope é um CoroutineScope (link em inglês) integrado que
segue automaticamente o ciclo de vida do ViewModel. O ViewModel usa o escopo para
acionar operações relacionadas a lógicas de negócios. Se você quiser usar um escopo personalizado em vez
de viewModelScope para testar mais facilmente, o ViewModel poderá receber um
CoroutineScope como dependência no construtor. Quando o
ViewModelStoreOwner limpa o ViewModel ao final do ciclo de vida, o
ViewModel também cancela o CoroutineScope.
class MyViewModel(
private val coroutineScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {
// Other ViewModel logic ...
override fun onCleared() {
coroutineScope.cancel()
}
}
A partir da versão 2.5 do ciclo de vida, é possível transmitir um ou mais objetos Closeable
para o construtor do ViewModel que é fechado automaticamente quando a
instância do ViewModel é limpa.
class CloseableCoroutineScope(
context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
class MyViewModel(
private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
// Other ViewModel logic ...
}
Práticas recomendadas
Confira abaixo diferentes práticas recomendadas que precisam ser consideradas ao implementar o ViewModel:
- Devido ao escopo, use os ViewModels como os detalhes da implementação de um detentor de estado de tela. Não use esses elementos como detentores de estados de componentes da interface reutilizáveis, como grupos de ícones ou formulários. Caso contrário, a mesma instância do ViewModel apareceria em diferentes usos do mesmo componente de interface no mesmo ViewModelStoreOwner, a menos que você use uma chave de modelo de visualização explícita por chip.
- Os ViewModels não podem ser informados sobre os detalhes de implementação da interface. Use os nomes mais genéricos possíveis nos métodos que a API ViewModel expõe e nos campos de estado da interface. Assim, o ViewModel pode acomodar qualquer tipo de interface: smartphones, dispositivos dobráveis, tablets ou até mesmo um Chromebook.
- Como é possível que eles durem mais tempo que o
ViewModelStoreOwner, os ViewModels não podem conter nenhuma referência a APIs relacionadas ao ciclo de vida, comoContextouResources, para evitar vazamentos de memória. - Não transmita ViewModels para outras classes, funções ou outros componentes de interface. Como eles são gerenciados pela plataforma, é necessário mantê-los o mais próximo possível dela, ou seja, perto da atividade, função combinável da tela ou destino de navegação. Isso impede que componentes de nível inferior acessem mais dados e lógicas do que o necessário.
Mais informações
À medida que seus dados se tornam mais complexos, você pode optar por usar uma classe separada apenas
para os carregar. O objetivo do ViewModel é encapsular os dados de um controlador
de interface para permitir que eles sejam mantidos
após mudanças de configuração. Para saber mais
sobre como carregar, manter e gerenciar dados após mudanças de configuração, consulte
Salvar estados da interface.
O Guia para arquitetura de apps Android sugere criar uma classe de repositório para processar essas funções.
Outros recursos
Para mais informações sobre a classe ViewModel, consulte os recursos
abaixo.
Documentação
- Camada de interface
- Eventos da interface de usuário
- Detentores de estado e estado da interface
- Produção de estado
- Camada de dados
Visualiza conteúdo
Amostras
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Usar corrotinas do Kotlin com componentes que reconhecem o ciclo de vida
- Salvar estados da interface
- Carregar e exibir dados paginados