Visão geral do ViewModel   Parte do Android Jetpack.

Teste com o Kotlin Multiplatform
O Kotlin Multiplatform permite compartilhar a lógica de negócios com outras plataformas. Aprenda a configurar e trabalhar com ViewModel no KMP

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 rememberViewModelStoreOwner para definir o escopo de um ViewModel diretamente em uma parte arbitrária da sua interface (como um Pager ou LazyList).

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.

Ilustra o ciclo de vida de um ViewModel como um estado de mudanças de atividade.
Figura 1. Estados do ciclo de vida de uma atividade e de um ViewModel.

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, como Context ou Resources, 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

Visualiza conteúdo

Amostras