Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

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 à IU 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 IU 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 IU 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 IU. Isso pode se tornar um problema ao navegar entre diferentes atividades ou destinos de navegação. Se esses dados não forem armazenados usando o mecanismo para salvar o estado de instância, eles serão destruídos durante a navegação. O ViewModel fornece uma API adequada para a persistência de dados, que resolve esse problema.

Os principais benefícios da classe ViewModel são essencialmente dois:

  • Permite manter o estado da IU.
  • Oferece acesso à lógica de negócios.

Persistência

O ViewModel permite que os dados sejam mantidos 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, um fragmento ou qualquer outro tipo que implemente a interface. Em seguida, o Lifecycle do ViewModelStoreOwner é definido como o escopo do ViewModel. Ele continua na memória até que o ViewModelStoreOwner apareça de forma definitiva.

Um intervalo de classes é uma subclasse direta ou indireta da interface ViewModelStoreOwner. As subclasses diretas são ComponentActivity, Fragment e NavBackStackEntry. Para uma lista completa de subclasses indiretas, consulte a documentação de referência do ViewModelStoreOwner.

Quando a atividade ou o fragmento que foi definido como escopo do ViewModel é destruído, o trabalho assíncrono continua a ser executado no ViewModel com escopo. Essa é a chave para a persistência de dados.

Para mais informações, consulte a seção abaixo sobre o ciclo de vida do ViewModel.

SavedStateHandle

SaveStateHandle permite manter os dados não só depois de mudanças de configuração, mas também após a recriação de processos. Ou seja, esse elemento permite manter o estado da IU mesmo quando o usuário fecha o app e o abre novamente depois.

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 IU 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 IU 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 IU. 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.

Jetpack Compose

No Jetpack Compose, o ViewModel é a principal forma de expor o estado da IU da tela aos elementos de composição. Em um app híbrido, as atividades e os fragmentos hospedam as funções de composição. Essa abordagem é diferente das anteriores, em que a criação de partes reutilizáveis da IU com atividades e fragmentos não era tão simples e intuitiva, fazendo com que esses elementos fossem muito mais ativos como controladores de IU.

O aspecto mais importante a ser considerado ao usar o ViewModel com o Compose é que não é possível definir um elemento de composição como o escopo de um ViewModel. Isso ocorre porque um elemento de composição não é um ViewModelStoreOwner. Nesse caso, duas instâncias do mesmo elemento na composição, ou dois elementos diferentes que acessam o mesmo tipo de ViewModel no mesmo ViewModelStoreOwner, receberiam a mesma instância do ViewModel, o que geralmente não é o comportamento esperado.

Para aproveitar os benefícios do ViewModel no Compose, hospede cada tela em um fragmento ou atividade diferente ou use a navegação do Compose e implemente os ViewModels em funções de composição que estejam o mais próximo possível do destino da navegação. Isso é necessário porque é possível definir atividades, fragmentos, gráficos e destinos de navegação como escopo de um ViewModel.

Para mais informações, consulte o guia sobre Estado e Jetpack Compose.

Implementar um ViewModel

Apresentamos abaixo um exemplo de implementação de um ViewModel. Esse caso de uso mostra uma lista de usuários.

Kotlin

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,
            )
        }
    }
}

Java

public class MyViewModel extends ViewModel {

    // Expose screen UI state
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }

    // Handle business logic
    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

Depois, você pode acessar a lista de uma atividade desta forma:

Kotlin

import androidx.activity.viewModels

class DiceRollActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same DiceRollViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: DiceRollViewModel by viewModels()
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

Java

import androidx.lifecycle.ViewModelProvider;

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

Jetpack Compose

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 IU.

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. Como mencionado acima, um ViewModel está 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 um fragmento, quando ele é removido.
  • No caso de uma entrada de navegação, quando ela é removida da backstack.

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. Os mesmos estados básicos se aplicam ao ciclo de vida de um fragmento.

Ilustra o ciclo de vida de um ViewModel como um estado de mudanças de 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.

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 IU 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 IU no mesmo ViewModelStoreOwner.
  • Os ViewModels não podem ser informados sobre os detalhes de implementação da IU. Use os nomes mais genéricos possíveis nos métodos que a API ViewModel expõe e nos campos de estado da IU. Assim, o ViewModel pode acomodar qualquer tipo de IU: 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 IU. Como eles são gerenciados pela plataforma, é necessário mantê-los o mais próximo possível dela. Perto da atividade, fragmento ou função de composição da tela. 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 IU 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 IU.

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

Exemplos