MutableStateFlow принудительно обновляет / уведомляет коллектор

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 не существует функции «принудительного уведомления».

прекратите использовать изменяемый класс данных, используйте неизменяемый класс данных и List вместо MutableList, теперь вам не нужно «принудительно» уведомлять

EpicPandaForce 20.12.2020 18:35

Спасибо за ваш ответ, но я не уверен, что понимаю. Неизменяемость свойств моего класса данных не исправит нечитаемый код, это просто заставит меня написать код, который работает во время компиляции, потому что я не могу изменить значения без использования copy() или toMutableList (что хорошо, но код все еще уродлив).

Noah 20.12.2020 21:09

Пожалуйста, обратитесь к моему ответу здесь stackoverflow.com/questions/65442588/…

Nikola Despotoski 28.12.2020 13:01

Учитывая, как вы используете его в своем примере тренировки, я думаю, что правильным подходом было бы действительно скопировать программу тренировки, применить изменения и назначить ее вместо старой. Большинство инструментов, связанных с потоками, таких как потоки, ориентированы на неизменяемые объекты, поэтому это будет «правильный» способ сделать это. Потому что, если потребитель начнет читать ваше значение, пока вы изменяете его в другом потоке, результаты могут быть трудно предсказуемыми и часто приводят к ошибкам.

Deinlandel 18.01.2021 14:34
Почему в Python есть оператор &quot;pass&quot;?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
Toor - Ангулярный шаблон для бронирования путешествий
Toor - Ангулярный шаблон для бронирования путешествий
Toor - Travel Booking Angular Template один из лучших Travel & Tour booking template in the world. 30+ валидированных HTML5 страниц, которые помогут...
8
4
7 422
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

В документации StateFlow указано следующее:

Сильное объединение на основе равенства

Значения в потоке состояний объединяются с помощью сравнения Any.equals в аналогичный способ с оператором DifferentUntilChanged. Он используется для объединения входящие обновления для значения в MutableStateFlow и для подавления эмиссии значений коллекторам, когда новое значение равно предыдущему издал один. Поведение потока состояния с классами, которые нарушают контракт для Any.equals не указан.

Обходной путь может заключаться в переопределении метода equals, чтобы всегда возвращать false. Так что класс данных не поможет в вашем случае.

class WorkoutRoutine() {
    ...
    override fun equals(other: Any?): Boolean {
        return false
    }    
}

Спасибо за Ваш ответ. Это кажется плохой практикой - я начинаю думать, что StateFlow не предназначен для моего варианта использования. Есть ли что-то подобное, что я мог бы использовать - может быть, LiveData?

Noah 21.12.2020 08:12

Вы можете и должны использовать SharedFlow вместо StateFlow, если вам не нужно объединение.

Roman Elizarov 21.12.2020 09:11

Спасибо, это кажется хорошим решением. Но у SharedFlow нет свойства value, которое было бы неплохо иметь. Должен ли я собирать поток каждый раз, когда я хочу получить доступ к текущему значению SharedFlows?

Noah 21.12.2020 11:13

Это может привести к распространению ошибок в местах, где выполняется сравнение. Это не очень хорошее решение.

Nikola Despotoski 28.12.2020 13:02

Чуваки, используйте LiveData, это лучше всего подходит для такой ситуации.

Gabriel TheCode 26.08.2022 10:32

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()
    }
}

Другие вопросы по теме