3

In MyViewModel a MutableStateFlow is used to transmit events to the fragment. When the value of the MutableStateFlow is changed the earlier values are being overwritten inside the coroutine. So never received by fragment.

internal class MyViewModel(application: Application) : AndroidViewModel(application) {
    private val myMutableStateFlow = MutableStateFlow<MySealedClass>(MySealedClass.Dummy1())
    private fun getData() {
        viewModelScope.launch {
            //yield()
            myMutableStateFlow.value = MySealedClass.Dummy2()
            myMutableStateFlow.value = MySealedClass.Dummy3()
        }
    }
}

internal class MyFragment : Fragment(){
    private var uiStateJob: Job? = null
    override fun onStart() {
        super.onStart()
        uiStateJob = lifecycleScope.launch {
           myViewModel.getUiFlow().collect {
              //do something
           }
       }
    }
}

If yield() is commented Dummy2 event is never received by the Fragment. Dummy 3 is received though. If yield() is uncommented Dummy2 & 3 are both received. If the state values are changed outside the coroutine then both Dummy2 and Dummy3 are received.

I need to predictably receive all events in my fragment. Is there a proper reasoning for this behaviour?

Lakshman Chilukuri
  • 1,067
  • 2
  • 11
  • 20

1 Answers1

4

StateFlow is meant to represent a state. Each event is technically a new up-to-date state value, making the previous states obsolete. This type of flow is for situations when only the latest state matters, because its events are conflated. From the docs:

Updates to the value are always conflated. So a slow collector skips fast updates, but always collects the most recently emitted value.

Edit in response to your comment: yield() is a suspend function that forces the suspension of the current coroutine. Therefore it gives a chance to the other coroutine to progress until its next suspension point, this is why in that case the collect is "ready" before the first value is set (and emitted).

However you shouldn't rely on that because it's brittle: if the other coroutine gets modified and has extra suspension points by calling other suspend functions, it might not reach the collect call, and you would be back to the other behaviour.

If you consistently need all events, you have several options:

  • Switch to a cold flow, which will only start when you collect
  • Use a Channel (with or without buffer)
  • Use a SharedFlow and trigger the events start by using onSubscription
Joffrey
  • 32,348
  • 6
  • 68
  • 100
  • That makes sense. But shouldnt the behaviour be consistent? It seems like if the states are changed inside coroutine, then only conflation is taking place. If not inside coroutine, consecutive states are received without conflation. Also I am not clear how the yield() statement is changing the situation. – Lakshman Chilukuri May 23 '21 at 02:27
  • 1
    It doesn't have to be deterministic or consistent in the manner it conflates events, because there is no guarantees in this respect by design. I edited my post to explain about yield. – Joffrey May 23 '21 at 09:19