androidkotlinkotlin-coroutineskotlin-stateflow

MutableStateFlow events being overwritten


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?


Solution

  • 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: