動機
Android Architecture Components ベースの Android アプリ開発においては ViewModel 経由でデータをやりとりするのが正しいのですが、Activity ⇔ Activity 間や ViewModel の取り回しが複雑になってしまう場合は、多用は禁物と思いつつもイベントバスのような仕組みが欲しくなります。
greenrobot/EventBus や otto のような既存のライブラリを使ったり、RxJava で代用 したりしてもいいのですが、せっかく Android Architecture Components ベースでの開発をしているなら LiveData で実現したいなと思ったのでした。
実装してみる
試行錯誤の結果、次のようになりました。
@Singleton
class EventBus @Inject constructor() {
val eventLiveData = MutableLiveData<Event>()
@MainThread
inline fun <reified T : Event> on(
owner: LifecycleOwner,
crossinline observer: (T) -> Unit
) {
val obs = Observer<Event> {
if (it != null && it is T) {
observer.invoke(it)
}
}
eventLiveData.observeForever(obs)
owner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
eventLiveData.removeObserver(obs)
}
})
}
@MainThread
fun post(event: Event) {
eventLiveData.value = event
eventLiveData.value = null
}
interface Event
}
シングルトンなインスタンスを Dagger 等で配信することを想定していますが、Dagger 等を利用しないのであれば object クラスにしても良いかもしれません(テストがやりにくくなるかも?ですが)。
イベントの受け取り側(on
メソッド)では、内部的に LiveDeta#observeForever()
を利用しています。LiveDeta#observe()
だと Activity が裏側にあるなど onStop()
時にイベントを受け取れないからです。LiveDeta#observeForever()
を利用しているので、購読を自前で解除しないといけないのですが、LifeCycle を利用して自動で解除するようにしています。
このように購読の解除の関係でイベントの受け取り側では LifecycleOwner
を必要とする仕様にしています。そうしないと自前で別途、購読を解除する必要があり煩雑になるというのもありますが、どこでも自由にイベントを送受信できてしまうとアプリケーションが複雑化してしまうし、LiveData の仕組みに乗っている以上この仕様は自然だろうと考えています。
インベントの送信側(post
メソッド)では、イベントを送信した後、即座に null で更新しています。LiveData は購読を開始した際に、過去最新の値(1件のみ)が流れてきてしまうので、「イベントを送信したその時点」で購読していない場合はイベントを伝えたくないからです。
使い方
イベントを送る
EventBus.Event
インタフェースを継承した任意のクラスのインスタンスを post()
メソッドに渡すだけですが、単純なイベントだけ(渡すデータが無い)を送りたい場合は、object クラスを利用します。
object HogeEvent: EventBus.Event
// ...
eventBus.post(HogeEvent)
イベントと同時にデータも渡したい場合は、data クラスを利用すればよいでしょう。
data class HogeEvent(val name: String): EventBus.Event
// ...
eventBus.post(HogeEvent("山田"))
イベントを受け取る
受け取りたいイベントの型を指定して on()
でイベントを購読します。
eventBus.on<HogeEvent>(this) { // this は LifecycleOwner
// do something..
}
this
は LifeCycleOwner なので Activity や Fragment などです。
おわりに
まだ実案件等で活用しているわけではなく知見も乏しいので、改善点やもしここはおかしいぞ等あればコメントでお知らせ頂けると嬉しいです。