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