【问题标题】:Android Jetpack Compose mutableStateListOf not doing RecompositionAndroid Jetpack Compose mutableStateListOf 不做重组
【发布时间】:2021-10-26 05:54:30
【问题描述】:

所以,我在viewModel 中有一个mutableStateListOf

var childTravellersList = mutableStateListOf<TravellersDetails>()

TravellersDetails 是一个数据类,它有一个名为error 的字段。

childTravellersList 在 UI 中用作:

val list = remember{viewModel.childTravellersList}

LazyColumn(state = lazyColumnState) {
    itemsIndexed(list) { index, item ->
        SomeBox(show = if(item.error) true else false)
    }
  }

我在viewModel 中编写了一个函数,该函数将 TravellersDetails 的 error 更新为给定的 indexchildTravellersList 为:

fun update(index){
    childTravellersList[index].error = true
}

所以,每当我调用这个函数时,列表都应该更新。

这会更新列表,但不会触发 UI 重组 ????。 我哪里做错了?

【问题讨论】:

  • 好问题。我也在这个上工作了几天。这在文档中的某处提到过吗?
  • 它在文档中。但我没有看到。谢天谢地,@Philip 是救命稻草!

标签: android kotlin android-jetpack-compose android-jetpack


【解决方案1】:

mutableStateListOf 只能通知添加/删除/替换列表中的某些元素。当您更改列表中的任何类时,可变状态无法知道它。

数据类非常适合在单向数据流中存储不可变状态,因为您始终可以使用copy“更改”它,同时您看到需要将新数据传递给视图或可变状态。所以避免在数据类中使用var 变量,始终将它们声明为val 以防止此类错误。

var childTravellersList = mutableStateListOf<TravellersDetails>()

fun update(index){
    childTravellersList[index] = childTravellersList[index].copy(error = true)
}

另一个问题是您正在使用val list = remember{viewModel.childTravellersList}:它会保存第一个列表值并防止将来更新。使用 ViewModel 可以直接使用itemsIndexed(viewModel.childTravellersList)

【讨论】:

  • 这对我有用。我为此苦苦挣扎了 4 天以上 :) @Philip Dukhov - 出于好奇 - 你是怎么知道的?它是否在文档中的某处提到?我是这样尝试的:childTravellersList[index].count += 1 并期望重新组合触发......
  • @KaranAhuja 你可以在state documentation 中找到这些信息,它非常好而且很完整,但我知道它有点难以立即阅读和理解 =)
【解决方案2】:

仅当您更改列表本身时才会发生重组。你可以这样做。

var childTravellersList by mutableStateOf(emptyList<TravellersDetails>())

fun update(indexToUpdate: Int) {
    childTravellersList = childTravellersList.mapIndexed { index, details ->
        if(indexToUpdate == index) details.copy(error = true)
        else details
    }
}

此外,您无需像在 val list = remember{viewModel.childTravellersList} 中所做的那样在可组合中记住此列表。由于 MutableState 位于视图模型内部,因此它将始终在所有重组中存活。只需在 LazyColumn 中使用 viewModel.childTravellersList

【讨论】:

  • 这可行,但@PhilipDukhov 的解决方案更好。谢谢马恩! ?
【解决方案3】:

使用单一触发机制来更新您的组合。您的列表应该只是作为普通变量可用。您可以添加/删除/删除列表中的项目,甚至更新每个项目的属性。对列表进行这些更新后,您可以通过生成一个随机数来重构您的可组合项,该随机数绑定到可组合项中观察到的可变状态可观察项:

科特林:

class MyViewModel: ViewModel() {
   var childTravellersList = mutableListOf<TravellersDetails>()
   var onUpdate = mutableStateOf(0)

   private fun updateUI() {
      onUpdate.value = (0..1_000_000).random()
   }

   fun update(index){
       childTravellersList[index].error = true
       updateUI()
   }
}

@Composable
fun MyComposableHandler() {

   // This will detect any changes to data and recompose your composable.
   viewmodel.onUpdate.value
  
   MyComposable(
      travelersList = viewmodel.childTravellersList
   )
}

@Composable
fun MyComposable(
    travelersList: List<TravellersDetails>
) {
   
}

您应该避免在视图模型中为需要更新的不同变量创建多个可变状态变量。完全没有必要这样做。只需创建一个可变状态变量,如上所示。每当您需要重新组合您的可组合组件时,您只需更新 UI 函数。您的视图模型中的逻辑应该决定需要更新什么,但将实际更新机制留给可观察的单个可变状态。这是一个干净的模式,可以更轻松地更新您的可组合项。

但是,请记住,您的 UI 通常由层次结构中的许多可组合项组成。当只有一个元素发生变化时,您不希望重新组合整个层次结构。出于这个原因,对于每个需要独立于层次结构中的其他组合进行重组的组合,应该使用一个可变的可观察状态。

使用上述解决方案的另一个好处是您可以轻松更新对象,而无需创建新对象。如果您希望您的可组合对象仅在对象的某个属性发生更改时重新组合,则不能使用可观察的可变状态,因为它们仅检测对象本身的更改,而不是对象属性的更改。这就是为什么您最好使用上面显示的触发方法并在可组合重组时简单地检索更新的对象。

【讨论】:

    猜你喜欢
    • 2021-06-01
    • 1970-01-01
    • 2022-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多