'MutableStateFlow force update / notify collector
MutableStateFlow
doesn't notify collectors if the updated value equals the old value (source). I've found a workaround for this, but it doesn't scale well for complex values.
Workaround: Duplicate data classes with copy()
and lists with toList()
/toMutableList()
.
Example 1: Simple data class WorkoutRoutine
using workaround to rename name
. Nothing wrong here.
data class WorkoutRoutine(
var name: String,
)
val workoutRoutine = MutableStateFlow(WorkoutRoutine("Initial"))
workoutRoutine.value.name = "Updated" // Doesn't notify collectors
workoutRoutine.value = workoutRoutine.value.copy(name = "Updated") // Workaround: works
Example 2: Complex data class WorkoutRoutine
with multiple dependencies, using workaround to add a Set
to an Exercise
in the WorkoutRoutine
: This requires a lot of copy()
and toMutableList()
calls, which make the code unreadable.
data class WorkoutRoutine(
var name: String,
var exercises: MutableList<Exercise> = mutableListOf(Exercise())
)
data class Exercise(
var sets: MutableList<Set> = mutableListOf(Set())
)
data class Set(
var weight: Int? = null
)
val workoutRoutine = MutableStateFlow(WorkoutRoutine("Initial"))
// Doesn't notify collectors
workoutRoutine.value.apply {
exercises = exercises.also {
it[0].sets.add(Set())
}
}
// Workaround: works
workoutRoutine.value = workoutRoutine.value.copy(
exercises = workoutRoutine.value.exercises.toMutableList().also {
it[0] = it[0].copy(sets = it[0].sets.apply { add(Set()) })
}
)
I've tried the following:
- Adding an extension value
MutableStateFlow.valueNotDistinct
that force updatesMutableStateFlow.value
.
-> Problem:MutableStateFlow.value
has to be nullable
var <T> MutableStateFlow<T?>.valueNotDistinct: T?
get() = null
set(newValue) {
value = null
value = newValue
}
- Using
MutableSharedFlow
, which doesn't check for equality
-> Problem: Not as performant, doesn't havevalue
property
What I want is to simply notify collectors on every emit, but I don't know how to do that, because there doesn't seem to be a "force notify" function for MutableStateFlow.
Solution 1:[1]
StateFlow documentation states this:
Strong equality-based conflation
Values in state flow are conflated using Any.equals comparison in a similar way to distinctUntilChanged operator. It is used to conflate incoming updates to value in MutableStateFlow and to suppress emission of the values to collectors when new value is equal to the previously emitted one. State flow behavior with classes that violate the contract for Any.equals is unspecified.
A workaround could be overriding the equals
method to always return false
. So a data class doesn't help in your case.
class WorkoutRoutine() {
...
override fun equals(other: Any?): Boolean {
return false
}
}
Solution 2:[2]
MutableStateFlow
is just an interface, so if you don't like how the default implementation works you can just write your own. Here is a simple implementation that uses a MutableSharedFlow
to back it. It doesn't do the comparison, so it will always update.
class NoCompareMutableStateFlow<T>(
value: T
) : MutableStateFlow<T> {
override var value: T = value
set(value) {
field = value
innerFlow.tryEmit(value)
}
private val innerFlow = MutableSharedFlow<T>(replay = 1)
override fun compareAndSet(expect: T, update: T): Boolean {
value = update
return true
}
override suspend fun emit(value: T) {
this.value = value
}
override fun tryEmit(value: T): Boolean {
this.value = value
return true
}
override val subscriptionCount: StateFlow<Int> = innerFlow.subscriptionCount
@ExperimentalCoroutinesApi override fun resetReplayCache() = innerFlow.resetReplayCache()
override suspend fun collect(collector: FlowCollector<T>): Nothing = innerFlow.collect(collector)
override val replayCache: List<T> = innerFlow.replayCache
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Glenn Sandoval |
Solution 2 |