Mohammad Hamdan
Mohammad Hamdan

Reputation: 113

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.

Upvotes: 11

Views: 3264

Answers (1)

niqueco
niqueco

Reputation: 2408

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.

Upvotes: 7

Related Questions