HarmonyOS应用开发之界面列表不刷新问题Bug排查记:从现象到解决完整记录
Bug排查在软件开发过程中扮演着至关重要的角色,本文采用日记形式记录了Bug排查的全过程,通过这种方式可以更加真实、详细地记录问题,便于后续追溯和经验沉淀。
Bug背景
在使用HarmonyOS的ArkUI框架开发一个卡片管理应用时,遇到了List列表界面不刷新的问题。具体表现为,虽然数据已经更新,但界面并未同步刷新显示。比如在子页面中明明添加了卡片并更新了数据,但是页面返回后,看到的卡片数量并未发生变化。
环境
- 设备:HarmonyOS模拟器
- 系统版本:HarmonyOS 5.0
- 应用框架:ArkUI
复现条件
在多个界面的状态共享时,使用了@Provider
和@Consumer
装饰器,同时对类和成员变量使用了@ObservedV2
和@Trace
装饰,从而实现了数据的共享和追踪。在添加、删除或更新卡片数据时,数据操作成功,但List列表并未刷新显示最新的数据。
这一行就是问题所在。之前的 uid 一直是同一个。。。,结果导致数据变化,UI不会触发刷新。
之前这里的写法如下:
ForEach(this.cardViewModel?.cardDeckList ?? [], (item: CardDeckData,index) => {Row() {CardDeck({})}}, (item: CardDeckData) => item.uid)
问题出在这个ForEach循环的最后一个参数item.uid上。
坑一:ForEach循环的key生成器
在使用ForEach
循环渲染列表时,注意到最后一个参数——key生成器。这个参数用于生成列表项的唯一标识,如果设置不当,就可能导致列表项无法正确更新。
问题
数据已经更新,但通过ForEach
循环渲染的列表界面并未刷新显示最新的数据。
分析与假设
分析关键代码片段,发现ForEach
循环的key生成器仅使用了item.id
,这样虽然可以保证唯一性,但当数据发生变化时,key并未随之更新,导致界面无法刷新。
解决方案
通过修改key生成器的逻辑,将item.id
与更新的成员变量的值进行组合,确保当数据变化时,key也会随之变化,从而实现界面的正确刷新。
修复代码具体改动
ForEach(this.cardViewModel?.cardDeckList ?? [], (item: CardDeckData,index) => {}, (item: CardDeckData) => item.uid + '_' + JSON.stringify(item))
要保证数据更新后,不但这个key是唯一的,且是变化了的。
测试验证
修改代码后,进行了单元测试和集成测试,确认问题已解决。
坑二:列表数组中元素值改变界面不刷新
再次遇到数据不刷新的问题,通过调试发现需要对cardDeckList
进行重新赋值才能刷新界面。
// viewmodol/cardViewModol.ets
import DBDeckApi from "../common/api/deckApi";
import { TBCardDeckEntity } from "../model/TBCardDeck";
import { getCurrentDateTime } from "../utils/dateUtil";
import { Log } from "../utils/logutil";
import { ToastUtil } from "../utils/ToastUtil";@ObservedV2
export class CardDeckData{@Trace uid: number | undefined = 0@Trace name: string| undefined='';@Trace desc: string| undefined = ''// 卡片张数@Trace cardNum: number | undefined = 0// 已学习张数@Trace readNum: number | undefined = 0// 学习时长@Trace timeLen: number | undefined = 0// 创建时间@Trace createTime: string | undefined = ''// 更新时间@Trace updateTime: string | undefined = ''// 开始学习时间@Trace startTime: string | undefined = ''// 结束学习时间@Trace endTime: string | undefined = ''
}@ObservedV2
export class CardViewModel {private static instance: CardViewModel | null = null;@Trace cardDeckList: CardDeckData[] = []static getInstance(): CardViewModel {if (!CardViewModel.instance) {CardViewModel.instance = new CardViewModel();}return CardViewModel.instance;}/*** 添加卡组* @param timelineId 时光轴ID*/addCardDeck(name:string, desc:string) {const rec = new TBCardDeckEntity()rec.name = namerec.desc = descrec.createTime = getCurrentDateTime()DBDeckApi.insertCardDeck(rec).then((number) => {if (number < 0) {Log.error("insertCardDeck error,code=%{public}d", number)ToastUtil.showError("添加失败")} else {Log.debug("insertCardDeck ok,code=%{public}d", number)this.reloadDeckDbData()ToastUtil.showSuccess("添加成功")}})}/*** 从数据库从新加载卡组数据列表*/reloadDeckDbData(){//从数据库获取数据DBDeckApi.queryAllCardDeck().then((result) => {Log.debug(result?.length)this.cardDeckList = result ?? [];const newList = [...this.cardDeckList];this.cardDeckList = newList;// 生成新数组,确保元素为全新实例//const newList = result?.map(item => ({ ...item })) ?? [];/*this.cardDeckList = result?.map(item => ({uid: item.uid ?? 0,name: item.name ?? '',desc: item.desc ?? '',cardNum: item.cardNum,readNum: item.readNum,createTime: item.createTime,updateTime: item.updateTime,startTime: item.startTime,endTime: item.endTime} as CardDeckData)) ?? [];* *//*this.cardDeckList = result?.map(entity => {const data = new CardDeckData();// 显式属性映射(示例)data.uid = entity.uid ?? undefined;data.name = entity.name ?? ''; // 处理 undefined 转空字符串data.desc = entity.desc ?? '';data.cardNum = entity.cardNum ?? 0;data.readNum = entity.readNum ?? 0;data.createTime = entity.createTime!;data.updateTime = entity.updateTime!;return data;})?? [];* */})}
问题
在添加、删除或更新卡片数据后,虽然数据已经变化,但List列表并未刷新显示最新的数据。
必须:非得这么红框里再次赋值才可以刷新啊。显得很低效,但也很无奈。说好的@ObservedV2和@Trace装饰类和成员变量,怎么没观察到成员变量的变化呢?
分析与假设
在reloadDeckDbData
方法中,直接将数据库查询到的数据赋值给cardDeckList
,但由于赋值的对象引用没有变化,导致界面未能检测到数据变化。
解决方案
通过重新生成一个新的数组,并将查询到的数据赋值给新的数组,然后再将新的数组赋值给cardDeckList
,这样可以确保对象引用发生变化,从而实现界面的正确刷新。
修复代码具体改动
DBDeckApi.queryAllCardDeck().then((result) => {Log.debug(result?.length)this.cardDeckList = result ?? [];const newList = [...this.cardDeckList];this.cardDeckList = newList;
})
测试验证
修改代码后,进行了单元测试和集成测试,确认问题已解决。
影响范围
- 功能模块:卡片管理模块
- 用户体验:用户无法看到最新的卡片数量,影响操作感受
- 业务逻辑:无法准确显示业务数据,影响应用功能
经验总结
技术收获
- 使用
@ObservedV2
和@Trace
装饰类和成员变量,可以方便地追踪和处理数据变化。 ForEach
循环的key生成器必须能够正确地反映数据变化,从而保证界面的正确刷新。- 重新赋值对象以改变其引用,可以确保界面能够正确检测到数据变化并进行刷新。
流程优化
- 使用代码审查(Code Review)和测试策略可以避免类似的问题。
- 在开发过程中,应充分考虑框架特性和代码逻辑,避免不必要的“坑”。
- 通过调试器和日志分析工具等工具,可以快速定位问题并进行修复。
附录
相关技术文档链接
- ArkUI ForEach文档
- HarmonyOS数据共享文档
参考工具清单
- HarmonyOS调试器
- Log分析工具(如Logcat)
- Sentry(错误监控系统)
通过以上记录,希望能够帮助其他开发者避免在使用ArkUI时遇到类似的问题,同时也为提升自身的代码质量和开发效率提供了一定的参考。