SharedFlowを使ったイベント通知

SingleLiveEventの代わりにSharedFlowを使ってイベント通知

Kenji Abe
6 min readNov 2, 2020
Photo by Adam Solomon on Unsplash

追記: まだまだ検討する必要があります。この記事は参考程度にしてもらえると助かります。

これまでViewModelからViewへのイベント通知を行うには、SingleLiveEvent がよく使われてきました。

これに代わる手法を個人的に色々模索してたのですが、Coroutines 1.4で導入された SharedFlow が使えるのではないかと思っています。
まだ実際にプロダクションに投入してるわけではないので、なにか見落としてることはあるかもです。そのときは指摘してもらえると嬉しいです。

SingleLiveEventの問題点

SingleLiveEventLiveData を改造したものになり、そもそもの LiveData の挙動から大きく異なるものになります。

こちらの IssueTracker ではそういった理由で Jetpack に含めるリクエストを断っています。

また、Android Dev Summit 2019 では “ don’t use it “ と言っています。

Redditにもあるので、こちらも参照してもらうと良いかもです。

SharedFlowを使ったイベント通知

SharedFlowの使用例です。非常に簡単です。

LiveData の使い方とほぼ同様な感じですが、注意としては emitcollect はsupend関数になってます。( tryEmit もありますが、今回は省略します)

また、イベント受信側では lifecycleScope.launchWhenStarted を使っています。こうすることで Activity/Fragment が動いてるときにのみ受信することができます。

SharedFlow と SingleLiveEvent の違い

挙動に少し違いあるので、あげていきます。

複数Observer/Collector

SingleLiveEvent の場合は複数Observerへ通知することできません。ちょっと工夫する必要があります。

対して、SharedFlow は複数Collectorを使用できます。ただ、少し注意としては、以下のように同じScope内に連続して書くと、最初のcollectで処理をブロックしてしまうので、二番目のが動きません。

複数受信したい場合は、Scopeを分ける必要があります。

バックグラウンド時のイベント

SingleLiveEvent では、バックグラウンド時に複数のイベントが発生した場合は、フォアグラウンドに戻ったときに、何度イベントが発生していようが最後の1回の値だけを通知します。

SharedFlow では、フォアグラウンドに戻ったときにすべてのイベントを通知します。この挙動は変更することが可能です。

SharedFlowのカスタマイズ

SharedFlow は汎用な作りになっていて、 replaybuffer を使用することでカスタマイズ可能になっています。また、Coroutines Scopeによってコントロールすることも可能です。

色々と試してもらえると良いかなと思いますが、一つだけ例をあげます。

先程挙動の違いであげた、バックグラウンド時に起きたイベントを SingleLiveEvent と同じように最後の1回だけをフォアグラウンドに戻ったときに通知したい場合です。
その場合は、以下のように extraBufferCapacityonBufferOverflow を設定するだけで実現できます。

追記: Collectorが存在してないときのイベント

指摘もらって見落としてた課題として、例えばonStartより以前のイベントをどのように受け取ったら良いかという問題です。

launchWhenStarted を使った場合、onCreate中に emit されたものは通知を受け取ることができません。
これには、 replay を使うことで受け取ることはできるのですが、再度 collect した際にも処理が走ってしまうことになります。

現時点で、すごくシンプルな解決策は見つけられてないのですが、こちらの SingleLiveEvent の記事にある Event Wrapper と replay を使うことで、対応は可能です。

ただ、これだと複数 Collectorがいるときに対応が難しくなります。あと、これは StateFlow でも良いかもしれないです。

Issueにも同じようなことが書かれていたので、参考にしてください。

追記2

SingleLiveEventのような動きをするには、Channel.receiveAsFlow を使うと良さそうです。

以下の記事が参考になると思います。

まとめ

個人的には、SharedFlow を使ってイベント通知を行うのはシンプルで良い気がしています。

bufferreplay によって汎用性も高くなっているのも強みかなと思います。

しかし、複雑になるケースや、そもそも実現できないケース、などの見落としてる課題があるかもしれません。
もし、こういった場合はどうするの?っていうのがあれば教えてもらえたら検証してみたいなと思っています。

--

--

Kenji Abe

Programmer / Gamer / Google Developers Expert for Android, Kotlin / @STAR_ZERO