前言

收藏按钮不是改一下心形图标那么简单。桌面上可能有多张音乐卡片,应用页面里也可能正在显示歌曲详情。用户在一张卡片上点收藏,其他地方也要同步。

当前项目用 SongRdbHelper + FormUtils + eventHub 把这件事串起来。

效果图

收藏状态虽然只是一个小图标,但它要同步到所有音乐卡片和应用页面,不能只改当前这一张。

音乐收藏同步效果

先看这些文件

  • entry/src/main/ets/widget/pages/MusicCard.ets
  • entry/src/main/ets/utils/ActionUtils.ets
  • entry/src/main/ets/utils/CardActionHandler.ets
  • entry/src/main/ets/database/SongRdbHelper.ets
  • entry/src/main/ets/utils/FormUtils.ets

卡片上的收藏按钮

MusicCard.ets 里有:

SymbolGlyph(this.isCollected ? $r('sys.symbol.heart_fill') : $r('sys.symbol.heart'))
  .fontSize(IconSize.MEDIUM)
  .symbolEffect(new ReplaceSymbolEffect(EffectScope.WHOLE), true)
  .onClick(() => {
    if (this.isCollected) {
      ActionUtils.collectAction(this, CollectAction.UNCOLLECTED, this.formId, this.songId);
    } else {
      ActionUtils.collectAction(this, CollectAction.COLLECTED, this.formId, this.songId);
    }
  });

这段代码不直接改 isCollected

它只是根据当前状态,发一个收藏或取消收藏的动作。真正状态由应用侧处理后再刷新回来。

A hand-drawn doodle illustration on pure white pap

collectAction 发了什么

ActionUtils.ets 里:

public collectAction(component: object, type: string, formId: string, songId: string): void {
  postCardAction(component, {
    action: FormCarAction.CALL,
    abilityName: ENTRY_ABILITY,
    params: {
      method: 'cardAction',
      actionType: CardActionType.COLLECT_ACTION,
      collectActionType: type,
      formId: formId,
      songId: songId,
    },
  });
}

收藏是应用业务,所以这里用 CALL 发给 ENTRY_ABILITY

参数里最重要的是:

  • collectActionType:收藏还是取消收藏。
  • songId:哪首歌。
  • formId:从哪张卡片发起。

CardActionHandler 更新数据库

CardActionHandler.ets 里:

private handleCollectAction(params: Record<string, string>): void {
  if (params.collectActionType && this.context) {
    let songRdbHelper = SongRdbHelper.getInstance(this.context);
    if (params.collectActionType === CollectAction.COLLECTED) {
      songRdbHelper.updateCollected(params.songId, CollectAction.COLLECTED);
      FormUtils.updateCardCollectStatus(this.context, true);
      this.context.eventHub.emit('collected', params.songId, CollectAction.COLLECTED);
    } else {
      songRdbHelper.updateCollected(params.songId, CollectAction.UNCOLLECTED);
      FormUtils.updateCardCollectStatus(this.context, false);
      this.context.eventHub.emit('collected', params.songId, CollectAction.UNCOLLECTED);
    }
  }
}

A hand-drawn doodle illustration on pure white pap

这段代码做了三件事。

第一,更新 RDB:

songRdbHelper.updateCollected(params.songId, CollectAction.COLLECTED);

这保证收藏状态有持久化结果。

第二,刷新所有音乐卡片:

FormUtils.updateCardCollectStatus(this.context, true);

第三,通知应用内页面:

this.context.eventHub.emit('collected', params.songId, CollectAction.COLLECTED);

为什么要同步所有音乐卡

用户桌面可能放了多张 MusicCard。如果只更新当前 formId,其他音乐卡还是旧状态,就会出现“同一首歌,有的显示收藏,有的没收藏”。

项目里 FormUtils.updateCardCollectStatus() 会查出所有 MusicCard

let formList: FormInfo[] = await FormRdbHelper.getInstance(context).queryFormByName('MusicCard');
formList.forEach((formInfo) => {
  this.updateForm(formInfo.formId, updateData);
});

这就是批量同步。

为什么还要 eventHub

updateForm() 只能刷新桌面卡片。应用内页面不会自动收到桌面卡片的更新。

所以项目用:

this.context.eventHub.emit('collected', params.songId, CollectAction.COLLECTED);

应用页面可以监听这个事件,刷新自己的 UI。

小白可以这样记

卡片点击收藏
  -> ActionUtils 发 COLLECT_ACTION
  -> CardActionHandler 接收
  -> SongRdbHelper 更新数据库
  -> FormUtils 刷新所有桌面音乐卡
  -> eventHub 通知应用页面

这就是收藏同步的完整闭环。

常见坑

1. 只改当前卡片 UI

这样会导致其他卡片不同步。收藏状态必须回到应用侧统一处理。

2. 不写数据库

应用重启后收藏状态丢失。

3. 忘记通知应用页面

桌面卡片更新了,但应用内详情页还是旧状态。

写在最后

收藏功能看起来小,但它是非常典型的多端同步问题。

小白记住:最终状态放 RDB,桌面卡片靠 FormUtils 刷新,应用页面靠事件通知。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