LoginSignup
7
3

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-01-28

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

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

参考

7
3
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
7
3