异步请求数据刷新卡片 和 应用内数据改变同步刷新卡片的实现 原创

X叶域Q
发布于 2024-12-26 22:43
浏览
0收藏

前言

下面代码基OpenHarmony API12,在DAYU200运行结果
HarmonyOS 应用如何修改为 OpenHarmony 应用(API12):

如何创建卡片服务卡片开发指南:打造更便捷的用户体验 (基于ArkTs UI 的卡片开发)

面向需求

  • 卡片无法发送网络请求想在卡片中获取接口数据
  • 想通过应用数据变化同步卡片数据变化
  • 卡片中无法使用延时函数并且卡片动画效果最多持续1秒,想要使用大于1秒的延迟
  • 另外 formExtensionAbility 进程不能常驻后台,即在卡片生命周期回调函数中无法处理长时间的任务

结果展示(有点糊hh)
异步请求数据刷新卡片 和 应用内数据改变同步刷新卡片的实现-鸿蒙开发者社区
整体思路

  1. 要通过应用改变卡片数据,那么应用需要获取到卡片ID
  2. 通过FormExtensionAbility只要知道卡片ID即可更新卡片数据
  3. 卡片通过call事件和应用UIAbility交互
  4. @Watch装饰器对状态变量的监听达到改变了就刷新的目的

全部代码,见最后仓库地址
需要如下权限,别忘了加哦

  1. 异步请求获取数据需要网络权限
  2. 卡片使用call事件拉起指定UIAbility到后台,需要后台运行权限

entry/src/main/module.json5

"requestPermissions": [
    {
        // 网络权限
    	"name": "ohos.permission.INTERNET"
    },
    {
        // 后台运行权限
    	"name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
    }
],

一、应用获取并持久化卡片ID

大致流程如下

  1. 卡片生命周期函数 onAddForm中将卡片 ID传递给卡片
  2. 卡片通过call事件主动上传卡片 ID到应用UIAbility
  3. 应用 Aibility 接收卡片 ID并通过用户首选项持久化卡片ID
  4. 移除卡片时,删除卡片 ID

1、创建卡片时,将卡片 ID传递给卡片

  1. 获取卡片ID,更新卡片数据

    entry/src/main/ets/entryformability/EntryFormAbility.ets

    // 使用方创建卡片时触发
    onAddForm(want: Want) {
        // Called to return a FormBindingData object.
        // 获取卡片id
        let formId = want.parameters?.[formInfo.FormParam.IDENTITY_KEY] as string
        // 用卡片id数据更新卡片
        let obj: Record<string, string> = {'formId': formId};
        let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
        return formData;
      }
    
  2. 卡片接收卡片 ID

    entry/src/main/ets/widget/pages/WidgetCard.ets

    卡片页面中使用LocalStorageProp装饰需要刷新的卡片数据

    let storage = new LocalStorage();
    @Entry(storage)
    @Component
    struct WidgetCard {
      @LocalStorageProp("formId") formId: string = "";
      // ...省略模板代码
      Row() {
        Column() {
          Text("卡片id: " + this.formId)
            .fontSize($r('app.float.font_size'))
            .fontWeight(FontWeight.Medium)
            .fontColor($r('app.color.item_title_font'))
        }
        .width(this.FULL_WIDTH_PERCENT)
      }
      .height(this.FULL_HEIGHT_PERCENT)
    }
    
  3. 效果

    添加卡片时 添加到桌面后
    异步请求数据刷新卡片 和 应用内数据改变同步刷新卡片的实现-鸿蒙开发者社区 异步请求数据刷新卡片 和 应用内数据改变同步刷新卡片的实现-鸿蒙开发者社区

