Я получил DerivateStateOf, который использует 3 разных состояния: heightValue, isOk и selectedOption, который был деконструирован в (selectedOption, onOptionSelected) и, возможно, является причиной этого. Он не будет реагировать на изменения в selectedOption, если я не укажу это в remember(selectedOption). Я пытаюсь понять, почему и было ли мое исправление правильным, или мне следовало поступить с деконструкцией по-другому.
val radioOptions = listOf("A", "B", "C", "D")
var heightValue by remember { mutableIntStateOf() }
var isOk by remember { mutableStateOf(true) }
val (selectedOption, onOptionSelected) = remember { mutableStateOf(radioOptions[0]) }
//val calculatedValue by remember() { /*This won't work. Next line is the fix*/
val calculatedValue by remember(selectedOption) {
derivedStateOf {
calcNewValueInch(
pickerValue = heightValue,
isOk = isOk,
selectedOption = selectedOption,
)
}
}
derivedStateOf работает только с объектами State. selectedOption не является состоянием, поэтому никакие изменения в нем не будут обнаружены.
Давайте посмотрим на это более подробно. В вашем оригинальном, неработающем примере только с remember(), derivedStateOf выполняется в первой композиции. Он также выполняет лямбду и отслеживает все использованные объекты State. Мы ожидаем, что это будет следующее:
heightValueisOkselectedOptionЗатем результат derivedStateOfremember фиксируется, поэтому он не будет выполняться снова при рекомпозиции. Это нормально: созданное производное состояние автоматически обновляется при изменении любого из состояний в списке выше.
Вот только это работает только для 1 и 2, но не работает для 3.
Поскольку derivedStateOf отслеживает только фактические объекты State, которые использовались в его лямбде, совершенно очевидно, что он не будет обновляться при 3. изменении: selectedOption — это просто строка, а не состояние. Более интересный вопрос заключается в том, почему это работает для 1. и 2., поскольку они также не являются состояниями, а просто Int и логическое значение. Но это не совсем так. На самом деле это делегаты, которые вы получили с помощью ключевого слова by. Это означает, что вы можете использовать их как простые Int и логические значения, но всякий раз, когда их значение считывается, фактически выполняется функция getValue делегированного объекта. Когда их значение установлено, вместо этого будет вызываться setValue. Котлин скрывает все это от вас, поэтому ваш код выглядит чище, но именно это и происходит на самом деле при доступе к делегату. В заключение отметим, что 1 и 2 являются делегатами состояния и, следовательно, derivedStateOf могут видеть эти состояния и отслеживать их изменения.
selectedOption, однако был создан путем деконструкции MutableState. В результате у вас останется простая строка, содержащая значение на момент деконструкции. Хитрость заключается в том, что в большинстве случаев этого будет достаточно, поэтому он не сильно отличается от делегированных состояний:
Text(selectedOption)
Это будет работать должным образом и обновляться каждый раз, когда базовое состояние selectedOption изменяется. Это связано с тем, что среда выполнения Compose отслеживает все чтения состояний, и деконструкция состояния считается таковой. Когда состояние изменяется, функция перекомпоновывается, поэтому деконструкция повторяется и selectedOption обновляется. Но derivedStateOf лежит за remember и на него не влияют такие перекомпозиции. Вот почему он сам отслеживает все объекты State. Но он никогда не видел деконструкции состояния за selectedOption, поэтому видит только результат, который представляет собой простую строку, а не состояние.
Теперь мы можем пересмотреть приведенный выше список государств, которые derivedStateOf отслеживают:
heightValueisOkselectedOption, так как это простая строка.Единственные состояния, к которым есть доступ в лямбде, — это 1. и 2., поэтому что бы вы ни делали с 3., это не повлияет на derivedStateOf.
Когда вы используете remember(selectedOption), кажется, что это работает, но на самом деле происходит то, что все производное состояние выбрасывается каждый раз при каждом изменении selectedOption и создается новое. Лямбда-выражение выполняется снова, и создается приведенный выше список объектов состояния, за которыми следует наблюдать на предмет изменений, но не имеет значения, что 3. на самом деле его нет в списке, потому что вы просто создаете новый производный объект состояния, когда он изменяется, вы не позволяете текущему производному объекту состояния обновляться.
Правильное решение проблемы — оставить Государство позади selectedOption. Либо делегируя это, как вы это делали с 1. и 2., либо, если по какой-либо причине вы не хотите этого делать, примерно так:
val selectedOptionState = remember { mutableStateOf(radioOptions[0]) }
val (selectedOption, onOptionSelected) = selectedOptionState
val calculatedValue by remember {
derivedStateOf {
calcNewValueInch(
pickerValue = heightValue,
isOk = isOk,
selectedOption = selectedOptionState.value, // this accesses the State now
)
}
}
В этом случае я бы посоветовал вам полностью удалить деконструкцию и получить доступ только к selectedOptionState.value, чтобы читатель вашего кода не задавался вопросом, почему вы иногда используете это, а иногда вы используете это для доступа к одному и тому же состоянию.
Я имел в виду val selectedOption by remember { mutableStateOf(radioOptions[0]) }
Фантастическое объяснение. Спасибо. И это правда, я также думаю, стоит ли того деконструкция, какой бы удобной она ни была, если я теряю State, которое мне тоже нужно использовать. Можете ли вы объяснить, что вы подразумеваете под «оставить Государство позади
selectedOption... делегируя его, как вы это сделали с 1»? Вы имеете в виду что-то вроде этого: val selectedOptionState, помните {mutableStateOf(selectedOption)} По сути, возьмите строку и снова превратите ее в состояние?