Unlocking Android MVI: How Reactive Architecture Beats MVC & MVVM
This article examines the origins, core principles, and practical implementation of the Model‑View‑Intent (MVI) pattern in Android, clarifies common misconceptions with Redux and MVVM, and provides Kotlin code examples and guidelines for building robust, unidirectional, immutable state architectures.
1. Introduction: Discarding Stereotypes
MVI is an indispensable architecture pattern in Android development, offering a clear and predictable data flow for stable UI construction. However, it is often mistakenly seen as a rigid, single‑purpose pattern akin to Redux, leading many teams to avoid it.
In reality, MVI has been flexibly adapted for Android, retaining its core ideas while improving usability.
2. Architectural Precursors: The Patterns that Gave Birth to MVI
MVI did not appear out of thin air; it evolved from earlier patterns such as MVC and reactive programming.
1979: Model‑View‑Controller (MVC)
Model : manages data and business logic
View : handles user input
Controller : processes input and drives model updates
May 2014: Flux
Actions : describe events
Dispatcher : dispatch actions to stores
Store : holds application state and responds to actions
Views : listen to store changes and trigger re‑rendering
December 2014: MVI (Reactive MVC)
André Staltz introduced the concept of “Reactive MVC”, borrowing Flux’s unidirectional data flow and simplifying implementation by removing the dispatcher. Intent directly drives the Model, connecting Model, user intent, state, and UI rendering into a pure reactive transformation.
3. The Origin of MVI: How the Pattern Formed
MVI was first proposed by André Staltz in 2014 for the Cycle.js framework and only later gained traction in Android after Redux’s popularity in 2015.
Practical Definition of MVI
Model‑View‑Intent enforces a single immutable state and a unidirectional event flow . Its core components are:
Model : holds the application state
View : renders the state
Intent : replaces the controller, representing user interaction as an event stream
Code Example
2015 blog post by Jannis Dornemann expressed MVI’s core idea with a mathematical formulation, which translates to the following Kotlin Flow pseudocode:
// Intent is a stream of user interaction events
val intents: Flow<Intent> = view.userInteractions()
// Model processes Intent and produces a state stream
val states: Flow<State> = intents.map { intent ->
model.reduce(intent)
}
// View collects the state stream and renders UI
states.collect { state ->
view.render(state)
}4. Misconceptions: What MVI Is Not
A common misconception is that MVI must follow a Redux‑style global store and reducer. In fact, Android implementations can use distributed state per module.
Misconception 1: MVI = Redux
MVI predates Redux; while it inspired Redux’s unidirectional flow, Redux is a centralized global state manager, whereas MVI typically uses distributed state.
Misconception 2: MVI = MVVM
Both use observable streams and immutable state, but MVVM’s view holds state and communicates bidirectionally with the ViewModel, while MVI enforces a strict Intent → Model → State → View flow.
5. How to Write a Proper MVI Implementation
The essential characteristics of a true MVI are:
Single immutable state model : centralized, immutable, usually wrapped in a ViewState.
State managed in the Model : ViewState is owned by the Model, not scattered across the view.
Intent as a unified abstraction of user actions .
Intent triggers state transitions via a reducer .
Unidirectional data flow : Intent → State → View, with no reverse dependencies.
MVI Data Models
UiState : immutable UI‑focused state data class.
Intent : sealed hierarchy representing UI actions.
SideEffect : one‑time events such as logging or navigation.
// UI State data class (immutable)
data class UserUiState(
val username: String = "",
val age: Int = 0,
val isLoading: Boolean = false
)
// User intents: user actions
sealed interface UserIntent {
data class ChangeUsername(val newName: String) : UserIntent
object IncrementAge : UserIntent
object DecrementAge : UserIntent
}
// Side effects (one‑time events)
sealed interface UserSideEffect {
data class ShowMessage(val message: String) : UserSideEffect
}ViewModel Responsibilities
In Android, a ViewModel should expose an immutable StateFlow of the UI state, process intents, and handle side effects.
class UserViewModel : ViewModel() {
private val _sideEffects = MutableSharedFlow<UserSideEffect>()
val sideEffects = _sideEffects.asSharedFlow()
private val _uiState = MutableStateFlow(UserUiState())
val uiState = _uiState.asStateFlow()
// Process a single intent immediately
fun processIntent(intent: UserIntent) {
when (intent) {
is UserIntent.ChangeUsername -> {
_uiState.update { it.copy(username = intent.newName) }
}
UserIntent.IncrementAge -> {
_uiState.update { it.copy(age = it.age + 1) }
viewModelScope.launch {
_sideEffects.emit(UserSideEffect.ShowMessage("Age increased!"))
}
}
UserIntent.DecrementAge -> {
if (current.age > 0) {
_uiState.update { current ->
current.copy(age = current.age - 1)
}
}
}
}
}
}6. Final Thoughts: Choosing the Right Architecture
Whether MVI is worth adopting depends on team size and project complexity; solo developers may find its constraints unnecessary, while teams benefit from its predictability.
Do not adopt MVI just for the sake of using it; first understand the problem you need to solve.
MVI challenges not only technical skills but also software‑engineering mindset, forcing clear thinking about state changes and event flow.
A global Redux‑style store is optional; small modules can implement lightweight MVI with a single ViewModel and local state.
Focus on the core principles—single immutable state, unidirectional flow, and intent‑driven updates—rather than strict adherence to a particular template.
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.