Chanzmao ʕ•ᴥ•ʔ Bear Blog

MVVM (Google Recommended) vs MVI

1. Input Processing (Events)

MVVM (Direct Method Calls)

UI triggers dedicated ViewModel methods directly.

Button(onClick = { viewModel.onSubmitClicked() })

MVI (Single Entry Point)

All UI actions are wrapped into a single Intent sealed interface and sent to one handler.

Button(onClick = { viewModel.onIntent(UserIntent.Submit) })

2. State Reduction (State Updates)

MVVM (Direct State Mutation)

State is updated directly inside asynchronous calls via _uiState.update { ... }.

fun loadData() {
    viewModelScope.launch {
        val result = repository.getData()
        _uiState.update { it.copy(data = result) }
    }
}

MVI (Pure Reducer Function)

The next state is calculated via a pure reduce function based on the current state and the received action.

fun reduce(oldState: UiState, action: Action): UiState = when (action) {
    is Action.DataLoaded -> oldState.copy(data = action.data)
    is Action.Loading -> oldState.copy(isLoading = true)
}

3. Output Processing (UI Updates & Side Effects)

MVVM (State-Driven Single Flow)

Both persistent state and one-off events are encapsulated inside a single UiState. Single-shot events are consumed and cleared by the UI.

val uiState: StateFlow<UiState> = _uiState.asStateFlow()
val state by viewModel.uiState.collectAsStateWithLifecycle()

LaunchedEffect(state.userMessage) {
    state.userMessage?.let {
        snackbarHostState.showSnackbar(it)
        viewModel.onMessageShown() // Consume event
    }
}

MVI (Separated Side Effects Flow)

State rendering and single-shot events are decoupled. Side effects stream through a Channel or SharedFlow.

private val _effect = Channel<UiEffect>()
val effect = _effect.receiveAsFlow()
val state by viewModel.state.collectAsStateWithLifecycle()

LaunchedEffect(Unit) {
    viewModel.effect.collect { effect ->
        when (effect) {
            is UiEffect.ShowSnackbar -> snackbarHostState.showSnackbar(effect.message)
        }
    }
}

4. Architecture Selection Guide

Criteria Google Recommended MVVM Pure MVI
Primary Goal Pragmatic developer velocity Strict state predictability
Boilerplate Low to Moderate High (Sealed classes for Intent, Action, Effect)
State Mutation Scattered across ViewModel methods Centralized inside Reducer
Ideal Team Size Any team size Mid to Large teams requiring strict patterns
Debugging Standard break-pointing Easy event logging & state snapshot inspection

#Android #Flow #Jetpack Compose