Home > front end >  StateFlow only gets collected by one of many collectors
StateFlow only gets collected by one of many collectors

Time:12-06

I want to notify all ViewModels in my app about the event when a user gets blocked.

My UserState:

    private val _refetchAllLists: MutableStateFlow<Boolean> = MutableStateFlow(false)
    val refetchAllLists: StateFlow<Boolean> = _refetchAllLists

   fun setRefetchAllLists(bool: Boolean){
        _refetchAllLists.value = bool
    }

Here my Repository:

override fun refetchAllLists(): StateFlow<Boolean> {
    return userState.refetchAllLists
}

and this is how I collect it inside all of my ViewModels:

init {
    viewModelScope.launch {
        repository.refetchAllLists().collect(){
            if(it){ 
            }
        }
    }
}

Now whenever a user gets blocked I set the MutableStateFlow Boolean to true but only one of all collectors (the one whose viewmodel gets created first) is getting notified.

CodePudding user response:

StateFlows are conflated which means a collector only gets the most recent value and values can be dropped if it doesn't collect it in time. Since you're setting the value back to false from one of the collectors, the true value might get dropped before the other collectors get to see it.

If your goal is to have an event that all collectors get if and only if they are subscribed at the time that the event is broadcast, then I think you can do this with a SharedFlow<Unit> that has no replay. We can set it to drop the oldest value since we don't care about dropped values in the event of emissions happening faster than colllection. This will allow us to use tryEmit() without risk of failing to emit a refresh.

private val _refetchAllLists: MutableSharedFlow<Unit> = 
    MutableSharedFlow(onBufferOverflow = BufferOverflow.DROP_OLDEST)
val refetchAllLists: StateFlow<Boolean> get() = _refetchAllLists

fun triggerRefetchAllLists() {
    _refetchAllLists.tryEmit(Unit)
}

You might consider keeping this flow private and using it as the basis for all your other flows that need to refresh. Then you won't have to externally worry about that. If you do it this way, you should give the shared flow a replay of 1 to ensure there is always something initially emitted. Something like this:

private val refetchAllLists: MutableSharedFlow<Unit> = 
    MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST).apply {
        tryEmit(Unit) // seed with initial value
    }

// Change this:
fun getSomeRepoFlow(someId: Long): Flow<Something> =
    someApiFetch(someId)

// to this:
fun getSomeRepoFlow(someId: Long): Flow<Something> =
    refetchAllLists.flatMapLatest {
        someApiFetch(someId)
    }
  • Related