fluxとかMVIみたいな構造のアプリを作ってみたかった その5
前回からの進捗としては、NavigationDelegate
をActivity
, Fragment
に持たせることにしたり機能追加をやっていた。その中でViewModel
以下のクラスをリファクタリングしたところしっくりくるような形になったのでまとめてみる。
クラスの構成について
Androidのデータバインディングのスタイルをなるべく守りたいので、ViewModel
をDataBinding
に渡してデータのプロパティやイベントリスナの関数とバインドしたいという大前提がある。また、Repository
のインタフェースはFlow
を返すかsuspend
関数にして値を返すことにしているので、Flow
からLiveData
に変換する必要がある *1 。そのためにはCoroutineContext
が必要なので、ViewModel.viewModelScope.coroutineContext
を使うことにする。そのような前提と、これまでの実装を踏まえつつクラスごとの役割分担を見直した結果、次のような構成になった。
作ってる物の解像度が上がって作りやすくなった気がするけどこうやって見るとなんかごちゃごちゃしてんな pic.twitter.com/FjNs9kdmhO
— まつだあきひと (@akihito104) 2021年3月2日
この構成の基本的なアイディアは、ViewModel
をイベントリスナの部分と状態遷移の部分とに分割して、それぞれの処理を別のクラスに委譲するということ。ViewModel
の主な役割は状態のFlow
をLiveData
に変換するのみになる。また、ViewModel
はviewModelScope
を持っていたり、LiveData
を持たせる都合上、Androidの世界とビジネスモデルとの境界に立って両者の橋渡しを行う役割も暗に持つことになった。イベントリスナや状態を表すためのデータはViewModel
側が別途定義する。
イベントリスナはActions
クラスに委譲する。当初、このクラスにはMVIのIntent
のような役割を持たせたいと考えて、イベントバスに流れてくるイベントを捕まえて個別のFlow
を作ることだけをやっていたのだが、いまいち役割が軽く見えていて不要かもしれないと思い始めていた。しかし、今回の見直しで、Actions
にイベントリスナを実装させることでUIイベントの生成から個別のFlow
に流すまでを担当することになり、よりMVIのIntent
に近づいたように思う。また、こうすることでイベントを流す具体的な処理や具体的なイベントクラスを隠蔽することができるようになった。
状態遷移はViewModelSource
クラスに委譲する。このクラスはもともとViewStates
という名前で、View
の状態遷移を担当していたのでやること自体はあまり変わらない。ただ、このクラスにActions
を注入する都合から、このクラスにもイベントリスナを実装させActions
に委譲する形にしておけば、ViewModel
はこのクラスだけに依存すればよいことになる。もしそうなったとき、ViewStates
とViewModel
との違いは状態をFlow
で流すかLiveData
で持つかだけになるので、実質的にViewStates
はほぼViewModel
と同じものになるんだなと考えて名前をViewModel
の方に寄せる(ViewModelSource
)ことにした。
状態遷移の処理もこれを機に見直した。当初はView
のプロパティをそれぞれ個別のFlow
やLiveData
にわけて更新していたが、複数のFlow
やLiveData
が関係しあうとコードが複雑になってよくなかったので、ViewModel
から提供される状態データのインタフェースを実装したデータクラスを使って一元的に管理することにした。このあたりのことはまた別の記事に書こうと思うが、ざっくりいうとRxやFlow
にあるscan()
のような仕組みを使って、イベントが流れてくるごとに新しい状態のデータを生成して流すというような感じの実装になっている。
流れてきたイベントや状態遷移の結果、画面遷移したりSnackbar
で表示するようなちょっとしたメッセージフィードバックをしたいときはそれ用のFlow
を用意してActivity
やFramgent
向けにイベントを流す。
ここまでやってきて
当初はfluxやMVIのようなものを目指して作ってきたが、自分の理解力が足りなかったり実装力が無かったりしてバグが多く、ちょっと足したり削ったり移したりするとすぐ壊れてしまって難しかった。しかし、今の形に至り、ある程度わかりやすくまとまったのではないかと感じている。今、これは何かと尋ねられたら、大きくなってしまったViewModel
を分割するための一つの方法、と答えると思う。今の実装をもっと洗練させていくとfluxやMVIといったものに近づいていくのかもしれないが、もうあまり意識していない。ただ、ある程度の達成感は得られたものの、この試みがここで終わるのかと聞かれるとそれも違う気がしていて、同じタイトルでこのまま続けていくかもしれない。
*1:いずれFlowのままDataBindingに渡せるようになるだろうから必要なくなるかも