Lifecycle-awareなFlowの収集
ActivityやFragmentでFlowを収集する際、ライフサイクルに適した動作をするLifecycle.repeatOnLifecycle
の利用が推奨されています。
通常のLifecycleScope.launchWhen
ではアプリがバックグラウンドにある状態など、ビューが表示されていない場合でもイベントが処理されるためです。
class LatestNewsActivity : AppCompatActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
...
// Start a coroutine in the lifecycle scope
lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Trigger the flow and start listening for values.
// Note that this happens when lifecycle is STARTED and stops
// collecting when the lifecycle is STOPPED
latestNewsViewModel.uiState.collect { uiState ->
// New value received
when (uiState) {
is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
is LatestNewsUiState.Error -> showError(uiState.exception)
}
}
}
}
}
}
flowWithLifecycle
repeatOnLifecycleを使いやすくする拡張関数Flow.flowWithLifecycle
も用意されています。
launchIn
やonEach
と組み合わせることでネストが少なくなるので、好みによってはこちらの書き方を選択する場合もあると思います。
私もこちらの書き方が好みですが、いくつか注意すべきポイントがあったので紹介します。
class LatestNewsActivity : AppCompatActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
...
latestNewsViewModel.uiState
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.onEach { uiState ->
when (uiState) {
is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
is LatestNewsUiState.Error -> showError(uiState.exception)
}
}
.launchIn(lifecycleScope)
}
}
実行する順番
flowWithLifecycleを利用した場合、指定したLifecycle.State以下になると上流のFlowはキャンセルされますが、下流のFlowはキャンセルされずlaunchしたCoroutineScopeがアクティブである限り動きつづけます。
例えば、以下のようにonEach内で時間のかかる処理を行っているときにLifecycle.StateがSTARTED以下に移行すると、flowWithLifecycleによってuiStateの収集はキャンセルされますが、onEachのブロックは動き続けるのでクラッシュする可能性があります。
class LatestNewsActivity : AppCompatActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
...
latestNewsViewModel.uiState
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.onEach { uiState ->
delay(5000) // long process
updateUI(uiState)
}
.launchIn(lifecycleScope)
}
}
onEachのブロックもキャンセルしたい場合は、onEachのあとにflowWithLifecycleを実行することで想定どおりの動作が実現できます。
class LatestNewsActivity : AppCompatActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
...
latestNewsViewModel.uiState
.onEach { uiState ->
delay(5000) // canceled if lifecycle.state < STARTED
updateUI()
}
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.launchIn(lifecycleScope)
}
}
複数のFlowの収集
ViewModelで複数の状態を別々のStateFlowで管理している場合など、複数のFlowを収集するケースもあると思います。
それぞれのFlowに対してflowWithLifecycleを利用したくなりますが、flowWithLifecycleは新たなホットフローを作成するのでリソースの観点から好ましくない場合もあります。
Flowの数が多い場合は、repeatOnLifecycleの利用も検討しましょう。
class LatestNewsActivity : AppCompatActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
latestNewsViewModel.state1.collect {
}
}
launch {
latestNewsViewModel.state2.collect {
}
}
launch {
...
}
}
}
}
}
参考