Model-View-Intent Android
What’s MVI?
Wiki topics:
📱 · Mobile Development
Model-View-Intent Android

What’s MVI?
MVI stands for Model-View-Intent. It’s an architectural pattern that focuses on a unidirectional data flow. Think of it as a very organized way to manage how your app’s data changes and how those changes are reflected in the user interface.
Here’s a breakdown of the core components:
Model:
- This holds the application’s state. It’s the “single source of truth.”
- Crucially, the Model is immutable. This means that when the state changes, you don’t modify the existing Model; you create a new one. This immutability helps prevent unpredictable side effects.
View:
- This is your UI (activities, fragments, Composables).
- The View’s job is to display the state of the Model. It’s “dumb,” meaning it doesn’t handle business logic. It simply renders what it’s given.
Intent:
- This represents the user’s actions. It’s not the same as Android’s traditional
Intent. - Think of Intents as events: “button clicked,” “text changed,” etc.
- Intents are passed to the Model, which then processes them and produces a new state.
The Unidirectional Data Flow
The key to MVI is this flow:
- User Action: The user interacts with the View, generating an Intent.
- Intent to Model: The View sends the Intent to the Model.
- Model Updates State: The Model processes the Intent and creates a new state.
- State to View: The Model returns the new state to the View.
- View Renders: The View updates the UI based on the new state.
Why Use MVI?
- Predictability: The unidirectional flow makes it easy to understand how data changes.
- Testability: Testing becomes much more straightforward because the state is immutable and the flow is clear.
- Maintainability: The separation of concerns makes your code more organized and easier to maintain.
A Simplified Example (Conceptual)
Let’s imagine a simple counter app:
Model:
// CounterViewModel.kt
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class CounterViewModel : ViewModel() {
private val _state = MutableStateFlow(CounterState())
val state: StateFlow<CounterState> = _state.asStateFlow()
fun processIntent(intent: CounterIntent) {
when (intent) {
CounterIntent.Increment -> performAction(CounterAction.IncrementCounter)
}
}
private fun performAction(action: CounterAction) {
when (action) {
CounterAction.IncrementCounter -> {
val currentState = _state.value
_state.value = currentState.copy(count = currentState.count + 1)
}
}
}
}
View:
- Displays the
countfromCounterState. - Has an “increment” button.
// MainActivity.kt
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
class MainActivity : ComponentActivity() {
private val viewModel: CounterViewModel by viewModels<CounterViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface(modifier = Modifier.fillMaxSize()) {
CounterScreen(viewModel)
}
}
}
}
}
@Composable
fun CounterScreen(viewModel: CounterViewModel) {
val state by viewModel.state.collectAsState()
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Count: ${state.count}")
Button(onClick = { viewModel.processIntent(CounterIntent.Increment) }) {
Text(text = "Increment")
}
}
}
Intent:
// CounterState.kt
data class CounterState(val count: Int = 0)
// CounterIntent.kt
sealed class CounterIntent {
object Increment : CounterIntent()
}
// CounterAction.kt
sealed class CounterAction {
object IncrementCounter : CounterAction()
}
Here’s how it would work:
- The user taps the “increment” button.
- The View sends
IncrementIntentto the Model. - The Model creates a new one
CounterStatewithcountincrements. - The Model sends the new
CounterStateto the View. - The View updates the displayed count.
Key Kotlin Technologies Often Used with MVI
- Kotlin Flows: Flows are excellent for handling asynchronous data streams, making them perfect for emitting state updates.
- Sealed Classes: Sealed classes are great for representing the various states of your UI.
- Data Classes: Data classes make it easy to create immutable state objects.
Important Notes
- MVI can add some boilerplate code, but the benefits in terms of maintainability and testability are often worth it.
- MVI is often used with ViewModel to help the view survive configuration changes.
I hope this helps to give you a solid overview of the MVI pattern.
메타데이터
- post_id
- deffb3faed63
- slug
- model-view-intent-android-deffb3faed63
- url
- https://medium.com/@kumar-dev/model-view-intent-android-deffb3faed63
- canonical_url
- https://medium.com/@kumar-dev/model-view-intent-android-deffb3faed63
- author_url
- https://medium.com/@kumar-dev
- status
- ok
- fetched_at
- 2026-06-09 15:37:30