2

I am using MutableStateFlow. My flow type is sealed class with different states (Loading, Success, Error, etc). Initial value of my flow is empty:

private val _updateDepartmentsState = MutableStateFlow<DepartmentFetchState>(DepartmentFetchState.Empty) 

In my repository I'm emitting different states. For example :

   suspend fun updateDepartments() {
      _updateDepartmentsState.emit(DepartmentFetchState.Loading)
      try {
        remoteDataSource.updateDepartments()
        // here some code
        _updateDepartmentsState.emit(DepartmentFetchState.Success(data))
      } catch(e: NetworkException) {
         _updateDepartmentsState.emit(DepartmentFetchState.Error)
       }
    }

Also in my repository I have read only flow:

val updateDepartmentsState = _updateDepartmentsState.asStateFlow() 

In view model I'm collect flow via interactor. My code inside view model:

 updateDepartmentsState.emitAll(
            interactor
                .updateState // state flow (`updateDepartmentsState` ) from repository via interactor
                .map { state->
                    when (state) {
                        DepartmentFetchState.Loading -> {}
                        DepartmentFetchState.Error-> {}
                        ...
  }
                }.also {
                    interactor.updateDepartments() // call updateDepartments() from repository via interator
                }

As I understand from the documentation, after we have completed the collect, we must get the initial value. But it doesn't happen. Moreover, I do not receive state DepartmentFetchState.Loading. I receive only last state - DepartmentFetchState.Success.

But the most interesting thing is that if I re-call the code from the view model (for example, when updating by swipe), then I get the DepartmentFetchState.Loading state, and then the DepartmentFetchState.Success state, as expected.

I don't understand why on the first call, the initial value that I set when initializing the flow and the DepartmentFetchState.Loading state are lost.

Please, help me(

testivanivan
  • 967
  • 13
  • 36
  • If you check the [documentation](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/) you will see that it mentions that updates are [conflated](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/conflate.html). – gpunto Apr 09 '22 at 21:35
  • 1
    @gpunto, Perhaps I did not quite understand the meaning of this fragment from the documentation ... As far as I understand, repeated events are discarded. I don't have recurring events. Also, I didn't understand why on the first call of collect from VM I only get the last event that was emitted. And on the second call, I get all the emitted events. – testivanivan Apr 09 '22 at 21:48
  • The documentation for `conflate` says: "The effect of this is that emitter is never suspended due to a slow collector, but collector always gets the most recent value emitted." Basically the purpose of a `StateFlow` is to provide a state, not to propagate every event. – gpunto Apr 10 '22 at 08:54
  • 1
    @gpunto, What could I use instead of a StateFlow to receive every event that was emitted ? – testivanivan Apr 10 '22 at 12:22
  • 3
    First I would suggest to ask yourself if you really need all emissions. Usually on the UI you only need the latest state because there's no point in trying to render outdated ones. If you still want to receive all emissions you can use a `SharedFlow`. – gpunto Apr 10 '22 at 14:07
  • @gpunto I've experienced similar problems, Finally someone saved me, thank you! you mentioned this [documention](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/#:~:text=parameters%20and%20the-,distinctuntilchanged,-operator%20is%20applied) said that `StateFlow` are always `distinctUntilChanged`, that is key for me. – ToddQ Jun 25 '22 at 04:54

2 Answers2

2

What you have described is the intended purposes of StateFlow.

Moreover, I do not receive state DepartmentFetchState.Loading. I receive only last state - DepartmentFetchState.Success.

This is because StateFlow is a state-holder observable flow that emits the current and new state updates to its collectors. And by the time you start collecting the flow your updateDepartments() has already been finished with a DepartmentFetchState.Success. Means that from this moment onward when you collect the flow, the current state is DepartmentFetchState.Success.

And then:

But the most interesting thing is that if I re-call the code from the view model (for example, when updating by swipe), then I get the DepartmentFetchState.Loading state, and then the DepartmentFetchState.Success state, as expected.

When you re-call the code you receive the result as expected, that's because you have been collecting from the flow as your updateDepartments() execute it will emit the current state DepartmentFetchState.Loading, and then DepartmentFetchState.Success respectively.

Sovathna Hong
  • 404
  • 4
  • 5
0

You can add a yield() after the emit, which will allow the collector to run it's collect method, as so:

 suspend fun updateDepartments() {
      _updateDepartmentsState.emit(DepartmentFetchState.Loading)
      try {
        remoteDataSource.updateDepartments()
        // here some code
        _updateDepartmentsState.emit(DepartmentFetchState.Success(data))

        yield() // <-- allows the collector to run after each update
      } catch(e: NetworkException) {
         _updateDepartmentsState.emit(DepartmentFetchState.Error)
       }
    }

I'm not sure why you would need to do this in your case, but it is useful when you want each emission to be processed in some way, so it does have a good use-case.

Just know that any processing will be forced to be re-processed, possibly unnecessarily using this approach.

RealityExpander
  • 133
  • 1
  • 8