Skip to content
ArceApps Logo ArceApps
ES

MVVM Architecture in Android: The Comprehensive Guide (2025)

⏱️ 4 min read
MVVM Architecture in Android: The Comprehensive Guide (2025)

🏛️ Theory: The Evolution of UI Architectures

Before understanding MVVM (Model-View-ViewModel), we must understand the problem it tries to solve: the coupling between presentation logic and the graphical interface.

Brief History

  1. MVC (Model-View-Controller): Born in the 70s for desktop interfaces. In Android, Activities used to act as “View” and “Controller” at the same time, creating “God Objects” impossible to test.
  2. MVP (Model-View-Presenter): Popular in 2015. The Presenter had no references to Android (good for tests), but had to update the View manually (view.showLoading(), view.hideLoading()). Lots of boilerplate.
  3. MVVM (Model-View-ViewModel): Created by Microsoft for WPF in 2005 and adopted by Google in 2017. The key is Data Binding and Observability.

The Observer Pattern in MVVM

The fundamental difference of MVVM is that the ViewModel does not know the View. There is no view.update(). Instead, the ViewModel exposes an observable State (StateFlow or LiveData). The View subscribes to this state and “reacts”.

ViewModel: “The state has changed to ‘Loading’”. View: (Listens to the change) -> Shows the ProgressBar.

This inverts the dependency and totally decouples UI logic.

🎯 The Layers of MVVM: Deep Anatomy

1. MODEL (The Truth)

It is agnostic to the UI. Contains business logic and data. In Clean Architecture, this is subdivided into:

  • Data Source: API, DB.
  • Repository: Single source of truth.
  • Use Cases: Business rules.

2. VIEW (The Painter)

It is dumb. It only knows how to paint pixels and capture touches.

  • In XML: Activities/Fragments.
  • In Compose: @Composable functions.
  • Responsibility: Observe the ViewModel and render itself. Never makes logical decisions.

3. VIEWMODEL (The Orchestrator)

It is the bridge. Transforms Model data into “UI State”.

  • Survives configuration changes (rotation).
  • Has no references to Views or Contexts (to avoid Memory Leaks).
  • Exposes data streams.

🎮 Implementing MVVM: Example with Minesweeper

To demonstrate MVVM in action, we will build a Minesweeper game.

1. MODEL: Pure Logic

// MinesweeperCell.kt - Immutable Entity
data class MinesweeperCell(
    val row: Int,
    val col: Int,
    val isMine: Boolean = false,
    val isRevealed: Boolean = false,
    val isFlagged: Boolean = false
)

// Use Case: Complex logic of revealing cells (Flood Fill)
class RevealCellUseCase @Inject constructor() {
    operator fun invoke(board: List<List<Cell>>, row: Int, col: Int): Board {
        // Pure recursive algorithm, easy to test
        // Knows nothing about Android, nor ViewModels.
    }
}

2. VIEWMODEL: State Management

// UI State: Represents the full screen at an instant in time
data class GameUiState(
    val board: List<List<MinesweeperCell>> = emptyList(),
    val gameState: GameStatus = GameStatus.PLAYING,
    val isLoading: Boolean = false
)

@HiltViewModel
class MinesweeperViewModel @Inject constructor(
    private val revealCellUseCase: RevealCellUseCase,
    private val repository: GameRepository
) : ViewModel() {

    // Backing property to encapsulate mutable state
    private val _uiState = MutableStateFlow(GameUiState())
    val uiState: StateFlow<GameUiState> = _uiState.asStateFlow()

    fun onCellClick(row: Int, col: Int) {
        // Launch coroutine in ViewModel scope
        viewModelScope.launch {
            val currentBoard = _uiState.value.board
            val newBoard = revealCellUseCase(currentBoard, row, col)

            // Update state immutably
            _uiState.update { currentState ->
                currentState.copy(board = newBoard)
            }

            checkWinCondition()
        }
    }
}

3. VIEW (Compose): Reaction to State

@Composable
fun MinesweeperScreen(
    viewModel: MinesweeperViewModel = hiltViewModel()
) {
    // 1. Consume state safely with lifecycle
    val state by viewModel.uiState.collectAsStateWithLifecycle()

    // 2. Render based ONLY on state
    if (state.isLoading) {
        CircularProgressIndicator()
    } else {
        BoardGrid(
            board = state.board,
            // 3. Pass events upwards (Unidirectional Data Flow)
            onCellClick = { row, col -> viewModel.onCellClick(row, col) }
        )
    }

    // 4. Handle effects (One-off events) like Game Over Dialogs
    if (state.gameState == GameStatus.LOST) {
        GameOverDialog()
    }
}

🔄 The ViewModel Lifecycle

One of the biggest advantages of using AndroidX’s ViewModel class is its lifecycle integration.

  1. Creation: Created when the Fragment/Activity is first created.
  2. Retention: If you rotate the screen, the Activity is destroyed and recreated, but the ViewModel instance is kept in memory. This avoids having to reload network data.
  3. Cleanup: When you leave the screen (Back press), onCleared() is called, where all viewModelScope coroutines are automatically cancelled.

⚠️ Common Errors in MVVM

  1. Logic in UI: if (user.isAdmin) { showButton() } in the Fragment.
    • Solution: The ViewModel should expose val showAdminButton: Boolean.
  2. Exposing mutable objects: Exposing MutableStateFlow or MutableList.
    • Solution: Always expose read-only interfaces (StateFlow, List).
  3. Passing Context to ViewModel: ViewModel(context).
    • Risk: Massive Memory Leak.
    • Solution: Use AndroidViewModel(application) if strictly necessary (for Resources), but preferably inject a resource provider.

🎯 Conclusion

MVVM is not just a way to organize code; it is a defensive strategy. It defends your business logic from the chaos of the Android lifecycle. It defends your UI from the complexity of data. And above all, it makes your application robust, testable, and maintainable.

You might also be interested in

Clean Architecture: The Ultimate Guide for Modern Android
Architecture October 15, 2025

Clean Architecture: The Ultimate Guide for Modern Android

Demystifying Clean Architecture: A deep dive into layers, dependencies, and data flow to build indestructible Android apps.

Read more
Kotlin Coroutines: The Android Guide
Kotlin October 15, 2025

Kotlin Coroutines: The Android Guide

Mastering Kotlin Coroutines on Android. Dispatchers, structured concurrency, and best practices for asynchronous programming.

Read more
MVVM: The View Layer Guide
MVVM October 15, 2025

MVVM: The View Layer Guide

How to implement the View layer in MVVM with Jetpack Compose. State collection, error handling, and separation of concerns.

Read more