SharedFlowを使ったイベント通知
追記: まだまだ検討する必要があります。この記事は参考程度にしてもらえると助かります。
これまでViewModelからViewへのイベント通知を行うには、SingleLiveEvent
がよく使われてきました。
これに代わる手法を個人的に色々模索してたのですが、Coroutines 1.4で導入された SharedFlow
が使えるのではないかと思っています。
まだ実際にプロダクションに投入してるわけではないので、なにか見落としてることはあるかもです。そのときは指摘してもらえると嬉しいです。
SingleLiveEventの問題点
SingleLiveEvent
は LiveData
を改造したものになり、そもそもの LiveData
の挙動から大きく異なるものになります。
こちらの IssueTracker ではそういった理由で Jetpack に含めるリクエストを断っています。
また、Android Dev Summit 2019 では “ don’t use it “ と言っています。
Redditにもあるので、こちらも参照してもらうと良いかもです。
SharedFlowを使ったイベント通知
SharedFlowの使用例です。非常に簡単です。
LiveData
の使い方とほぼ同様な感じですが、注意としては emit
や collect
はsupend関数になってます。( tryEmit
もありますが、今回は省略します)
また、イベント受信側では lifecycleScope.launchWhenStarted
を使っています。こうすることで Activity/Fragment が動いてるときにのみ受信することができます。
SharedFlow と SingleLiveEvent の違い
挙動に少し違いあるので、あげていきます。
複数Observer/Collector
SingleLiveEvent
の場合は複数Observerへ通知することできません。ちょっと工夫する必要があります。
対して、SharedFlow
は複数Collectorを使用できます。ただ、少し注意としては、以下のように同じScope内に連続して書くと、最初のcollectで処理をブロックしてしまうので、二番目のが動きません。
複数受信したい場合は、Scopeを分ける必要があります。
バックグラウンド時のイベント
SingleLiveEvent
では、バックグラウンド時に複数のイベントが発生した場合は、フォアグラウンドに戻ったときに、何度イベントが発生していようが最後の1回の値だけを通知します。
SharedFlow
では、フォアグラウンドに戻ったときにすべてのイベントを通知します。この挙動は変更することが可能です。
SharedFlowのカスタマイズ
SharedFlow
は汎用な作りになっていて、 replay
や buffer
を使用することでカスタマイズ可能になっています。また、Coroutines Scopeによってコントロールすることも可能です。
色々と試してもらえると良いかなと思いますが、一つだけ例をあげます。
先程挙動の違いであげた、バックグラウンド時に起きたイベントを SingleLiveEvent
と同じように最後の1回だけをフォアグラウンドに戻ったときに通知したい場合です。
その場合は、以下のように extraBufferCapacity
と onBufferOverflow
を設定するだけで実現できます。
追記: Collectorが存在してないときのイベント
指摘もらって見落としてた課題として、例えばonStartより以前のイベントをどのように受け取ったら良いかという問題です。
launchWhenStarted
を使った場合、onCreate中に emit
されたものは通知を受け取ることができません。
これには、 replay
を使うことで受け取ることはできるのですが、再度 collect
した際にも処理が走ってしまうことになります。
現時点で、すごくシンプルな解決策は見つけられてないのですが、こちらの SingleLiveEvent
の記事にある Event Wrapper と replay
を使うことで、対応は可能です。
ただ、これだと複数 Collectorがいるときに対応が難しくなります。あと、これは StateFlow
でも良いかもしれないです。
Issueにも同じようなことが書かれていたので、参考にしてください。
追記2
SingleLiveEventのような動きをするには、Channel.receiveAsFlow
を使うと良さそうです。
以下の記事が参考になると思います。
まとめ
個人的には、SharedFlow
を使ってイベント通知を行うのはシンプルで良い気がしています。
buffer
や replay
によって汎用性も高くなっているのも強みかなと思います。
しかし、複雑になるケースや、そもそも実現できないケース、などの見落としてる課題があるかもしれません。
もし、こういった場合はどうするの?っていうのがあれば教えてもらえたら検証してみたいなと思っています。