2、卡片主动上传卡片ID到应用

  1. 利用 @Watch装饰器对状态变量的监听来触发上传

    entry/src/main/ets/widget/pages/WidgetCard.ets

    let storage = new LocalStorage();
    @Entry(storage)
    @Component
    struct WidgetCard {
      @Watch("upFormId") // 设置状态变量回调函数。
      @LocalStorageProp("formId") formId: string = "";
      upFormId(){
        // 在卡片中使用postCardAction接口的call能力,能够将卡片提供方应用的指定的UIAbility拉到后台。同时,call能力提供了调用应用指定方法、传递数据的功能,使应用在后台运行时可以通过卡片上的按钮执行不同的功能。
        postCardAction(this, {
          action: 'call',
          abilityName: 'EntryAbility',
          params: {
            method: 'createCard',
            formId: this.formId,
          }
        });
      }
      // ... 省略下方代码
    }
    
  2. 在UIAbility中接收call事件并获取参数

    entry/src/main/ets/entryability/EntryAbility.ets

    // callee中要求返回的数据类型
    class MyPara implements rpc.Parcelable {
      marshalling(dataOut: rpc.MessageSequence): boolean {
        return true
      }
      unmarshalling(dataIn: rpc.MessageSequence): boolean {
        return true
      }
    }
    
    onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
        // ...
        // 监听事件
        this.callee.on("createCard", (data: rpc.MessageSequence) => {
          // 接收id
          let cardIdDate = JSON.parse(data.readString()) as Record<string, string>;
          let formId = cardIdDate.formId;
          return new MyPara();
        });
    }
    

3、记录卡片id,持久化存储

异步请求数据刷新卡片 和 应用内数据改变同步刷新卡片的实现-鸿蒙开发者社区

  1. 封装用户首选项工具类

    此时接收到卡片 id 后,需要将卡片 id 持久化存储,避免重新打卡手机时,无法联系到已经创建的卡片

    entry/src/main/ets/common/FormIdStore.ets

    import { preferences } from "@kit.ArkData";
    
    export class FormIdStore {
      static CardKey: string = "card_collect";
      static tokenKey: string = "token"
      static dataPreferences: preferences.Preferences | null = null;
    
      //  初始化
      static init(context: Context) {
        if (FormIdStore.dataPreferences) {
          return;
        }
        FormIdStore.dataPreferences = preferences.getPreferencesSync(
          context,
          {name: FormIdStore.CardKey }
        );
      }
      //  获取卡片id 数组
      static getList() {
        const str = FormIdStore.dataPreferences?.getSync(FormIdStore.CardKey, "[]");
        const list = JSON.parse(str as string) as string[];
        return list;
      }
    
      // 新增卡片数组
      static async set(item: string) {
        const list = FormIdStore.getList();
        if (!list.includes(item)) {
          list.push(item);
          FormIdStore.dataPreferences?.putSync(
            FormIdStore.CardKey,
            JSON.stringify(list)
          );
          await FormIdStore.dataPreferences?.flush();
        }
      }
    
      // 删除元素
      static async remove(item: string) {
        const list = FormIdStore.getList();
        const index = list.indexOf(item);
        if (index !== -1) {
          list.splice(index, 1);
          FormIdStore.dataPreferences?.putSync(
            FormIdStore.CardKey,
            JSON.stringify(list)
          );
          await FormIdStore.dataPreferences?.flush();
        }
        FormIdStore.getList();
      }
    
    }
    
  2. 初始化并调用用户首选项工具类

    onCreate 中初始化

    entry/src/main/ets/entryability/EntryAbility.ets

    onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
       // ...
       // 传入上下文初始化工具类
       FormIdStore.init(this.context);
       this.callee.on("createCard", (data: rpc.MessageSequence) => {
           let cardIdDate = JSON.parse(data.readString()) as Record<string, string>;
           let formId = cardIdDate.formId;
           // 保存卡片ID
          FormIdStore.set(formId);
          return new MyPara();	// 具体情况见上方
       });
    }
    

4、移除卡片时,删除卡片 id

卡片生命周期管理在卡片移除时删除持久化的数据即可

entry/src/main/ets/entryformability/EntryFormAbility.ets

 onRemoveForm(formId: string): void {
    // 删除卡片实例数据
    hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onRemoveForm');
    // 删除之前持久化的卡片实例数据
    // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
}

