Compose architecture: MVVM or MVI with Flow?
日付:2021/07/08
URL:https://codingtroops.com/android/compose-architecture-part-1-mvvm-or-mvi-architecture-with-flow/
調査者:rmakiyama.icon rmakiyama
カテゴリ:Jetpack Compose, Architecture, Kotlin Coroutines
一言で表すと
State/Event/Effectという概念を用いてMVVMのViewModelに秩序を持たせた話
: chigi: DroidKaigiのアーキテクチャでみた話っぽい?
概要
Having multiple entry points to our ViewModel because of many interactions between the View layer and the Presentation layer could cause a simple MVVM with a plain ViewModel to become difficult to maintain and scale.
View周りが複雑になるとViewModelの保守とか大変になるから単純なMVVMだと良くないかもね、という発想
つまりは秩序のないViewModelにルールを設けましたという話だという理解rmakiyama.icon
ここにこの記事だとMVIのエッセンスを入れている
State
画面に表示するコンテンツ
Event
Widgetのクリック処理などのユーザーアクション
ViewModelはこのイベントに応じて処理をする
Effect
ダイアログの表示等のUI側が処理すべきアクション
記事だと、それぞれに対して
StateはMutableState
EventはMutableSharedFlow
EffectはChannel(receiveAsFlow)
を使うようにしてUIに公開。
これらの考えをカプセル化したものも紹介されていた。
code:kotlin
abstract class BaseViewModel
<Event : ViewEvent,
UiState : ViewState,
Effect : ViewSideEffect> : ViewModel() {
private val initialState: UiState by lazy { setInitialState() }
abstract fun setInitialState(): UiState
private val _viewState: MutableState<UiState> = mutableStateOf(initialState)
val viewState: State<UiState> = _viewState
private val _event: MutableSharedFlow<Event> = MutableSharedFlow()
private val _effect: Channel<Effect> = Channel()
val effect = _effect.receiveAsFlow()
init {
subscribeToEvents()
}
fun setEvent(event: Event) {
viewModelScope.launch { _event.emit(event) }
}
protected fun setState(reducer: UiState.() -> UiState) {
val newState = viewState.value.reducer()
_viewState.value = newState
}
private fun subscribeToEvents() {
viewModelScope.launch {
_event.collect {
handleEvents(it)
}
}
}
abstract fun handleEvents(event: Event)
protected fun setEffect(builder: () -> Effect) {
val effectValue = builder()
viewModelScope.launch { _effect.send(effectValue) }
}
}
Dataのフローを単一方向にしているという意味ではDroidKaigiのアプリと発想は同じな気がしている。
chrisbanes/tiviでも似た感じ。
ViewStateをFlowで公開
ActionをMutableSharedFlowでcollectして処理
uiEffectsをMutableSharedFlowで公開
code:ShowDetailsViewModel
init {
viewModelScope.launch {
pendingActions.collect { action ->
when (action) {
is ShowDetailsAction.RefreshAction -> refresh(true)
ShowDetailsAction.FollowShowToggleAction -> onToggleMyShowsButtonClicked()
is ShowDetailsAction.MarkSeasonWatchedAction -> onMarkSeasonWatched(action)
...
}
}
}
...
:chigi: reducer が爆発する〜問題がありますね...
気になるポイント
懸念点って何が挙げられるだろう?不向きな場合ってある?
コメント
Mori Atsushi.icon ViewよりViewModelのほうがスコープが長い場合、FlowでViewに通知するとsubscribeされてないで漏れるケースがあるでChannelを使ってそう
Mori Atsushi.icon 以前のViewに対してだと、Stateが大きくなると余計な更新を気にしないといけなかったけど、Composeで気にしなくて良くなった(嬉しい)