Pular para o conteúdo

Boas práticas e padrões · Arquitetura do código · 6 min de leitura

MVVM e MVI na interface

A tela só desenha um estado e envia ações; o ViewModel guarda o estado e aplica a lógica — fluxo de dados em uma só direção.

Tela → Ação1Ação → ViewModel2ViewModel → Repository3ViewModel → UiState4UiState → Tela5Tela — Compose / SwiftUITelaCompose / SwiftUIAção — Buscar("kotlin")AçãoBuscar("kotlin")ViewModel — reduz o estadoViewModelreduz o estadoUiState — imutávelUiStateimutávelRepositoryRepository

Arraste para o lado para ver o diagrama inteiro.

Os dados andam num círculo de mão única: ação → ViewModel → novo estado → tela. Nada atualiza a tela por fora.

MVVM

O ViewModel expõe o estado observável (StateFlow, @Observable) e funções para os eventos da tela. Ele sobrevive a rotação de tela e não conhece a View. É o padrão recomendado pelo Android e muito usado com SwiftUI.

MVI

Leva o MVVM ao extremo: um único estado imutável por tela e todas as interações viram ações (sealed interface) processadas por um reducer. Vantagem: estado previsível, fácil de testar e de reproduzir bugs. Custo: mais código para telas simples.

Regras práticas

  • Estado é imutável: copy() em vez de mutar.
  • Eventos únicos (navegar, mostrar snackbar) não são estado persistente — trate com cuidado.
  • ViewModel sem Context nem referências a Views.

No código

✓ ExemploKotlin
data class BuscaState(val termo: String = "", val carregando: Boolean = false, val itens: List<Repo> = emptyList(), val erro: String? = null)

sealed interface BuscaAcao {
  data class Digitou(val termo: String) : BuscaAcao
  data object Buscar : BuscaAcao
}

class BuscaViewModel(private val repo: RepoRepository) : ViewModel() {
  private val _state = MutableStateFlow(BuscaState())
  val state: StateFlow<BuscaState> = _state.asStateFlow()

  fun enviar(acao: BuscaAcao) = when (acao) {
    is BuscaAcao.Digitou -> _state.update { it.copy(termo = acao.termo) }
    BuscaAcao.Buscar -> viewModelScope.launch {
      _state.update { it.copy(carregando = true, erro = null) }
      runCatching { repo.buscar(_state.value.termo) }
        .onSuccess { itens -> _state.update { it.copy(carregando = false, itens = itens) } }
        .onFailure { e -> _state.update { it.copy(carregando = false, erro = e.message) } }
    }
  }
}

A tela só chama enviar(...) e desenha o state; testar é chamar ações e conferir o estado resultante.

Em resumo

Vantagens e custos

Vantagens
  • Estado único e reprodutível.
  • Testes do ViewModel sem UI.
  • Sobrevive a rotação e recriação.
Custos e riscos
  • Mais código em telas triviais.
  • Eventos únicos exigem cuidado extra.

Referências

Continue aprendendo

Todos os temas de boas práticas e padrões →