LoginSignup
2
4

【Android】StateFlowのEvent的使い方まとめ

Posted at

はじめに

MVVMのようなアーキテクチャーを使用していると使用頻度の高いFlowシリーズですが、中でもStateFlowはその利便性から使用頻度が多い機能ではないでしょうか?
普段使用されるケースで多いのはViewModelとFragment間のやりとりだと思いますが、このStateFlowは性質上EventBusのような使い方も可能です。
今回はStateFlowを使用してEventBusのように使用する方法をまとめたいと思います。

StateFlowを使用したEvent通知実装方法

まず、StateFlowのベース部分であるSampleEventStoreから実装します。

SampleEventStore.kt
class SampleEventStore {
    companion object {
        private var instance = SampleEventStore()
        fun sharedInstance(): SampleEventStore {
            return instance
        }
    }

    private val _state = MutableStateFlow<Boolean>(false)
    val state = _state.asStateFlow()
    fun postValue(newValue: Boolean) {
        _state.value = newValue
    }
}

現状はサンプルの為Booleanの値を渡していますが、sealed classのような形で状態を別途定義し、値に指定する形でも良いと思います。
そこはご利用の要件に合わせて設定すべき内容を変えてもらえれば良いと思います。

また、StateFlowは常にアクティブでメモリに常駐しており、ガベージコレクションの対象となるのはガベージコレクションルートからの参照が他にない場合のみです。
つまり購読されている限り、StateFlowは破棄されません。
今回の実装はEventBusのような形で使用でき非常に便利ですが、計画的に使用しないと色々と問題につながる可能性がありますので、使用にはある程度縛りを設けた上で実装いただくのが良いかと思います。

次に通知を受け取るA画面の実装になります。

SampleAFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)

   lifecycleScope.launch {
        viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
            SampleEventStore.sharedInstance().state.collect { value ->
                // postValueで値が更新された際に呼ばれる
            }
        }
    }
}

受け取る側の実装は以上です。
StateFlowはcollectを使用することで購読可能です。
この購読に関して、公式のドキュメントに以下警告が記載されています。

警告: UI を更新する必要がある場合は、launch または launchIn 拡張関数を使用して UI から直接 Flow を収集しないでください。これらの関数は、ビューが表示されていなくてもイベントを処理します。その結果、アプリがクラッシュする可能性があります。これを回避するには、上記のように repeatOnLifecycle API を使用します。

まさに警告通りで、StateFlowは状態を監視している都合上バックグラウンドにいる際や画面が破棄された状態でも呼び出されてしまいます。
そこで警告にも記載されている通り、repeatOnLifecycleを使用し呼び出されるタイミングに制約を設けることで、意図しないタイミングで呼び出されることを防ぐ事が可能です。

ちなみに余談ですが、購読の仕方としてflowWithLifecycleというのもあるので、以下のように購読することも可能です。

SampleAFragment.kt
SampleEventStore.sharedInstance().state
   .onEach { value ->
      // postValueで値が更新された際に呼ばれる
   }
   .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
   .launchIn(lifecycleScope)

上記で実装する場合の注意点として、呼び出し順序です。
上記記述とは逆にしflowWithLifecycleをonEachより先に呼びだした場合、Lifecycle.StateがSTARTED以下になるとflowWithLifecycleによってStateFlowの収集はキャンセルされますが、onEachのブロックは動き続けるのでクラッシュする可能性があります。
その点だけは注意してください。

次に通知を送る側の調整です。

SampleBActivity.kt
SampleEventStore.sharedInstance().postValue(true)

通知を送る側は簡単で、必要なタイミングで上記を呼び出すだけです。
これで受け取る側の画面が指定した状態に呼び出され、値を受け取る事が可能です。
ご説明は以上です。

さいごに

StateFlowやLiveDataなど今では多種多様ですが、どれも理屈がわかるとなかなか面白い機能ですよね。
今回の実装は計画的利用が必須ですが、知っておくと救われることもあるかもしれませんね。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4