Skip to main content

MVC, MVP, MVVM, and MVI compared

Pattern labels vary between teams. In interviews and design reviews, explain ownership and arrows rather than relying on the acronym.

Comparison​

PatternUI talks toUI receivesTypical strengthTypical failure
MVCcontroller/modelmodel + controller updatessimple separationActivity becomes controller + view
MVPpresenterimperative view callsunit-testable presenterhuge view interface, lifecycle attachment
MVVMViewModelobservable UI statelifecycle fit and declarative bindinggod ViewModel, duplicated events/state
MVI/reducer UDFintent dispatcher/reducerimmutable stateexplicit transitions and replayabilityceremony, giant state/reducer

MVP example​

interface LoginView {
fun showLoading(show: Boolean)
fun showError(message: UiText)
fun openHome()
}

Presenter must attach/detach safely; async callbacks after detach are a classic bug. It can suit legacy Views but imperative calls become awkward with recreation and multiple outputs.

MVVM/UDF example​

data class LoginUiState(
val email: String = "",
val submitting: Boolean = false,
val error: LoginError? = null,
)

sealed interface LoginAction {
data class EmailChanged(val value: String) : LoginAction
data object Submit : LoginAction
}

ViewModel accepts actions/method calls and exposes StateFlow<LoginUiState>. Compose renders state. This is MVVM in framework role and UDF in data direction; labels can overlap.

MVI reducer boundary​

fun reduce(state: CartState, result: CartResult): CartState = when (result) {
is CartResult.QuantityAccepted -> state.copy(lines = result.lines, error = null)
is CartResult.Failed -> state.copy(error = result.reason)
CartResult.Submitting -> state.copy(submitting = true)
}

Pure reducers make transitions testable. Effects (network/database/navigation) remain outside and feed results back. If reducer starts coroutines, purity and replayability disappear.

Avoid the god ViewModel​

Warning signs:

  • owns unrelated tabs/features
  • injects 15 repositories
  • exposes dozens of mutable flows
  • formats Android resources and controls navigation details
  • contains reusable business policy
  • every test needs enormous fixture setup

Split by screen/feature ownership, move reusable business rules to data/domain, keep complex UI mechanics in a UI-scoped state holder, and expose cohesive immutable state.

Migration paths​

  • MVC Activity: first extract repository/data ownership, then state holder.
  • MVP Views: replace imperative render calls with one state model incrementally.
  • MVVM with many LiveData fields: combine into cohesive UI state to prevent impossible combinations.
  • MVI ceremony: keep UDF but use named ViewModel methods where a universal intent bus adds no value.
Decision rule

Choose the simplest state ownership and event flow that makes concurrency, lifecycle, and business rules explicit. A team-wide fashionable acronym is not evidence.

Exercises​

  1. Draw arrows and owners for the same login screen in MVP and MVVM.
  2. Migrate three independent loading/error LiveData values into valid UI states.
  3. Decide whether a settings toggle needs a reducer-based MVI pipeline.