
Android の UI アーキテクチャでは、MVVM と MVI のどちらを選ぶべきかという議論がよくあります。
ただし、現在の Google の公式ガイドを見ると、重要なのは「MVVM か MVI か」という名前ではありません。
Google が推奨している中心的な考え方は、UDF(Unidirectional Data Flow), UI State・State Holder, ViewModel です。
その上で、MVI はより厳密に「すべての入力を Action として集約し、Reducer で State を生成する」方向へ進めたアーキテクチャとして考えることができます。
🧑🏻💻 1. Input Processing — Event の処理
MVVM: ViewModel のメソッドを直接呼び出す
UI から発生したイベントごとに、対応する ViewModel のメソッドを呼び出します。
Button(
onClick = { viewModel.onSubmitClicked() }
)
fun onSubmitClicked() {
viewModelScope.launch {
...
}
}
このスタイルでは、UI イベントと ViewModel の API が直接対応します。
Google の Compose アーキテクチャガイドでも、UI イベントを ViewModel の onSignIn() のようなメソッドで処理する例が示されています。
MVI: Single Entry Point
MVI では、UI からの入力を Intent や Action に変換し、1つの入口に集約することがあります。
Button(
onClick = { viewModel.onIntent(UserIntent.Submit) }
)
sealed interface UserIntent {
data object Submit : UserIntent
data object Refresh : UserIntent
}
fun onIntent(intent: UserIntent) {
...
}
これにより、UI → ViewModel の入力経路を1本化できます。
すべての入力を同じ形式で扱えるため、ログやテスト、イベント追跡を統一しやすくなります。
🧑🏻💻 2. State Reduction — State の更新
MVVM: ViewModel の処理から State を更新する
MVVM では、ViewModel の各メソッドが非同期処理を行い、その結果を UiState に反映する構成が一般的です。
fun loadData() {
viewModelScope.launch {
val result = repository.getData()
_uiState.update {
it.copy(data = result)
}
}
}
この場合、State を変更する場所は ViewModel 内の複数のメソッドに分散する可能性があります。
その代わり、コードが比較的シンプルで、処理の流れを直接追いやすいというメリットがあります。
MVI: Reducer で State を計算する
MVI では、現在の State と Action から次の State を計算する reduce を中心に据えることができます。
fun reduce(
oldState: UiState,
action: Action
): UiState =
when (action) {
is Action.DataLoaded ->
oldState.copy(data = action.data)
is Action.Loading ->
oldState.copy(isLoading = true)
}
ここで重要なのは、
Current State + Action
↓
Reducer
↓
Next State
という形にすることです。
Reducer を純粋関数として設計すれば、同じ State と Action から常に同じ State を生成するという性質を持たせられます。
そのため、State 遷移のテストやログの再現が容易になります。
🧑🏻💻 3. Output Processing — UI 更新と Side Effect
MVVM: State を中心に UI を更新する
Google の現在の Android アーキテクチャガイドは、ViewModel が UI State を生成し、UI がそれを購読する UDF を推奨しています。
val uiState: StateFlow<UiState> =
_uiState.asStateFlow()
val state by viewModel.uiState
.collectAsStateWithLifecycle()
例えば Snackbar を UI State に含める実装では、UI が State の変化を監視して表示できます。
LaunchedEffect(state.userMessage) {
state.userMessage?.let {
snackbarHostState.showSnackbar(it)
viewModel.onMessageShown()
}
}
この方式では、
ViewModel
↓
UiState
↓
UI
という一本の State Flow に寄せることができます。
なお、「一度だけ実行したいイベントを State に入れるべきか」については、現在の Google のガイドラインでは以前より明確に State 中心へ寄っています。
Google は ViewModel から UI へ直接イベントを送るのではなく、イベントの結果を UI State に反映することを推奨しています。
MVI: State と Effect を分離する
一方、MVI では永続的な UI State と、一度だけ実行したい Side Effect を分離する設計も一般的です。
private val _effect = Channel<UiEffect>()
val effect = _effect.receiveAsFlow()
val state by viewModel.state
.collectAsStateWithLifecycle()
UI 側では、
LaunchedEffect(Unit) {
viewModel.effect.collect { effect ->
when (effect) {
is UiEffect.ShowSnackbar ->
snackbarHostState.showSnackbar(
effect.message
)
}
}
}
のように State と Effect を別々に処理します。
┌───────────┐
│ ViewModel │
└─────┬─────┘
│
┌────────┴────────┐
↓ ↓
UiState UiEffect
↓ ↓
UI Side Effect
この設計のメリットは、「画面を表現する State」と「UI に一度だけ何かをさせる Effect」の責務を明確に分離できることです。
一方で、Channel や SharedFlow、Effect の lifecycle や delivery guarantee まで設計する必要があり、複雑さも増えます。
🧑🏻💻 4. MVVM と MVI の違いを整理する

🧑🏻💻 5. 「MVVM vs MVI」ではなく「どこまで厳密にするか」
ここで重要なのは、MVVM と MVI は完全に対立するものではないということです。
UDF
│
┌─────────┴─────────┐
↓ ↓
MVVM MVI
│ │
ViewModel Intent / Action
│ │
UI State Reducer
│ │
└─────────┬─────────┘
↓
UI
Google の公式ガイドが推奨しているのは、MVI という名前ではなく、UDF によって UI State を一方向に流し、ViewModel などの State Holder がユーザーイベントを処理する構造です。
そのため、ViewModel に複数のイベントハンドラを持たせること自体が「悪い MVVM」なのではありません。
むしろ Google の公式サンプルでも、onSignIn() のようなイベントハンドラを ViewModel に公開する形が使われています。
MVI はそこからさらに一歩進めて、
UI Event
↓
Intent / Action
↓
Reducer
↓
UiState
↓
UI
という形に統一することで、状態遷移そのものをアーキテクチャの中心に置く考え方だと言えます。
🧑🏻💻 6. どちらを選ぶべきか
シンプルな画面
- MVVM + UDF が適しています。
- ViewModel のメソッドを直接呼び出すだけで十分です。
状態遷移が複雑な画面
- MVI の考え方が有効です。
- Action と Reducer を明確に分離することで、状態遷移を追いやすくできます。
大規模チーム
- MVI のような厳密なルールが、チーム内の実装パターンを統一する助けになる場合があります。
- ただし、Reducer や Intent、Effect を増やすこと自体が目的になってはいけません。
重要なのはアーキテクチャの名前ではありません。
- State をどこに置くのか。
- 誰が State を変更するのか。
- Event をどこで処理するのか。
- UI に何を State として公開するのか。
- UI Effect をどのように扱うのか。
これらを明確にすることが、MVVM と MVI を選択する本質です。
🧑🏻💻 まとめ
MVVM は、少ないコードで UDF を実現しやすい実用的な選択肢です。
MVI は、Intent → Action → Reducer → State という流れを厳密に定義することで、State 遷移の予測可能性を高めます。
Google の現在の公式 Android ガイドは、MVI を推奨しているわけではなく、UDF + UI State + State Holder + ViewModel を中心に説明しています。
したがって、現代の Android では、
MVVM
↓
UDF + State
↓
必要なら MVI 的な Reducer / Action を導入
という考え方のほうが、「MVVM か MVI か」という二択よりも実際の設計に近いと言えるでしょう。
Related Categories : Android・Developmemt・Google・Great・JetpackCompose・Kotlin・Trending