Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

StateFlow is emitting every time the activity paused and resumed

I have a state flow that emits a value once the HTTP request is returned with the response, the value will be shown as a list in Activity, I'm using Kotlin coroutines StateFlow for communication between the ViewModel and Activity.

I'm using androidx lifecycle repeatOnLifecycle function like this:

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.successFlow.collect { binding.recyclerView.adapter = ExampleAdapter(it) }
    }
}

This is working fine at the beginning but then I realized that every time the user goes to another screen and back to the previous screen the state flow will reemit the value which in this case will lose the list state, for example, if the user scrolled to item 10 in the list and then goes to another screen and return back the list will scroll to position 0 because the setAdapter method invoked again, which is not the case when using LiveData.

Now I need to handle StateFlow state and configuration state too, I tried to use the distinctUntilChanged method but as the documentation says Applying 'distinctUntilChanged' to StateFlow has no effect.

The question here how I can achieve the same LiveData behaviour using StateFlow.

like image 977
Mohammad Hamdan Avatar asked Sep 08 '21 11:09

Mohammad Hamdan


People also ask

What is the difference between flow and StateFlow?

Unlike a cold flow built using the flow builder, a StateFlow is hot: collecting from the flow doesn't trigger any producer code. A StateFlow is always active and in memory, and it becomes eligible for garbage collection only when there are no other references to it from a garbage collection root.

Why StateFlow is better than LiveData?

LiveData is an Android Framework If we want to share the common code between different platforms (e.g. iOS and Android, e.g. using Kotlin Multiplatform Module — KMM), using LiveData will make the common code Android-specific. StateFlow is platform-agnostic, and hence more portable to support across platforms.

Is StateFlow lifecycle aware?

StateFlow(hot stream) does similar things like LiveData but it is made using flow by kotlin guys and only difference compare to LiveData is it's not lifecycle aware but this is also been solved using repeatOnLifecycle api's, so whatever LiveData can do StateFlow can do much better with power of flow's api.

What is Android StateFlow?

StateFlow is a state-holder observable flow that emits the current and new state updates to its collectors. In Android, StateFlow is a great fit for classes that need to maintain an observable mutable state.


Video Answer


1 Answers

You have hit the problem that was recently described in a Medium post. Each flow collection starts anew and will receive the value again and again even if it has not changed. LiveData, on the other hand, knows about which version of the data each observer has seen and will only send data that has been posted again.

A simple fix for your problem would be to store the data somewhere and check if it has changed. You can also collect the flow with flowWithLifecycle() and do distinctUntilChanged() on it (beware of using a buffered operator after this or the value emission might be lost, as described in the post linked above).

It would look like this:

viewModel.successFlow.flowWithLifecycle(lifecycle).distinctUntilChanged().onEach {
    binding.recyclerView.adapter = ExampleAdapter(it)
}.launchIn(lifecycleScope)

However, I think you should not replace the adapter each time the data changes! You should be doing:

(binding.recyclerView.adapter as ListAdapter<*,*>).submitList(it)

(Of course, you should be using ListAdapter for this to work)

With this nothing will get disrupted when new data arrives, whether is the same or different data.

like image 54
niqueco Avatar answered Oct 12 '22 14:10

niqueco