二、用异步请求数据刷新卡片

这里使用同步方法模拟异步请求数据,数据分为4天数据,getMsgList方法为获取一天的数据

entry/src/main/ets/DataModel/Data.ets

const msgLists: Array<Array<string>> = [
  ["apple1", "apple2", "apple3", "apple4"],
  ["orange1", "orange2", "orange3", "orange4"],
  ["banana1", "banana2", "banana3", "banana4"],
  ["watermelon1", "watermelon2", "watermelon3", "watermelon4"],
]

// 同步方法模拟异步请求获取数据
export async function getMsgList(idx: number): Promise<string[]>{
  if(idx >= msgLists.length){
    return [];
  }
  return msgLists[idx];
}

1、初始化刷新卡片

2、卡片主动上传卡片ID到应用时将卡片需要更新数据的天数传递给应用UIAbility

entry/src/main/ets/widget/pages/WidgetCard.ets

@Watch("upFormId")
@LocalStorageProp("formId") formId: string = "";
// 需要的数据
@LocalStorageProp("msgList") msgList: Array<string> = [];

@LocalStorageProp("selectDay") selectDay: number = 0;

upFormId(){
  postCardAction(this, {
    action: 'call',
    abilityName: 'EntryAbility',
    params: {
      method: 'createCard',
      formId: this.formId,
      selectDay: this.selectDay		// 将卡片需要更新数据的天数传递给应用UIAbility
    }
  });
}

应用在监听事件接收时通过(模拟)请求获取卡片需要天数数据传递给卡片,通过FormExtensionAbility更新卡片数据

entry/src/main/ets/entryability/EntryAbility.ets

this.callee.on("createCard", (data: rpc.MessageSequence) => {
  let cardIdDate = JSON.parse(data.readString()) as Record<string, string>;
  let formId = cardIdDate.formId;
  let selectDay = cardIdDate.selectDay;
  // 保存卡片ID
  FormIdStore.set(formId);
  // 模拟异步请求 获取卡片传来天数的数据传给卡片
  getMsgList(parseInt(selectDay))
    .then((data) => {
      let msgObj: Record<string, Array<string>> = {
        "msgList": data
      }
      let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(msgObj);
      // 更新卡片数据
      formProvider.updateForm(formId, formData);
    })
  return new MyPara();
});

效果展示

entry/src/main/ets/widget/pages/WidgetCard.ets

Row() {
  Column() {
    Text("卡片id: " + this.formId)
      .fontSize($r('app.float.font_size'))
      .fontWeight(FontWeight.Medium)
      .fontColor($r('app.color.item_title_font'))

    Text(`第${this.selectDay+1}天数据`)
      .fontSize(32)

    ForEach(this.msgList, (item: string) => {
      Text(item)
        .fontSize(32)
    })
  }
  .width(this.FULL_WIDTH_PERCENT)
}
.height(this.FULL_HEIGHT_PERCENT)

异步请求数据刷新卡片 和 应用内数据改变同步刷新卡片的实现-鸿蒙开发者社区

2、手动刷新数据

设置点击事件,通过调用call触发应用UIAbility的回调,卡片刷新

entry/src/main/ets/widget/pages/WidgetCard.ets

Text(`第${this.selectDay+1}天数据`)
  .fontSize(32)
  .onClick(() => {
    // 天数加一,模拟数据变化
    this.selectDay = (this.selectDay + 1) % 4;
    postCardAction(this, {
      action: 'call',
      abilityName: 'EntryAbility',
      params: {
        method: 'updateCard',
        formId: this.formId,
        selectDay: this.selectDay
      }
    });
  })

在中专门监听updateCard方法,获取数据并传递给卡片,刷新卡片数据

