[Android] flowWithLifecycleを使う際気をつけたいこと

# Lifecycle-awareなFlowの収集
ActivityやFragmentでFlowを収集する際、ライフサイクルに適した動作をする`Lifecycle.repeatOnLifecycle`の利用が[推奨されています](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow?hl=ja#stateflow)。
通常の`LifecycleScope.launchWhen`ではアプリがバックグラウンドにある状態など、ビューが表示されていない場合でもイベントが処理されるためです。

```kotlin
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`と組み合わせることでネストが少なくなるので、好みによってはこちらの書き方を選択する場合もあると思います。
私もこちらの書き方が好みですが、いくつか注意すべきポイントがあったので紹介します。

```kotlin
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のブロックは動き続けるのでクラッシュする可能性があります。

```kotlin
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を実行することで想定どおりの動作が実現できます。

```kotlin
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の利用も検討しましょう。

```kotlin
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 {
                ...
                }
            }
        }
    }
}
```

# 参考
https://developer.android.com/reference/kotlin/androidx/lifecycle/package-summary#flowwithlifecycle