HarmonyOS应用开发之界面列表不刷新问题Bug排查记:从现象到解决完整记录

Bug排查在软件开发过程中扮演着至关重要的角色,本文采用日记形式记录了Bug排查的全过程,通过这种方式可以更加真实、详细地记录问题,便于后续追溯和经验沉淀。

Bug背景

在使用HarmonyOS的ArkUI框架开发一个卡片管理应用时,遇到了List列表界面不刷新的问题。具体表现为,虽然数据已经更新,但界面并未同步刷新显示。比如在子页面中明明添加了卡片并更新了数据,但是页面返回后,看到的卡片数量并未发生变化。

环境

  • 设备:HarmonyOS模拟器
  • 系统版本:HarmonyOS 5.0
  • 应用框架:ArkUI

复现条件

在多个界面的状态共享时,使用了@Provider@Consumer装饰器,同时对类和成员变量使用了@ObservedV2@Trace装饰,从而实现了数据的共享和追踪。在添加、删除或更新卡片数据时,数据操作成功,但List列表并未刷新显示最新的数据。

这一行就是问题所在。之前的 uid 一直是同一个。。。,结果导致数据变化,UI不会触发刷新。

之前这里的写法如下:

typescript 复制代码
 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也会随之变化,从而实现界面的正确刷新。

修复代码具体改动
javascript 复制代码
ForEach(this.cardViewModel?.cardDeckList ?? [], (item: CardDeckData,index) => {}, (item: CardDeckData) => item.uid + '_' + JSON.stringify(item))

要保证数据更新后,不但这个key是唯一的,且是变化了的。

测试验证

修改代码后,进行了单元测试和集成测试,确认问题已解决。

坑二:列表数组中元素值改变界面不刷新

再次遇到数据不刷新的问题,通过调试发现需要对cardDeckList进行重新赋值才能刷新界面。

javascript 复制代码
//  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 = name
    rec.desc = desc
    rec.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,这样可以确保对象引用发生变化,从而实现界面的正确刷新。

修复代码具体改动
javascript 复制代码
DBDeckApi.queryAllCardDeck().then((result) => {
  Log.debug(result?.length)
  this.cardDeckList = result ?? [];
  const newList = [...this.cardDeckList];
  this.cardDeckList = newList;
})

测试验证

修改代码后,进行了单元测试和集成测试,确认问题已解决。

影响范围

  • 功能模块:卡片管理模块
  • 用户体验:用户无法看到最新的卡片数量,影响操作感受
  • 业务逻辑:无法准确显示业务数据,影响应用功能

经验总结

技术收获

  • 使用@ObservedV2@Trace装饰类和成员变量,可以方便地追踪和处理数据变化。
  • ForEach循环的key生成器必须能够正确地反映数据变化,从而保证界面的正确刷新。
  • 重新赋值对象以改变其引用,可以确保界面能够正确检测到数据变化并进行刷新。

流程优化

  • 使用代码审查(Code Review)和测试策略可以避免类似的问题。
  • 在开发过程中,应充分考虑框架特性和代码逻辑,避免不必要的"坑"。
  • 通过调试器和日志分析工具等工具,可以快速定位问题并进行修复。

附录

相关技术文档链接

参考工具清单

  • HarmonyOS调试器
  • Log分析工具(如Logcat)
  • Sentry(错误监控系统)

通过以上记录,希望能够帮助其他开发者避免在使用ArkUI时遇到类似的问题,同时也为提升自身的代码质量和开发效率提供了一定的参考。

相关推荐
川石教育3 小时前
软件测试中的Bug知识总结
软件测试·bug·压力测试·缺陷管理·bug分类
hfd19903 小时前
Bug 排查日记:一次曲折的技术解谜之旅
bug
As33100103 小时前
Bug 排查日记:技术难题的攻克之旅
bug
安卓开发者4 小时前
鸿蒙Next的UI国际化与无障碍适老化实践:构建全球包容的数字世界
ui·华为·harmonyos
云天徽上9 小时前
【数据可视化-106】华为2025上半年财报分析:用Python和Pyecharts打造炫酷可视化大屏
开发语言·python·华为·信息可视化·数据分析·pyecharts
爱笑的眼睛1118 小时前
深入剖析 HarmonyOS ArkUI 声明式开发:状态管理艺术与最佳实践
华为·harmonyos
花先锋队长18 小时前
为何三折叠手机只有华为可以?看华为Mate XTs非凡大师就知道
华为·智能手机
安卓开发者19 小时前
鸿蒙NEXT交互机制解析:从输入设备到手势响应的全面指南
microsoft·交互·harmonyos
奶糖不太甜。21 小时前
鸿蒙分布式数据同步失败全解
分布式·华为·harmonyos·数据同步