Jetpack Compose: принудительная перекомпоновка при изменении состояния из-за другого действия
У меня есть два действия: и . Оба они используют Jetpack Compose для отображения пользовательского интерфейса. В первом элемент (скажем,Text
) должен быть показан с переменнойshowtText
являетсяtrue
и скрыто, если оно ложно. Я достигаю этого, используя:
@Composable
fun MyUI(){
AnimatedVisibility(visible = viewModel.showText) {
Text("Some text")
}
}
Моя переменная определена в ViewModel, например:
val showText by mutableStateOf(false)
Таким образом, каждый раз, когда я изменяю значение в моей ViewModel, когда оно видимо, оно появляется или исчезает. Я хочу добиться следующего:
- Из которого пользователь может перейти к (первый работает в фоновом режиме, он не очищается от стека).
- Там есть
Switch
который может переключать значение. - Когда пользователь нажимает "назад", вызывает
finish()
и он исчезает, показывая снова (был в стеке). - Если пользователь переключил значение в , текст должен быть скрыт или показан автоматически, так как состояние изменилось.
Проблема в том, что хотя значение действительно меняется, пользовательский интерфейс не реагирует на эти изменения. Я проверил эту рекомпозициюActivityOne.kt
пользовательский интерфейс не происходит послеActivityTwo.kt
завершен, потому что он не помнит предыдущее состояниеshowText
.
Как я мог этого добиться? Заранее спасибо!
РЕДАКТИРОВАТЬ 1 Проблема немного сложнее, как я объяснил, но основа та же. Вот мой полный код:
ActivityOne.kt:
class ActivityOne : ComponentActivity() {
private lateinit var vm: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vmFactory = MainViewModelFactory()
vm = ViewModelProvider(this, vmFactory).get(MainViewModel::class.java)
setContent {
MyTheme {
Surface {
MyUI()
}
}
}
}
@Composable
fun MyUI() {
AnimatedVisibility(visible = myPrefs.showText) {
Text("Some text")
}
}
}
MainViewModel.kt:
class MainViewModel : ViewModel() {
companion object {
val myPrefs by mutableStateOf( AppPrefs() )
}
}
ActivityTwo.kt:
class ActivityTwo : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyTheme {
Surface {
MyUI2()
}
}
}
}
@Composable
fun MyUI2() {
val showText = remember {
mutableStateOf(MainViewModel.myPrefs.showText)
}
Switch(
checked = showText.value,
onCheckedChange = {
showText.value = it
MainViewModel.myPrefs.showText = it
}
)
}
}
AppPrefs.kt:
class AppPrefs {
var showText: Boolean = false
}
1 ответ
Рекомпозиция не происходит, потому что отслеживается состояние, которое никогда не меняется (сама ссылка никогда не меняется).
Вы можете добиться рекомпозиции несколькими способами.
Здесь я буду предполагать, что ваш класс либо уже содержит более одного члена/поля, либо вы хотели бы легко добавить в него больше членов в будущем. Вот почему я добавил к нему еще 2 свойства в приведенных ниже примерах и предложил только решения, в которых добавление дополнительных членов не повлияет на существующий код.
Опция 1
Если вы можете изменить класс, чтобы отслеживать состояние каждого свойства по отдельности, внесите эти изменения.
class MainViewModel : ViewModel() {
companion object {
val myPrefs = AppPrefs()
}
}
class AppPrefs {
var showText by mutableStateOf(false)
var prop2 by mutableStateOf(0)
var prop3: String? by mutableStateOf(null)
}
Все остальное остается прежним. Здесь рекомпозиция снова работает, потому что теперь состояние отслеживается для элементов, которые фактически изменяются.
Вариант 2
Если вы не можете (или не хотите) иметьmutableStateOf
внутри можно изменитьAppPrefs
от нормальногоclass
к классу данных . Таким образом, вы автоматически реализуете функцию (наряду сequals
,hashCode
,toString
иcomponentN
для поддержки деструктуризации).
class MainViewModel : ViewModel() {
companion object {
// the only change here is val -> var
var myPrefs by mutableStateOf(AppPrefs())
}
}
// data class instead of a normal class
data class AppPrefs(
val showText: Boolean = false,
val prop2: Int = 0,
val prop3: String? = null,
)
В этом случае вы также должны изменить способ изменения значенияmyPrefs
. Это то, что заставляет рекомпозицию снова работать правильно
onCheckedChange = {
showText.value = it
MainViewModel.myPrefs = MainViewModel.myPrefs.copy(showText = it)
}
Если вы не можете использовать класс данных, вы все равно можете использовать вариант 2, но вы реализуетеcopy
функция в вашем существующем классе
// normal class with a copy function
class AppPrefs(
val showText: Boolean = false,
val prop2: Int = 0,
val prop3: String? = null,
) {
fun copy(
showText: Boolean = this.showText,
prop2: Int = this.prop2,
prop3: String? = this.prop3,
) = AppPrefs(showText, prop2, prop3)
}
В качестве бонуса, если вы выберете любое из вышеперечисленных решений, вы даже сможете упростить существующий код дляMyUI2
компонуемый, и рекомпозиция все равно будет работать.
Пример:
@Composable
fun MyUI2() {
val showText = MainViewModel.myPrefs.showText
Switch(
checked = showText,
onCheckedChange = {
// for Option 1
MainViewModel.myPrefs.showText = it
// for Option 2
MainViewModel.myPrefs = MainViewModel.myPrefs.copy(showText = it)
}
)
}