Pular para o conteúdo
Todos os case studies

Case study · Agronegócio

Do MVP legado para MVVM e Clean Architecture

Migração de módulos legados em MVP de apps do agronegócio para MVVM e Clean Architecture, com entrega automatizada por CI/CD.

Cargo
Engenheiro Android I → Engenheiro Android II
Empresa
Terras App Solutions
Período
abr 2021 – set 2023

Contexto

A Terras App Solutions desenvolve apps mobile nativos para o agronegócio. Entrei em abril de 2021 como Engenheiro Android I, trabalhando em apps Kotlin e Java e integrando APIs REST e bancos locais com Room para persistência offline. Em setembro de 2021 fui promovido a Engenheiro Android II e fiquei até setembro de 2023.

Desafio

Parte do código ainda seguia MVP. Nesse padrão, os presenters guardam referências diretas às views e tendem a misturar regras de negócio com lógica de UI, então as mudanças se espalham pelas camadas e ficam mais difíceis de cobrir com testes unitários. Os deploys também eram um processo manual.

O desafio era modernizar essa base aos poucos, módulo por módulo, enquanto os apps continuavam evoluindo.

Abordagem

Migração módulo por módulo. Em vez de reescrever tudo de uma vez, migrei os módulos legados em MVP para MVVM e Clean Architecture um de cada vez, para que cada passo pudesse ser revisado, testado e publicado separadamente.

Estado no lugar de callbacks. Presenters que empurravam dados para as views viraram ViewModels que expõem estado. A UI observa esse estado e sobrevive a mudanças de configuração sem controle manual de ciclo de vida.

// Before: the presenter drives the view directly
class FieldsPresenter(
    private val view: FieldsView,
    private val api: FieldsApi,
) {
    fun load() {
        view.showLoading()
        api.getFields(
            onSuccess = { fields -> view.showFields(fields) },
            onError = { view.showError() },
        )
    }
}

// After: the ViewModel exposes state and the UI only renders it
@HiltViewModel
class FieldsViewModel @Inject constructor(
    getFields: GetFieldsUseCase,
) : ViewModel() {
    val uiState: StateFlow<FieldsUiState> = getFields()
        .map<List<Field>, FieldsUiState> { FieldsUiState.Content(it) }
        .catch { emit(FieldsUiState.Error) }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = FieldsUiState.Loading,
        )
}

Camadas claras. Os use cases concentram as regras de negócio, e os repositórios viraram a fonte única da verdade, combinando APIs REST via Retrofit com o Room para persistência offline. A injeção de dependência com Dagger e Hilt deixou cada camada substituível nos testes.

Entrega automatizada. Criei workflows de CI/CD com GitLab e GitHub Actions para fazer o build, os testes e o deploy dos apps automaticamente, substituindo as etapas manuais de release.

Qualidade como hábito. Code reviews e testes unitários faziam parte da rotina do time, e a nova estrutura facilitou os dois.

Arquitetura

antes · MVPActivity / Fragmentrenderiza e repassa eventosPresenterlógica + callbacks da viewModelAPI · banco localmigraçãodepois · MVVM + Clean ArchitectureUI · Fragment / Composeobserva o estadoViewModelStateFlow · viewModelScopeUse casesregras de negócioRepositoryfonte única da verdadeRemoteRetrofitLocalRoom · offlineCI/CD · GitLab CI + GitHub Actionsbuild · testes · deploy
Simplificado. Cada módulo legado foi migrado para a mesma estrutura em camadas.

Resultados

  • Módulos legados migrados para MVVM e Clean Architecture, com fronteiras claras entre UI, domínio e dados, e uma base mais fácil de manter a longo prazo.
  • Dados prontos para uso offline, com o Room por trás dos repositórios.
  • Deploys automatizados com GitLab e GitHub Actions no lugar de etapas manuais de release.
  • Promoção de Engenheiro Android I para Engenheiro Android II.

Stack

As ferramentas por trás desse trabalho, todas usadas em produção:

  • Java
  • Kotlin
  • MVVM
  • Clean Architecture
  • Room
  • Dagger/Hilt
  • GitLab CI
  • GitHub Actions

Os detalhes foram simplificados e os nomes de clientes omitidos para respeitar a confidencialidade.

Próximo case study

Rastreamento de entregas em tempo real

T10

Quer construir algo assim?

Estou aberto a vagas de Android sênior em empresas internacionais.