Androidの公式ドキュメントにある設計ガイドが更新され、実装について以前よりだいぶ明確に指針が記載されています。
その中で、ViewModelからのイベントで画面にメッセージを表示するような消費型のイベントについての記載があります。
https://developer.android.com/jetpack/guide/ui-layer/events#consuming-trigger-updates
イベントも状態として表現しており、メッセージ等を表示後にその状態を更新するような感じの処理になります。
個人的にずっと悩んでた、SingleLiveEventは推奨されないけどどう実装するのが良いのだろう…っていう悩みが解決しました。
ただ、ガイドでは簡略されてるため実際にこのパターンで実装するうえでの悩みと実際にどう実装すれば良いかを考えてみました。
実装の悩みどころ
設計ガイドの実装では Snackbar でメッセージを表示するようになっていますが、現実ではそれ以外にも処理したいことがあります。
例えば、何か処理が終わって成功したときに画面遷移をしたい場合にどう実装するのが良いのか。
メッセージを表示する手段も、 Snackbar だけじゃなく AlertDialog や Toast もあります。
この複数の種類があるイベント処理についてどう実装するのが良いのかが悩みどころです。
これをどう解決するかを考えてみました。
UIの処理パターンで状態を分ける
例えば、以下のような感じで、UIでの処理パターンを状態としてUI Stateで別々で管理する方法です。
更にViewModel側でこれらのイベントを消費するメソッドを用意してあげます。実装例は省略してますが、それぞれ処理済みのイベントListを除外してUI Stateを更新する感じです。
ViewModelからイベントを発行するには以下のような感じです。
UI側はそれぞれで対応する処理をする感じです。以下の例はComposeを想定していますが、Viewシステムで似たような感じになります。
実装としてはこれでも問題なく実現できるかなぁとは思いますが、だいぶ冗長な感じがしています。UIの処理パターンが増えるたびに管理する状態も増えていきます。
また、ViewModelからイベントを発行する際にUIの処理を強く意識する必要があります。
イベントを1つの状態で管理する
ぼくが考えたもう一つの実装方法として、イベントを1つの状態として管理する方法です。
まず、ViewModelから発生するイベントを sealed interface/class
で定義し、UI Stateで管理します。この例では成功とエラーの2種類を定義しています。
次にイベント処理後に消費するためのメソッドを追加します。単純にイベントListから消費したいイベントを除外したListを作成してUI Stateを更新する感じですね。
ViewModelからイベントを発行するには以下のような感じです。
UI側では、定義されたイベントごとに対応する処理を行うだけです。この例では成功時はSnackbarを表示して、エラー時はAlertDialogを表示しています。
この実装方法であれば、UI Stateに持つ状態としては1つで済みますし、 sealed interface/class
でイベントを定義してるので管理しやすいです。
また、イベントを発行するViewModel側ではUIがどういう処理をするのかを気にしなくて良くなります。UI側でもイベントごとに処理を変えるだけなのでUIでの処理が変わった際も修正しやすい状態だと思います。
個人的にはこちらの方法が良いかなと思っています。
イベントの重複について
紹介した2つの処理ですが、同じイベントが重複した場合に1つしか処理されないような感じになっています。
2つ目の実装方法で説明しますが、例えば同じメッセージのErrorのイベントを2回同時に発行した場合、イベント消費する箇所で data class
の比較によって同じものと見なされます。そのため片方のイベントを消費したときに同時にもう一方のイベントも消費された扱いになります。
こういう場合は独自にIDを振ることで解決できます。注意としては object
を使うとすべて同じIDになってしまうので、使えません。
消費する処理ではIDによってイベントを比較します。
個人的にはあまり無いケースだとは思っていますが、必要に応じてこういった対応をする必要があります。
さいごに
この実装をまだちゃんと運用はしてないので何か問題がある可能性があります。何か気づいたことあればコメントもらえると嬉しいです。また、もっと良い方法がある場合は教えてもらえると嬉しいです。