Skip to content
All case studies

Case study · Agribusiness

From legacy MVP to MVVM and Clean Architecture

Migrating legacy MVP modules of agribusiness apps to MVVM and Clean Architecture, and automating delivery with CI/CD.

Role
Android Engineer I → Android Engineer II
Company
Terras App Solutions
Period
Apr 2021 – Sep 2023

Context

Terras App Solutions builds native mobile apps for the agribusiness sector. I joined in April 2021 as Android Engineer I, working on Kotlin and Java apps and integrating REST APIs and local Room databases for offline data persistence. In September 2021 I was promoted to Android Engineer II, and I stayed until September 2023.

Challenge

Part of the codebase still followed MVP. In that pattern, presenters hold direct references to their views and tend to mix business rules with UI logic, so changes ripple across layers and are harder to cover with unit tests. Deployments were also a manual process.

The challenge was to modernize that foundation incrementally, module by module, while the apps kept evolving.

Approach

Migrate module by module. Instead of a big-bang rewrite, I migrated legacy MVP modules to MVVM and Clean Architecture one at a time, so each step could be reviewed, tested and released on its own.

State instead of callbacks. Presenters that pushed data into views became ViewModels that expose state. The UI observes it and survives configuration changes without manual lifecycle handling.

// 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,
        )
}

Clear layers. Use cases hold the business rules, and repositories became the single source of truth, combining REST APIs through Retrofit with Room for offline persistence. Dependency injection with Dagger and Hilt made every layer replaceable in tests.

Automated delivery. I set up CI/CD workflows with GitLab and GitHub Actions to build, test and deploy the apps automatically, replacing the manual release steps.

Quality as a habit. Code reviews and unit tests were part of the team’s routine, and the new structure made both easier.

Architecture

before · MVPActivity / Fragmentrenders and forwards eventsPresenterlogic + view callbacksModelAPI · local databasemigrationafter · MVVM + Clean ArchitectureUI · Fragment / Composeobserves stateViewModelStateFlow · viewModelScopeUse casesbusiness rulesRepositorysingle source of truthRemoteRetrofitLocalRoom · offlineCI/CD · GitLab CI + GitHub Actionsbuild · tests · deploy
Simplified. Each legacy module was migrated to the same layered structure.

Results

  • Legacy modules migrated to MVVM and Clean Architecture, with clear boundaries between UI, domain and data, and better long-term maintainability.
  • Offline-ready data with Room behind repositories.
  • Automated deployments with GitLab and GitHub Actions instead of manual release steps.
  • A promotion from Android Engineer I to Android Engineer II.

Stack

The tools behind this work, all used in production:

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

Details are simplified and client names are omitted to respect confidentiality.

Next case study

Real-time delivery tracking for drivers

T10

Want to build something like this?

I’m open to senior Android roles at international companies.