this.callee.on("updateCard", (data: rpc.MessageSequence) => {
  let cardIdDate = JSON.parse(data.readString()) as Record<string, string>;
  let formId = cardIdDate.formId;
  let selectDay = cardIdDate.selectDay;
  getMsgList(parseInt(selectDay))
    .then((data) => {
      let msgObj: Record<string, Array<string>> = {"msgList": data}
      let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(msgObj);
      formProvider.updateForm(formId, formData);
    })
  return new MyPara();
});

3、效果展示

异步请求数据刷新卡片 和 应用内数据改变同步刷新卡片的实现-鸿蒙开发者社区

4、常见错误

错误1:点击卡片无效,数据没有变化,甚至无法触发任何事件

原因:卡片设置为了静态卡片

修改:将下面目录下isDynamic改为true即可

entry/src/main/resources/base/profile/form_config.json

说明:在API 10及以上 Stage模型的工程中,在Service Widget菜单可直接选择创建动态或静态服务卡片。创建服务卡片后,也可以在卡片的form_config.json配置文件中,通过isDynamic参数修改卡片类型:isDynamic置空或赋值为"true",则该卡片为动态卡片;isDynamic赋值为"false",则该卡片为静态卡片。

更多参考:创建一个ArkTS卡片

错误2:call失效或网络请求无效,没有申请相应权限

entry/src/main/module.json5

"requestPermissions": [
    {
        // 网络权限
    	"name": "ohos.permission.INTERNET"
    },
    {
        // 后台运行权限
    	"name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
    }
],

按如下添加即可
异步请求数据刷新卡片 和 应用内数据改变同步刷新卡片的实现-鸿蒙开发者社区

三、卡片数据跟随应用数据刷新

1、主页实现

看完上面的那么这个就简单了,也能想到怎么处理了吧

异步请求数据刷新卡片 和 应用内数据改变同步刷新卡片的实现-鸿蒙开发者社区

通过 @Watch装饰器对状态变量的监听来触发卡片刷新(因为我们已经获取到并存储了卡片ID),就这么简单!

最后就直接贴代码了,效果见文章开头

entry/src/main/ets/pages/Index.ets

import { getMsgList } from '../DataModel/Data';
import { formBindingData, formProvider } from '@kit.FormKit';
import { FormIdStore } from '../common/FormIdStore';

const TAG = "===";
@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  @State selectDay: number = 0;
  @Watch("appUpdateCard")
  @State msgList: Array<string> = [];
  appUpdateCard(){
    console.log(TAG, "主页msgList变化", JSON.stringify(this.msgList));
    // 刷新所有相关卡片
    FormIdStore.getList().forEach((formId) => {
      getMsgList(this.selectDay)
        .then((data) => {
          let msgObj: Record<string, Array<string> | number> = {
            "msgList": data,
            "selectDay": this.selectDay
          }
          let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(msgObj);
          formProvider.updateForm(formId, formData);
        })
    })
  }
  onPageShow(): void {
    getMsgList(this.selectDay).then((data) => {
      this.msgList = data;
    })
  }
  build() {
    Column(){
      Button("点击获取下一天的数据")
        .onClick(() => {
          this.selectDay = (this.selectDay + 1) % 4;
          getMsgList(this.selectDay)
            .then((data) => {
              this.msgList = data;
            })
        })

      Text(`第${this.selectDay+1}天数据`)
        .fontSize(32)
      ForEach(this.msgList, (item: string) => {
        Text(item)
          .fontSize(32)
      })
    }
    .height('100%')
    .width('100%')
  }
}

2、总结

代码已经推到仓库,欢迎大家下载尝试

仓库地址CardData · AtomGit_开放原子开源基金会代码托管平台

异步请求数据刷新卡片 和 应用内数据改变同步刷新卡片的实现-鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
1
收藏
回复
举报
2条回复
按时间正序
/
按时间倒序
free_d
free_d

写的太有水平啦,一看就是高手

回复
2024-12-26 23:18:38
在敲键盘的小鱼干很饥饿
在敲键盘的小鱼干很饥饿

666,这个入是桂

回复
2024-12-27 10:25:15
回复
    相关推荐