O que e ViewModel no Android?
ViewModel e um componente da biblioteca Android Jetpack projetado para armazenar e gerenciar dados relacionados a interface de usuario 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 recriacao e mantem os dados intactos, evitando carregamentos desnecessarios.
O ViewModel separa a logica de apresentacao da camada de UI. A Activity ou Fragment cuida apenas de exibir dados e capturar interacoes do usuario, enquanto o ViewModel gerencia o estado, executa operacoes assincronas e expoe dados reativos.
Configuracao
Adicione as dependencias 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 basica
Um ViewModel basico 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, voce obtem 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 configuracao (rotacao de tela, mudanca de idioma). Ele so e destruido quando a Activity e finalizada definitivamente (o usuario 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 metodo onCleared() e chamado quando o ViewModel e destruido definitivamente, permitindo limpar recursos:
class MinhaViewModel : ViewModel() {
private val conexao = abrirConexao()
override fun onCleared() {
super.onCleared()
conexao.fechar()
println("ViewModel destruido, recursos liberados")
}
}
Exemplos praticos
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 ate 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 injecao de dependencia (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 dinamicos como listas de API, formularios e dados de banco local.
- Operacoes assincronas que nao 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, paginacao e navegacao.
- Separacao de responsabilidades retirando logica de negocio da Activity/Fragment.
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 referencia ao Context da Activity, essa Activity nao sera coletada pelo garbage collector, causando vazamento de memoria.
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 injecao de dependencia do Android que simplifica a criacao de ViewModels com dependencias.
- Repository pattern: padrao arquitetural que abstrai fontes de dados, frequentemente usado em conjunto com ViewModels.
Conclusao
O ViewModel e a peca central da arquitetura moderna de aplicativos Android com Kotlin. Ele resolve problemas fundamentais como perda de estado ao rotacionar a tela, vazamento de memoria e acoplamento entre logica de negocio e UI. Combinado com StateFlow, Compose e injecao de dependencia, o ViewModel permite construir aplicativos reativos, testaveis e manteniveis. Dominar esse componente e essencial para qualquer desenvolvedor Android.