Home > Enterprise >  Updating MutableStateFlow without emitting to collectors
Updating MutableStateFlow without emitting to collectors

Time:04-01

In an Android project, we are currently trying to switch from LiveData to StateFlow in our viewmodels. But for some rare cases, we need to update our state without notifying the collectors about the change. It might sound weird when we think of the working mechanism of flows, but I want to learn if it's a doable thing or not. Any real solution or workaround would be appreciated.

CodePudding user response:

Instead of exposing the state flow directly, we can expose another flow that filters the items according to our needs.

For example, we can keep the shouldEmit flag inside emitted items. Or use any other filtering logic:

suspend fun main(): Unit = coroutineScope {
    launch {
        stateFlow.collect {
            println("Collected: $it")
        }
    }

    delay(100)
    setState(1)
    delay(100)
    setState(2)
    delay(100)
    setState(3, shouldEmit = false)
    delay(100)
    setState(4)
    delay(100)
    setState(5)
    delay(100)
}

private val _stateFlow = MutableStateFlow(EmittableValue(0))

val stateFlow = _stateFlow.filter { it.shouldEmit }
    .map { it.value }

fun setState(value: Int, shouldEmit: Boolean = true) {
    _stateFlow.value = EmittableValue(value, shouldEmit)
}

private data class EmittableValue<T>(
    val value: T,
    val shouldEmit: Boolean = true
)

We can also keep the shouldEmit flag in the object and switch it on/off to temporarily disable emissions.

If you need to expose StateFlow and not just Flow, this should also be possible, but you need to decide if ignored emissions should affect its value or not.

CodePudding user response:

If you don't need to react to the true state anywhere, but only the publicly emitted state, I would store the true state in a property directly instead of a MutableStateFlow.

private var trueState: MyState = MyState(someDefault)
private val _publicState = MutableStateFlow<MyState>()
val publicstate = _publicState.asStateFlow()

fun updateState(newState: MyState, shouldEmitPublicly: Boolean) {
    trueState = newState
    if (shouldEmitPublicly) {
        _publicState.value = newState
    }
}

If you do need to react to it, one alternative to a wrapper class and filtering (@broot's solution) would be to simply keep two separate StateFlows.

  • Related