RE: AndroidでFlow<T>をrepeatOnLifecycleでcollectするのをちょっとだけ楽にする

## 前置き

以前 [AndroidでFlow<T>をcollectするのをちょっとだけ楽にする](https://qiita.com/KazaKago/items/b24b457d8f6bf5bcf591)という記事を書いたのですが、 `Flow<T>` をcollectする際には `launchWhenStarted` ではなく代わりに `repeatOnLifecycle` を使いましょうというお達しが出ているのでそれに合わせて記事の内容も書き直してみます。

## 課題

[公式の案内](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow)でもあるとおり、AndroidのActivity/Fragmentで`Flow<T>`を `collect` する場合には下記のいずれかの記述をする必要があります。

```kotlin:Activity
override fun onCreate(savedInstanceState: Bundle?) {
    lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            viewModel.uiState.collect { uiState ->
               ...
            }
        }
    }
}
```
```kotlin:Activity
override fun onCreate(savedInstanceState: Bundle?) {
    lifecycleScope.launch {
        viewModel.uiState
            .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
            .collect { uiState ->
                ...
            }
        }
    }
}
```

なんというかネストが深くて記述が冗長ですね。
`repeatOnLifecycle`よりは`flowWithLifecycle`を使うことでそこはかとなく読みやすくなっているような気もしますが、それでもいくつもcollectが増えると可読性が下がりそうです。

## `Flow<T>`の拡張関数を作って対処する

そこで下記のような拡張関数を用意して、launch関数でネストさせなくてもcollect出来るようにしてみます。

```kotlin:FlowCollector
inline fun <T> Flow<T>.collectOnLifecycle(lifecycleOwner: LifecycleOwner, state: Lifecycle.State, crossinline action: suspend (value: T) -> Unit): Job {
    return lifecycleOwner.lifecycleScope.launch {
        lifecycleOwner.repeatOnLifecycle(state) {
            collect(action)
        }
    }
}

inline fun <T> Flow<T>.collectOnCreated(lifecycleOwner: LifecycleOwner, crossinline action: suspend (value: T) -> Unit): Job {
    return collectOnLifecycle(lifecycleOwner, Lifecycle.State.CREATED, action)
}

inline fun <T> Flow<T>.collectOnStarted(lifecycleOwner: LifecycleOwner, crossinline action: suspend (value: T) -> Unit): Job {
    return collectOnLifecycle(lifecycleOwner, Lifecycle.State.STARTED, action)
}

inline fun <T> Flow<T>.collectOnResumed(lifecycleOwner: LifecycleOwner, crossinline action: suspend (value: T) -> Unit): Job {
    return collectOnLifecycle(lifecycleOwner, Lifecycle.State.RESUMED, action)
}
```

## repeatOnLifecycle+collectを置き換える

先程の拡張関数を使うと下記のように表すことができます。
LiveData並に読みやすくなったと思います。

```kotlin:Activity
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    viewModel.flow.collectOnStarted(this) {
        binding.textView.text = it
    }
}
```
```kotlin:Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewModel.flow.collectOnStarted(viewLifecycleOwner) {
        binding.textView.text = it
    }
}
```

## 余談: Fragmentにおける`lifecycleScope`の`lifecycleOwner`について

上記でさらりとFragmentにおけるパターンも紹介しましたが、`lifecycleOwner`に指定には注意点があります。
ActivityではActivity自身が`lifecycleOwner`なので、とりあえずthisを指定しておけば基本的には問題ありませんが、Fragmentでは`lifecycleOwner`はFragment自身と`viewLifecycleOwner`の2種類があります。
Fragment自身のほうはFragmentクラス自体のライフサイクルに準拠しており、Viewのライフサイクルとは異なります。
なので、あくまでFragment内のViewを操作する場合には`viewLifecycleOwner`側の`lifecycleScope`を使うようにしてください。

ではFragment自身のlifecycleOwnerはいつ指定するのかというと、私がこれまで必要だったシーンとしては、UIを持たない（=Activityにattachしない）Fragmentとして利用する場合です。
その場合は逆に誤って`viewLifecycleOwner`を指定してしまうとViewが存在しないのでSTARTEDまで到達せず、結果としてcollectが通知されなくなってしまうのでご注意ください。