Activity/FragmentでのFlowのcollect方法
課題
Activity/FragmentでFlowを使う時、受け取り方がいくつかあるっぽいので整理。
具体的にはMVVMでイベント・状態を受け取る時、今までRxやLiveDataでやっていた部分はどう置き換えられるのか。
実装
launchを使う
一般的にFlowを受け取る時に使うlaunch/collectを使う方法で、DESTROYED になった時は lifecycleScope により自動でキャンセルされる。
リソースを考慮してSTARTED で収集し STOPPED でcollectをキャンセルする場合、つまりバックグラウンドに行った時はFlow出力を処理しない場合には自前で実装する必要がある。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch{
flow.collect {
// Viewの更新
}
}
}
lateinit var job: Job
override fun onStart() {
super.onStart()
job = lifecycleScope.launch{
flow.collect {
// Viewの更新
}
}
}
override fun onStop() {
super.onStop()
job.cancel()
}
launchWhenXXを使う
lifecycleScope には launchWhenCreated launchWhenStarted launchWhenResumed が生えており、これらは名前の通りライフサイクルが最低XXの時にだけ(例えばSTARTED~STOPPED) 値が受け付けられる。
STOPPED になったら一時停止され、 STARTED でおなじジョブから再開される。
lateinit var job: Job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launchWhenStarted {
flow.collect {
// Viewの更新
}
}
}
repeatOnLifecycle
lifecycle-runtime-ktx:2.4.0-alpha01 で導入されたLifecycle.repeatOnLifecycle を使う方法。
指定されたライフサイクルの状態で値が受け付けられる。
launchWhenStarted はSTOPPEDになったら一時停止されSTARTEDで再開されるのに対し、 repeatOnLifecycle ではSTOPPEDになったら一旦ジョブがキャンセルされ、STARTEDでは新しく開始される。
複数のflowを同時に受け付ける時(例えば画面状態を表すUiStateのFlowと、Toastなど単発のイベントを受け取るFlow)、 collect はsuspendなため並列で受け取るためにそれぞれを別のcoroutineScopeで実行するためにlaunchでくくる必要がある。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
flow.collect {
// Viewの更新
}
}
}
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
flow.collect {
// Viewの更新
}
}
launch {
flow.collect {
// Viewの更新
}
}
}
}
}
flowWithLifecycle
こちらもlifecycle-runtime-ktx:2.4.0-alpha01 で導入されたFlow.flowWithLifecycle を使う。
上記のrepeatOnLifecycleの書き換えになっている。
こちらのほうがネストが一段少なくなる。
Flowのextensionで生えており、 map や flowOn と同じノリで、ライフサイクルを制限するように使われるようだ。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
flow.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect {
// Viewの更新
}
}
}
addRepeatingJob
lifecycle-runtime-ktx:2.4.0-alpha01 で上記と合わせて導入されたLifecycleOwner.addRepeatingJob を使う。が、2.4.0-alpha01 で削除された。
最新では使えないので以下参考までに。
addRepeatingJob は今までの書き方と比べて launch{} の部分もやってくれる。更にネストが減って嬉しい。
またこれまでの方法と比べるとflowを複数collectする場合にはそれぞれでlaunchをしなければいけなかったのに対し、こちらは addRepeatingJob を複数回書けば良い。
実際 flowWithLifecycle のドキュメントに、複数回collectする場合はこちらを使うべき、と書いてあった。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addRepeatingJob(Lifecycle.State.STARTED) {
flow.collect {
// Viewの更新
}
}
}
結論
いくつか書き方を示したが基本的に repeatOnLifecycle を使うで統一するのが良いかと思う。
理由として
- 特別な理由がない限りはリソース的に
launchWhenXXよりrepeatOnLifecycleが良い -
repeatOnLifecycleとflowWithLifecycle同じことができるが、前者のほうが複数flowを受け付ける場合に推奨されている
が挙げられる。
flowWithLifecycle はメソッドチェーンで処理を連結したい場合、 launchWhenXX は onStop -> onStart など一旦状態が落ちて戻ってきた時にFlowをキャンセルしたくないといった場合に使う。
Discussion