Jetpack Compose: принудительная перекомпоновка при изменении состояния из-за другого действия

У меня есть два действия: и . Оба они используют Jetpack Compose для отображения пользовательского интерфейса. В первом элемент (скажем,Text) должен быть показан с переменнойshowtTextявляетсяtrueи скрыто, если оно ложно. Я достигаю этого, используя:

      @Composable
fun MyUI(){   
 AnimatedVisibility(visible = viewModel.showText) {
      Text("Some text")      
   }
}

Моя переменная определена в ViewModel, например:

      val showText by mutableStateOf(false)

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

  1. Из которого пользователь может перейти к (первый работает в фоновом режиме, он не очищается от стека).
  2. Там естьSwitchкоторый может переключать значение.
  3. Когда пользователь нажимает "назад", вызываетfinish()и он исчезает, показывая снова (был в стеке).
  4. Если пользователь переключил значение в , текст должен быть скрыт или показан автоматически, так как состояние изменилось.

Проблема в том, что хотя значение действительно меняется, пользовательский интерфейс не реагирует на эти изменения. Я проверил эту рекомпозицию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)
        }
    )
}
Другие вопросы по тегам