fresh digitable

めんどくさかったなってことを振り返ったり振り返らなかったりするための記録

fluxとかMVIみたいな構造のアプリを作ってみたかった その5

前回: akihito104.hatenablog.com

前回からの進捗としては、NavigationDelegateActivity, Fragmentに持たせることにしたり機能追加をやっていた。その中でViewModel以下のクラスをリファクタリングしたところしっくりくるような形になったのでまとめてみる。

クラスの構成について

Androidのデータバインディングのスタイルをなるべく守りたいので、ViewModelDataBindingに渡してデータのプロパティやイベントリスナの関数とバインドしたいという大前提がある。また、RepositoryのインタフェースはFlowを返すかsuspend関数にして値を返すことにしているので、FlowからLiveDataに変換する必要がある *1 。そのためにはCoroutineContextが必要なので、ViewModel.viewModelScope.coroutineContextを使うことにする。そのような前提と、これまでの実装を踏まえつつクラスごとの役割分担を見直した結果、次のような構成になった。

この構成の基本的なアイディアは、ViewModelをイベントリスナの部分と状態遷移の部分とに分割して、それぞれの処理を別のクラスに委譲するということ。ViewModelの主な役割は状態のFlowLiveDataに変換するのみになる。また、ViewModelviewModelScopeを持っていたり、LiveDataを持たせる都合上、Androidの世界とビジネスモデルとの境界に立って両者の橋渡しを行う役割も暗に持つことになった。イベントリスナや状態を表すためのデータはViewModel側が別途定義する。

イベントリスナはActionsクラスに委譲する。当初、このクラスにはMVIのIntentのような役割を持たせたいと考えて、イベントバスに流れてくるイベントを捕まえて個別のFlowを作ることだけをやっていたのだが、いまいち役割が軽く見えていて不要かもしれないと思い始めていた。しかし、今回の見直しで、Actionsにイベントリスナを実装させることでUIイベントの生成から個別のFlowに流すまでを担当することになり、よりMVIのIntentに近づいたように思う。また、こうすることでイベントを流す具体的な処理や具体的なイベントクラスを隠蔽することができるようになった。

状態遷移はViewModelSourceクラスに委譲する。このクラスはもともとViewStatesという名前で、Viewの状態遷移を担当していたのでやること自体はあまり変わらない。ただ、このクラスにActionsを注入する都合から、このクラスにもイベントリスナを実装させActionsに委譲する形にしておけば、ViewModelはこのクラスだけに依存すればよいことになる。もしそうなったとき、ViewStatesViewModelとの違いは状態をFlowで流すかLiveDataで持つかだけになるので、実質的にViewStatesはほぼViewModelと同じものになるんだなと考えて名前をViewModelの方に寄せる(ViewModelSource)ことにした。

状態遷移の処理もこれを機に見直した。当初はViewのプロパティをそれぞれ個別のFlowLiveDataにわけて更新していたが、複数のFlowLiveDataが関係しあうとコードが複雑になってよくなかったので、ViewModelから提供される状態データのインタフェースを実装したデータクラスを使って一元的に管理することにした。このあたりのことはまた別の記事に書こうと思うが、ざっくりいうとRxやFlowにあるscan()のような仕組みを使って、イベントが流れてくるごとに新しい状態のデータを生成して流すというような感じの実装になっている。

流れてきたイベントや状態遷移の結果、画面遷移したりSnackbarで表示するようなちょっとしたメッセージフィードバックをしたいときはそれ用のFlowを用意してActivityFramgent向けにイベントを流す。

ここまでやってきて

当初はfluxやMVIのようなものを目指して作ってきたが、自分の理解力が足りなかったり実装力が無かったりしてバグが多く、ちょっと足したり削ったり移したりするとすぐ壊れてしまって難しかった。しかし、今の形に至り、ある程度わかりやすくまとまったのではないかと感じている。今、これは何かと尋ねられたら、大きくなってしまったViewModelを分割するための一つの方法、と答えると思う。今の実装をもっと洗練させていくとfluxやMVIといったものに近づいていくのかもしれないが、もうあまり意識していない。ただ、ある程度の達成感は得られたものの、この試みがここで終わるのかと聞かれるとそれも違う気がしていて、同じタイトルでこのまま続けていくかもしれない。

次回: akihito104.hatenablog.com

*1:いずれFlowのままDataBindingに渡せるようになるだろうから必要なくなるかも