异步请求数据刷新卡片 和 应用内数据改变同步刷新卡片的实现 原创
前言
下面代码基OpenHarmony API12,在DAYU200运行结果
HarmonyOS 应用如何修改为 OpenHarmony 应用(API12):如何创建卡片:服务卡片开发指南:打造更便捷的用户体验 (基于ArkTs UI 的卡片开发)
面向需求:
- 卡片无法发送网络请求想在卡片中获取接口数据
- 想通过应用数据变化同步卡片数据变化
- 卡片中无法使用延时函数并且卡片动画效果最多持续1秒,想要使用大于1秒的延迟
- 另外 formExtensionAbility 进程不能常驻后台,即在卡片生命周期回调函数中无法处理长时间的任务
结果展示(有点糊hh)
整体思路
- 要通过应用改变卡片数据,那么应用需要获取到卡片ID
- 通过FormExtensionAbility只要知道卡片ID即可更新卡片数据
- 卡片通过call事件和应用UIAbility交互
- @Watch装饰器对状态变量的监听达到改变了就刷新的目的
全部代码,见最后仓库地址
需要如下权限,别忘了加哦
- 异步请求获取数据需要网络权限
- 卡片使用call事件拉起指定UIAbility到后台,需要后台运行权限
entry/src/main/module.json5
"requestPermissions": [
{
// 网络权限
"name": "ohos.permission.INTERNET"
},
{
// 后台运行权限
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
}
],
一、应用获取并持久化卡片ID
大致流程如下
- 卡片生命周期函数 onAddForm中将卡片 ID传递给卡片
- 卡片通过call事件主动上传卡片 ID到应用UIAbility
- 应用 Aibility 接收卡片 ID并通过用户首选项持久化卡片ID
- 移除卡片时,删除卡片 ID
1、创建卡片时,将卡片 ID传递给卡片
-
获取卡片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; }
-
卡片接收卡片 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) }
-
效果
添加卡片时 添加到桌面后
2、卡片主动上传卡片ID到应用
-
利用 @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, } }); } // ... 省略下方代码 }
-
在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,持久化存储
-
封装用户首选项工具类
此时接收到卡片 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(); } }
-
初始化并调用用户首选项工具类
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、总结
代码已经推到仓库,欢迎大家下载尝试
写的太有水平啦,一看就是高手
666,这个入是桂