2

Let's say that we have a simple fragment with a view based on the UI state held in StateFlow in the view model. On onCreate() we collect state as usually:

override fun onCreate(savedInstanceState: Bundle?) {
        lifecycleScope.launchWhenStarted {
            viewModel.uiState.collect {
                // UI update according to state
            }
        }
    }

Now we navigate to the next fragment - the previous is kept by the fragment manager, but the view is destroyed. On the new fragment, we pop back stack and now is surprising: the previous fragment is recreating the view on the initial state and even if we try to update state flow nothing will happen since it doesn't emit equal data twice one by one.

So, how to restore the view state after return to the fragment?

Karol Kulbaka
  • 1,136
  • 11
  • 21
  • Does behavior change if you do `launchWhenResumed`? Have you tried (since you're updating the views) override `onViewCreated` and use `viewLifecycleOwner.lifecycleScope` instead? – Pawel Sep 30 '21 at 17:48
  • @Pawel `launchWhenResumed` changes nothing. Move to `onViewCreated` helps, but it seems to be more workaround than the solution - when I have for example different `ActivityResultLauncher`s and decide which to use basing on state, it forcing me to create each as fragment property. – Karol Kulbaka Sep 30 '21 at 17:59
  • It's hard to believe that such a case can be an issue :( – Karol Kulbaka Sep 30 '21 at 18:00
  • It seems that you have to decouple UI state from fragments state. What you described as "surprising" is quite normal - its fragment entering detached state (If i'm understanding correctly and it undergoes `onDestroyView` call without `onDestroy`). – Pawel Sep 30 '21 at 18:46
  • @Pawel You're probably right. I was surprised because state flow is presented as a replacement for live data and it can't be used to create consistent handling of the obvious android scenarios. F#@%!! – Karol Kulbaka Sep 30 '21 at 19:01
  • Moving to `onViewCreated` and using `viewLifecycleOwner` is not a workaround. It's the intended way to be lifecycle-aware when you're in a Fragment where the view can be destroyed and recreated independently from the Fragment itself. This doesn't depend on `Flow`s. The recommendation is the same for `LiveData` – gpunto Sep 30 '21 at 21:09

1 Answers1

1

Maybe a bit later but what you are looking for is repeatOnLifeCycle

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { ... }
    }
}

repeatOnLifecycle together with lifecycleScope.launch allows you to collect flows from a Fragment or Activity.

The main difference with your approach is that launchWhenStarted will run you collection function ONCE the UI State is started, but will not repeat the collection when a fragment is restored, while repeatOnLifeCycle will create a new coroutine starting the collection everytime the lifecycle scope reaches the specified state (hence the name) and will cancel it when the lifecycle reaches the opposite state (for example, if you specified STARTED, then the coroutine will be cancelled on onStop).

In addition to that, the launchWhenX documentation nowadays indicates the following

Caution: This API is not recommended to use as it can lead to wasted resources in some cases. Please, use the Lifecycle.repeatOnLifecycle API instead. This API will be removed in a future release.

PD, lifecycleScope.launch { ... } creates a coroutine, and is required as repeatOnLifecycle is a suspend function (suspend functions run on a coroutine or another suspend function)