【问题标题】:Jetpack compose mutableStateOf list doesn't trigger re-composition when changing property value in list item classJetpack 撰写 mutableStateOf 列表在更改列表项类中的属性值时不会触发重新组合
【发布时间】:2022-11-30 21:12:43
【问题描述】:

我想我在这里遗漏了 Jetpack Compose 的核心概念。当我尝试更改可组合项内部的 non-constructor data class property 时遇到问题,而此可组合项是观察列表的一部分。

不起作用:(sadProperty 未在构造函数中声明)

data class IntWrapper(val actualInt: Int = 0) {
var sadProperty: Int = 0
}

@Preview
@Composable
fun test() {
var state by remember { mutableStateOf(listOf(IntWrapper(1), IntWrapper(2), IntWrapper(3),IntWrapper(4)))}

    fun onClick(item: IntWrapper) {
        val indexOf = state.indexOf(item)
        val newState = state.minus(item).toMutableList()
        val copy = item.copy()
        copy.sadProperty = Random.nextInt()
        newState.add(indexOf, copy)
        state = newState
    }
    
    Column() {
        for (item in state) {
            Text("ac: ${item.actualInt} sad: ${item.sadProperty}", modifier = Modifier.clickable { onClick(item)})
        }
    }

}

作品:(actualInt在构造函数中声明)

data class IntWrapper(var actualInt: Int = 0) {
var sadProperty: Int = 0
}

@Preview
@Composable
fun test() {
var state by remember { mutableStateOf(listOf(IntWrapper(1), IntWrapper(2), IntWrapper(3),IntWrapper(4)))}

    fun onClick(item: IntWrapper) {
        val indexOf = state.indexOf(item)
        val newState = state.minus(item).toMutableList()
        val copy = item.copy()
        copy.actualInt = Random.nextInt()
        newState.add(indexOf, copy)
        state = newState
    }
    
    Column() {
        for (item in state) {
            Text("ac: ${item.actualInt} sad: ${item.sadProperty}", modifier = Modifier.clickable { onClick(item)})
        }
    }

}

有人可以解释为什么会这样吗?

【问题讨论】:

    标签: android kotlin android-jetpack-compose data-class compose-recomposition


    【解决方案1】:

    这看起来像是Jetpack ComposeKotlin 数据类的问题,请告诉我,我会尽力而为。

    让我们先从 Kotlin 数据类开始

    根据 kotlin docs 关于 Data Class

    编译器自动从所有派生以下成员 在主构造函数中声明的属性:

    • equals()/hashCode() 对
    • toString() 形式为“User(name=John, age=42)”
    • componentN() 函数对应于声明顺序中的属性。
    • 复制()。

    你的IntWrapper数据类有一个一级建造师,类名后面的括号,并在其中声明了 1 个属性。

    data class IntWrapper(val actualInt: Int = 0) {
          var sadProperty: Int = 0
    }
    

    这样,我们可以说,您的IntWrapper 数据类有

    • 1 个组件 (actualInt)
    • IntWrapper(actualInt=?) 形式的 toString()
    • 一个生成的copy()函数
    • 生成的 equals()/hashCode() 对

    并再次基于docs

    编译器只使用主函数中定义的属性 自动生成函数的构造函数。排除一个 来自生成的实现的属性,在 班级机构:

    equals 将仅使用/评估从 IntWrapper's 主构造函数(即 actualInt : Int)声明的属性,而 sadProperty 被排除在外,因为它位于数据类主体的一部分。

    现在考虑以下问题:

    val intWrapper1 = IntWrapper(actualInt = 5)
    intWrapper1.sadProperty = 5
    
    val intWrapper2 = IntWrapper(actualInt = 5)
    intWrapper2.sadProperty = 10
    
    Log.e("AreTheyEqual?", "${intWrapper1 == intWrapper2}")
    

    它打印,

    E/AreTheyEqual?: true
    

    因为 equality 看到两个派生属性具有相同的值 5,所以 sadProperty 被排除在这个比较之外。

    val intWrapper1 = IntWrapper(actualInt = 5)
    intWrapper1.sadProperty = 5
    
    val intWrapper2 = IntWrapper(actualInt = 10)
    intWrapper2.sadProperty = 5
    

    印刷,

    E/AreTheyEqual?: false
    

    因为生成的等号验证生成的组件 (actualInt) 与两个 IntWrapper 实例不同。

    现在转到Jetpack Compose,应用我们对数据类的理解,

    • 第一个test 符合data class 的所有内容,它创建了一个具有新值的新对象,这就是Compose 需要触发re-composition

    • 第二个 test 不会触发 re-compositionCompose 仍然看到相同的 IntWrapper 实例,因为 sadProperty 不是数据类的 equals 操作将使用的生成组件的一部分。

    【讨论】:

    • 我有一种感觉,它会如此明显......不敢相信我错过了它,谢谢你清晰简洁的解释!
    【解决方案2】:

    在 Compose 中,您必须使用以下两种方法之一才能成功执行重组操作:

    1 - 使用 mutableStateListOf(),但是,通过更新列表中项目的值,执行重组操作

    2- 使用您发布的自己的方法

    但是对于第二种方法,你需要告诉Compose,actualInt发生了变化,所以你需要创建一个新的int实例。

    如果你不想这样做,你需要更多地解释你的场景,以便我提供更完整的指南

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-01-25
      • 1970-01-01
      • 2022-10-12
      • 2021-07-18
      • 2020-09-02
      • 1970-01-01
      • 2021-12-11
      相关资源
      最近更新 更多