O que é ViewModel no Android?
ViewModel é um componente da biblioteca Android Jetpack projetado para armazenar e gerenciar dados relacionados a interface de usuário de forma consciente do ciclo de vida. Quando uma Activity ou Fragment e destruida e recriada (por exemplo, ao girar a tela), o ViewModel sobrevive a essa recriação e mantém os dados intactos, evitando carregamentos desnecessários.
O ViewModel separa a lógica de apresentação da camada de UI. A Activity ou Fragment cuida apenas de exibir dados e capturar interações do usuário, enquanto o ViewModel gerencia o estado, executa operações assíncronas e expõe dados reativos.
Configuração
Adicione as dependências no build.gradle.kts:
dependencies {
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
// Para usar com Compose
implementation("androidx.activity:activity-compose:1.9.3")
}
Sintaxe básica
Um ViewModel básico herda da classe ViewModel():
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class ContadorViewModel : ViewModel() {
private val _contador = MutableStateFlow(0)
val contador: StateFlow<Int> = _contador.asStateFlow()
fun incrementar() {
_contador.value++
}
fun decrementar() {
_contador.value--
}
fun resetar() {
_contador.value = 0
}
}
Na Activity ou Fragment, você obtém o ViewModel assim:
import androidx.activity.viewModels
class ContadorActivity : AppCompatActivity() {
private val viewModel: ContadorViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
viewModel.contador.collect { valor ->
binding.textoContador.text = "Contagem: $valor"
}
}
binding.botaoIncrementar.setOnClickListener {
viewModel.incrementar()
}
}
}
Ciclo de vida do ViewModel
O ViewModel e criado na primeira vez que a Activity ou Fragment solicita e sobrevive a recriacoes de configuração (rotação de tela, mudanca de idioma). Ele só e destruído quando a Activity e finalizada definitivamente (o usuário navega para tras ou chama finish()).
Activity criada --> ViewModel criado
Tela rotacionada --> Activity destruida e recriada, ViewModel SOBREVIVE
Usuario sai --> Activity finalizada, ViewModel.onCleared() chamado
O método onCleared() e chamado quando o ViewModel e destruído definitivamente, permitindo limpar recursos:
class MinhaViewModel : ViewModel() {
private val conexao = abrirConexao()
override fun onCleared() {
super.onCleared()
conexao.fechar()
println("ViewModel destruido, recursos liberados")
}
}
Exemplos práticos
ViewModel com chamada de API
data class Usuario(val id: Int, val nome: String, val email: String)
sealed class UiState<out T> {
data object Carregando : UiState<Nothing>()
data class Sucesso<T>(val dados: T) : UiState<T>()
data class Erro(val mensagem: String) : UiState<Nothing>()
}
class UsuarioViewModel(
private val repositorio: UsuarioRepositorio
) : ViewModel() {
private val _estado = MutableStateFlow<UiState<List<Usuario>>>(UiState.Carregando)
val estado: StateFlow<UiState<List<Usuario>>> = _estado.asStateFlow()
init {
carregarUsuarios()
}
fun carregarUsuarios() {
viewModelScope.launch {
_estado.value = UiState.Carregando
try {
val usuarios = repositorio.buscarTodos()
_estado.value = UiState.Sucesso(usuarios)
} catch (e: Exception) {
_estado.value = UiState.Erro(e.message ?: "Erro desconhecido")
}
}
}
fun buscarPorId(id: Int) {
viewModelScope.launch {
_estado.value = UiState.Carregando
try {
val usuario = repositorio.buscarPorId(id)
_estado.value = UiState.Sucesso(listOf(usuario))
} catch (e: Exception) {
_estado.value = UiState.Erro("Usuario nao encontrado")
}
}
}
}
ViewModel com Jetpack Compose
@Composable
fun TelaUsuarios(
viewModel: UsuarioViewModel = viewModel()
) {
val estado by viewModel.estado.collectAsStateWithLifecycle()
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
Text(
text = "Usuarios",
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.height(16.dp))
when (val uiState = estado) {
is UiState.Carregando -> {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
is UiState.Sucesso -> {
LazyColumn {
items(uiState.dados) { usuario ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = usuario.nome)
Text(
text = usuario.email,
style = MaterialTheme.typography.bodySmall
)
}
}
}
}
}
is UiState.Erro -> {
Text(
text = uiState.mensagem,
color = MaterialTheme.colorScheme.error
)
Button(onClick = { viewModel.carregarUsuarios() }) {
Text("Tentar novamente")
}
}
}
}
}
ViewModel com SavedStateHandle
O SavedStateHandle permite que dados do ViewModel sobrevivam até mesmo a morte do processo pelo sistema operacional:
class PesquisaViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val termoPesquisa: StateFlow<String> =
savedStateHandle.getStateFlow("termo_pesquisa", "")
private val _resultados = MutableStateFlow<List<String>>(emptyList())
val resultados: StateFlow<List<String>> = _resultados.asStateFlow()
fun pesquisar(termo: String) {
savedStateHandle["termo_pesquisa"] = termo
viewModelScope.launch {
_resultados.value = executarPesquisa(termo)
}
}
private suspend fun executarPesquisa(termo: String): List<String> {
// Simula chamada de rede
kotlinx.coroutines.delay(500)
return listOf("Resultado 1 para $termo", "Resultado 2 para $termo")
}
}
ViewModel com injeção de dependência (Hilt)
@HiltViewModel
class ProdutoViewModel @Inject constructor(
private val repositorio: ProdutoRepositorio,
private val analytics: AnalyticsService
) : ViewModel() {
private val _produtos = MutableStateFlow<List<Produto>>(emptyList())
val produtos: StateFlow<List<Produto>> = _produtos.asStateFlow()
private val _carrinho = MutableStateFlow<List<Produto>>(emptyList())
val carrinho: StateFlow<List<Produto>> = _carrinho.asStateFlow()
init {
carregarProdutos()
}
private fun carregarProdutos() {
viewModelScope.launch {
_produtos.value = repositorio.listarTodos()
analytics.registrarEvento("produtos_carregados")
}
}
fun adicionarAoCarrinho(produto: Produto) {
_carrinho.value = _carrinho.value + produto
analytics.registrarEvento("produto_adicionado_carrinho")
}
fun removerDoCarrinho(produto: Produto) {
_carrinho.value = _carrinho.value - produto
}
}
Quando usar ViewModel
- Qualquer tela que exibe dados dinâmicos como listas de API, formularios e dados de banco local.
- Operações assíncronas que não devem ser canceladas ao rotacionar a tela, como chamadas de rede ou consultas a banco de dados.
- Compartilhamento de estado entre Fragments usando um ViewModel com escopo da Activity.
- Gerenciamento de estado de UI mantendo o estado de selecoes, filtros, paginação e navegação.
- separação de responsabilidades retirando lógica de negócio da Activity/Fragment.
Casos de Uso no Mundo Real
Telas de listagem com paginação e filtros: aplicativos de e-commerce e redes sociais usam ViewModels para gerenciar o estado de listas paginadas, filtros ativos e termos de busca. O ViewModel mantém a posicao da lista e os filtros aplicados mesmo quando o usuário rotaciona a tela, evitando recarregamento desnecessário.
Formularios multi-etapas: aplicativos bancarios e de cadastro usam ViewModels para armazenar dados preenchidos em formularios de várias etapas. Se o usuário navega entre etapas ou rotaciona a tela, os dados já preenchidos sao preservados no ViewModel sem necessidade de persistencia em disco.
Compartilhamento de estado entre Fragments: telas com abas (tabs) ou paineis laterais que precisam compartilhar dados usam um ViewModel com escopo da Activity. Quando o usuário seleciona um item em um Fragment, o outro Fragment pode observar a mudanca via o ViewModel compartilhado.
Controle de operações assíncronas em telas de detalhe: telas que exibem detalhes de um item e permitem acoes como favoritar, compartilhar ou excluir usam o ViewModel para coordenar essas operações. O
viewModelScopegarante que chamadas de rede em andamento não sejam canceladas por mudancas de configuração, mas sejam canceladas quando o usuário sai da tela.
Boas Praticas
- Nunca passe
Context,Activity,FragmentouViewpara o ViewModel. O ViewModel sobrevive a Activity e manter essas referências causa vazamento de memória. Se precisar de contexto, useAndroidViewModelcomApplication. - Exponha dados usando
StateFlowimutavel (nãoMutableStateFlow). A Activity ou Fragment deve apenas observar, nunca modificar diretamente o estado. Use o padrão de backing property:private val _estado(mutavel) eval estado(somente leitura). - Use
sealed classousealed interfacepara modelar os estados da UI (Carregando,Sucesso,Erro). Isso torna o gerenciamento de estado explicito e o compilador garante que todos os estados sejam tratados nowhen. - Colete flows respeitando o ciclo de vida usando
collectAsStateWithLifecycle()no Compose ourepeatOnLifecycleem Activities/Fragments. Coletar sem respeitar o ciclo de vida pode causar atualizações de UI com a Activity destruida. - Injete dependências no ViewModel através do construtor usando Hilt ou outra biblioteca de DI. Isso torna o ViewModel testavel, pois permite substituir dependências reais por mocks nos testes unitarios.
Perguntas Frequentes
P: O ViewModel sobrevive a rotação de tela. Ele também sobrevive a morte do processo?
R: Nao. O ViewModel sobrevive apenas a mudancas de configuração (rotação, mudanca de idioma). Quando o sistema mata o processo para liberar memória, o ViewModel e destruído. Para persistir dados além da morte do processo, use SavedStateHandle, que armazena dados no Bundle do sistema e os restaura automaticamente.
P: Posso compartilhar um ViewModel entre Activities diferentes?
R: Nao diretamente. O ViewModel e vinculado ao ciclo de vida de uma Activity ou Fragment. Para compartilhar entre Fragments, use um ViewModel com escopo da Activity (activityViewModels()). Para compartilhar dados entre Activities, use mecanismos como repositórios, banco de dados ou SharedPreferences.
P: Qual a diferenca entre LiveData e StateFlow no ViewModel?
R: Ambos servem para expor dados reativos, mas StateFlow e parte do Kotlin Coroutines e não depende do Android. StateFlow sempre tem um valor inicial, e thread-safe por padrão e se integra melhor com Compose via collectAsStateWithLifecycle(). LiveData e lifecycle-aware nativamente mas esta sendo gradualmente substituido por StateFlow nas recomendacoes oficiais do Google.
P: Devo colocar lógica de negócio no ViewModel? R: O ViewModel deve conter lógica de apresentação (como formatar dados para a UI, gerenciar estado de tela), mas lógica de negócio pura deve ficar em camadas inferiores como use cases ou repositórios. Isso segue o princípio de separação de responsabilidades e facilita a reutilização e testabilidade da lógica de negócio.
Erros comuns
Passar Context ou View para o ViewModel
// ERRADO: pode causar vazamento de memoria
class MinhaViewModel(
private val context: Context // NUNCA faca isso
) : ViewModel()
// CORRETO: use AndroidViewModel se precisar de Application context
class MinhaViewModel(
application: Application
) : AndroidViewModel(application) {
val context: Context get() = getApplication()
}
O ViewModel sobrevive a Activity. Se ele mantiver uma referência ao Context da Activity, essa Activity não sera coletada pelo garbage collector, causando vazamento de memória.
Observar dados fora do ciclo de vida
// ERRADO: collect sem respeitar ciclo de vida
viewModel.estado.collect { /* pode atualizar UI com Activity destruida */ }
// CORRETO: usar collectAsStateWithLifecycle no Compose
val estado by viewModel.estado.collectAsStateWithLifecycle()
// CORRETO: usar repeatOnLifecycle em Activities
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.estado.collect { estado ->
atualizarUI(estado)
}
}
}
Criar ViewModel manualmente
// ERRADO: perde a associacao com o ciclo de vida
val viewModel = MinhaViewModel()
// CORRETO: usar delegate ou ViewModelProvider
val viewModel: MinhaViewModel by viewModels()
Termos relacionados
- LiveData: classe observavel que respeita o ciclo de vida, alternativa mais antiga ao StateFlow para exposicao de dados no ViewModel.
- StateFlow: fluxo de estado do Kotlin Coroutines, recomendado atualmente para expor dados reativos no ViewModel.
- viewModelScope: CoroutineScope vinculado ao ciclo de vida do ViewModel, cancelado automaticamente em onCleared.
- SavedStateHandle: mecanismo para persistir estado do ViewModel entre reconstrucoes do processo.
- Hilt: biblioteca de injeção de dependência do Android que simplifica a criação de ViewModels com dependências.
- Repository pattern: padrão arquitetural que abstrai fontes de dados, frequentemente usado em conjunto com ViewModels.
Conclusão
O ViewModel e a peça central da arquitetura moderna de aplicativos Android com Kotlin. Ele resolve problemas fundamentais como perda de estado ao rotacionar a tela, vazamento de memória e acoplamento entre lógica de negócio e UI. Combinado com StateFlow, Compose e injeção de dependência, o ViewModel permite construir aplicativos reativos, testáveis e manteniveis. Dominar esse componente e essencial para qualquer desenvolvedor Android.