Understanding MVI Architecture in Android: Centralized State and Intent Management
This article explains Google's shift from MVVM to MVI for Android, detailing how the MVI pattern centralizes UI state in a sealed class, manages user intents with a single data flow, optionally uses a domain layer, and provides Kotlin code examples for implementation.
In November last year Google quietly replaced the recommended MVVM architecture on its official site with MVI, prompting this article to explore the MVI pattern for Android development.
The MVI pattern separates the UI layer, domain layer, and data layer (UDD), with the Intent representing user actions such as clicks or refreshes, and a single data flow where state moves down and events flow up.
State management is centralized by defining UI states in a sealed class (e.g., MainUiState) that includes loading, success, and error states. The ViewModel exposes a MutableSharedFlow<MainUiState> as a private mutable flow and a read‑only SharedFlow<MainUiState> for the UI to collect.
sealed class MainUiState {
object isLoading : MainUiState()
data class loadError(val error: Exception) : MainUiState()
data class loadSuccess(val reqData: ReqData) : MainUiState()
}The UI collects the state with lifecycleScope.launch { viewModel.state.collect { ... } } and can apply distinctUntilChanged() to avoid unnecessary redraws.
lifecycleScope.launch {
viewModel.state.distinctUntilChanged().collect { /* handle UI states */ }
}User intent management replaces direct ViewModel method calls with a sealed MainIntent class. The ViewModel holds a MutableSharedFlow<MainIntent> named userIntent , and a dispatch function emits intents.
sealed class MainIntent {
object refresh : MainIntent()
object loadData : MainIntent()
}In the ViewModel’s init block, intents are collected and mapped to corresponding private functions (e.g., refresh() , loadData() ).
init {
viewModelScope.launch {
userIntent.collect {
when (it) {
is MainIntent.refresh -> refresh()
is MainIntent.loadData -> loadData()
}
}
}
}Activities now simply dispatch intents:
binding.btnRefresh.setOnClickListener { viewModel.dispatch(MainIntent.refresh) }
binding.btnLoadData.setOnClickListener { viewModel.dispatch(MainIntent.loadData) }The optional domain layer sits between the UI and data layers, encapsulating reusable business logic and acting like a façade, reducing duplication across repositories.
Overall, the article demonstrates how MVI centralizes state and intent handling, improves consistency, and can be combined with Jetpack Compose for a cleaner UI implementation.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.