前置き
以前 AndroidでFlowをcollectするのをちょっとだけ楽にするという記事を書いたのですが、 Flow<T>
をcollectする際には launchWhenStarted
ではなく代わりに repeatOnLifecycle
を使いましょうというお達しが出ているのでそれに合わせて記事の内容も書き直してみます。
課題
公式の案内でもあるとおり、AndroidのActivity/FragmentでFlow<T>
を collect
する場合には下記のいずれかの記述をする必要があります。
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
...
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launch {
viewModel.uiState
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect { uiState ->
...
}
}
}
}
なんというかネストが深くて記述が冗長ですね。
repeatOnLifecycle
よりはflowWithLifecycle
を使うことでそこはかとなく読みやすくなっているような気もしますが、それでもいくつもcollectが増えると可読性が下がりそうです。
Flow<T>
の拡張関数を作って対処する
そこで下記のような拡張関数を用意して、launch関数でネストさせなくてもcollect出来るようにしてみます。
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並に読みやすくなったと思います。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.flow.collectOnStarted(this) {
binding.textView.text = it
}
}
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が通知されなくなってしまうのでご注意ください。