Mobile Development 11 min read

Understanding Android Architecture: From MVC/MVP/MVVM to MVI

This article reviews classic Android architectural patterns—MVC, MVP, and MVVM—highlights their limitations, introduces the MVI pattern as a more streamlined alternative, and provides a detailed Kotlin implementation with ViewState, ViewEvent, and unidirectional data flow examples.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Understanding Android Architecture: From MVC/MVP/MVVM to MVI

Introduction

Android development has matured and several architectural patterns such as MVC, MVP, and MVVM are widely used. While MVVM is officially recommended, it still has drawbacks that can be mitigated by the MVI pattern.

Classic Architecture Overview

MVC

MVC separates the view, controller, and model, but on Android the Activity often plays both view and controller roles, violating the single‑responsibility principle and causing tight coupling between view and model.

MVP

MVP introduces a Presenter that handles business logic, allowing the view to interact only with the presenter and reducing coupling. However, the presenter holds a reference to the view and the view interface can become large as the page grows.

MVVM

MVVM replaces the presenter with a ViewModel and uses two‑way data binding so that UI updates automatically when the model changes. Many developers dislike data binding, and MVVM still requires duplicated mutable/immutable LiveData declarations and scattered view‑model interactions.

What Is MVI?

MVI (Model‑View‑Intent) borrows ideas from front‑end frameworks and emphasizes a single source of truth and unidirectional data flow. Its components are:

Model – represents UI state rather than domain data.

View – subscribes to state changes and renders UI.

Intent – user actions wrapped as intents sent to the model.

The data flow proceeds as: Intent → Model updates State → View renders the new State.

Practical MVI Implementation (Kotlin)

Below is a concise Kotlin example that demonstrates the core pieces.

ViewState and ViewEvent

data class MainViewState(val fetchStatus: FetchStatus, val newsList: List
)

sealed class MainViewEvent {
    data class ShowSnackbar(val message: String) : MainViewEvent()
    data class ShowToast(val message: String) : MainViewEvent()
}

ViewState Updates in ViewModel

class MainViewModel : ViewModel() {
    private val _viewStates = MutableLiveData
()
    val viewStates = _viewStates.asLiveData()
    private val _viewEvents = SingleLiveEvent
()
    val viewEvents = _viewEvents.asLiveData()

    init {
        emit(MainViewState(fetchStatus = FetchStatus.NotFetched, newsList = emptyList()))
    }

    private fun fabClicked() {
        count++
        emit(MainViewEvent.ShowToast(message = "Fab clicked count $count"))
    }

    private fun emit(state: MainViewState?) { _viewStates.value = state }
    private fun emit(event: MainViewEvent?) { _viewEvents.value = event }
}

Observing ViewState in the View

private fun initViewModel() {
    viewModel.viewStates.observe(this) { renderViewState(it) }
    viewModel.viewEvents.observe(this) { renderViewEvent(it) }
}

Dispatching Actions from the View

class MainActivity : AppCompatActivity() {
    private fun initView() {
        fabStar.setOnClickListener { viewModel.dispatch(MainViewAction.FabClicked) }
    }
}

class MainViewModel : ViewModel() {
    fun dispatch(action: MainViewAction) = reduce(viewStates.value, action)
    private fun reduce(state: MainViewState?, viewAction: MainViewAction) {
        when (viewAction) {
            is MainViewAction.NewsItemClicked -> newsItemClicked(viewAction.newsItem)
            MainViewAction.FabClicked -> fabClicked()
            MainViewAction.OnSwipeRefresh -> fetchNews(state)
            MainViewAction.FetchNews -> fetchNews(state)
        }
    }
}

This structure centralises state management, reduces boilerplate, and makes the flow of user actions explicit.

Conclusion

MVI offers a clear unidirectional data flow, easier state tracing, and less repetitive code compared with MVVM, especially when data binding is avoided. Its drawbacks include potentially large immutable state objects and increased memory usage due to state copying. Developers should choose the pattern that best fits their project requirements.

Mobile DevelopmentarchitectureandroidKotlinMVVMMVI
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.