LoginSignup
6
2

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-12-02

前置き

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

課題

公式の案内でもあるとおり、AndroidのActivity/FragmentでFlow<T>collect する場合には下記のいずれかの記述をする必要があります。

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

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

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

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

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並に読みやすくなったと思います。

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

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

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

余談: FragmentにおけるlifecycleScopelifecycleOwnerについて

上記でさらりと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が通知されなくなってしまうのでご注意ください。

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2