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.