JetPack Compose LazyColumn 数据更新但 UI 未更新的可能原因

892 阅读2分钟

1. 数据更新时直接修改了原有对象的属性

Compose 的重组执行条件为:判断上一次的缓存值与这一次的传入值是否“相同”,值不同 ==> 执行重组,值相同 ==> 跳过重组,对象使用 equals 比较,基础类型直接比较值

比如我们用如下方式来更新某个 新闻 的点击次数:

class News{
    var id:String = ""
    var name:String = ""
    var clickCount:Int = 0
    ...
}

// inside ViewModel
fun updateNewsClickCount(id:String, newClickCount:Int){
    _uiState.update{ 
        it.copy(newsList = it.newsList.find{ it.id == id }?.clickCount = newClickCount) 
    }
}
// Compose UI
@composable
fun NewsPage(uiState: NewsUiState){
    LazyColumn{
        items(uiState.newsList){
            NewsCard(it)
        }
    }
}


Compose 节点缓存的对象 就是 _uiState.value.newsList中的对象,我们如果直接对对象属性进行编辑,则之后进行比较时,当然不会执行重组。

建议:使用 data class,更新属性时使用自带的 copy 方法完成更新,或使用 clone 等其他方式,只要每次返回的对象不相等即可。

2. 数据类被重写了 equals

开发中经常看见有重写equals并只判断id就返回的代码逻辑,我个人看见的绝大部分使用初衷都是为了能使用 list.contains()及 list.indexOf()这些类似的简便操作,虽然这也满足“自反性、一致性...”等等要求,但其实这并不规范,或者说就是有问题。

普通 View 因为是主动触发 UI 更新,所以没觉得这有多大问题,即使遇到 DiffUtil 这种需要在内部判断内容相等的,比较一下 equals 也就可以了。

但 Compose 是数据驱动 UI,通过数据是否改变决定UI是否刷新,没有DiffUtil这种给我们添加自定义判断条件的地方,所以如果 equals 一直相等,则会一直跳过重组过程。

建议:使用 data class,或在equalshashCode中补全所有属性。

3. TODO

过于频繁更新 #1 中的 newsList,UI 也不会及时响应,而且假如最后是 emptyList 会直接崩溃