MutableStateFlow
не уведомляет сборщиков, если обновленное значение равно старому (источник) . Я нашел обходной путь для этого, но он плохо масштабируется для сложных значений.
Обходной путь: дублируйте классы данных с помощью copy()
и списки с помощью toList()
/toMutableList()
.
Пример 1: Простой класс данных WorkoutRoutine
с использованием обходного пути для переименования name
. Здесь нет ничего плохого.
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
Пример 2: Сложный класс данных WorkoutRoutine
с несколькими зависимостями, использование обходного пути для добавления Set
к Exercise
в WorkoutRoutine
: для этого требуется много вызовов copy()
и toMutableList()
, что делает код нечитаемым.
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()) })
}
)
Я пробовал следующее:
MutableStateFlow.valueNotDistinct
, которое принудительно обновляет MutableStateFlow.value
.MutableStateFlow.value
должен быть обнуляемымvar <T> MutableStateFlow<T?>.valueNotDistinct: T?
get() = null
set(newValue) {
value = null
value = newValue
}
MutableSharedFlow
, которое не проверяет на равенствоvalue
Я хочу просто уведомлять сборщиков о каждом выбросе, но я не знаю, как это сделать, потому что для MutableStateFlow не существует функции «принудительного уведомления».
Спасибо за ваш ответ, но я не уверен, что понимаю. Неизменяемость свойств моего класса данных не исправит нечитаемый код, это просто заставит меня написать код, который работает во время компиляции, потому что я не могу изменить значения без использования copy()
или toMutableList
(что хорошо, но код все еще уродлив).
Пожалуйста, обратитесь к моему ответу здесь stackoverflow.com/questions/65442588/…
Учитывая, как вы используете его в своем примере тренировки, я думаю, что правильным подходом было бы действительно скопировать программу тренировки, применить изменения и назначить ее вместо старой. Большинство инструментов, связанных с потоками, таких как потоки, ориентированы на неизменяемые объекты, поэтому это будет «правильный» способ сделать это. Потому что, если потребитель начнет читать ваше значение, пока вы изменяете его в другом потоке, результаты могут быть трудно предсказуемыми и часто приводят к ошибкам.
В документации StateFlow указано следующее:
Сильное объединение на основе равенства
Значения в потоке состояний объединяются с помощью сравнения Any.equals в аналогичный способ с оператором DifferentUntilChanged. Он используется для объединения входящие обновления для значения в MutableStateFlow и для подавления эмиссии значений коллекторам, когда новое значение равно предыдущему издал один. Поведение потока состояния с классами, которые нарушают контракт для Any.equals не указан.
Обходной путь может заключаться в переопределении метода equals
, чтобы всегда возвращать false
. Так что класс данных не поможет в вашем случае.
class WorkoutRoutine() {
...
override fun equals(other: Any?): Boolean {
return false
}
}
Спасибо за Ваш ответ. Это кажется плохой практикой - я начинаю думать, что StateFlow не предназначен для моего варианта использования. Есть ли что-то подобное, что я мог бы использовать - может быть, LiveData?
Вы можете и должны использовать SharedFlow вместо StateFlow, если вам не нужно объединение.
Спасибо, это кажется хорошим решением. Но у SharedFlow
нет свойства value
, которое было бы неплохо иметь. Должен ли я собирать поток каждый раз, когда я хочу получить доступ к текущему значению SharedFlow
s?
Это может привести к распространению ошибок в местах, где выполняется сравнение. Это не очень хорошее решение.
Чуваки, используйте LiveData, это лучше всего подходит для такой ситуации.
MutableStateFlow
— это просто интерфейс, поэтому, если вам не нравится, как работает реализация по умолчанию, вы можете просто написать свою собственную. Вот простая реализация, которая использует MutableSharedFlow
для поддержки. Он не выполняет сравнение, поэтому всегда будет обновляться.
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
}
Используйте функцию MutableStateFlows update
в сочетании с функцией класса данных copy
для безопасного обновления вашего StateFlow:
data class WorkoutRoutine(
var name: String,
var exercises: List<Exercise> = emptyList()
)
data class Exercise(
var sets: List<Set> = emptyList()
)
data class Set(
val weight: Int? = null
)
val workoutRoutine = MutableStateFlow(WorkoutRoutine("Initial"))
// Doesn't notify collectors
workoutRoutine.update { currentState ->
currentState.copy(
exercises = (currentState.exercises.firstOrNull()?.sets ?: emptyList()) + Set()
}
}
прекратите использовать изменяемый класс данных, используйте неизменяемый класс данных и
List
вместоMutableList
, теперь вам не нужно «принудительно» уведомлять