由于卡片不支持导入模块,因此我们无法通过引入@ohos.net.http等包来进行数据请求,所以只能借助外部能力获取数据,然后再传入卡片内部进行展示。外部能力有以下两种:
- 卡片的EntryFormAbility或者主应用的EntryAbility
- 应用或者元服务的各个页面(包括组件)
添加卡片到桌面时初始化数据
当用户把卡片添加到桌面上时,会触发FormExtensionAbility中的onAddForm生命周期方法,此时我们可以给卡片传递初始数据,我们通过三个步骤来实现这个过程:
- 在FormExtensionAbility中的onAddForm中异步获取数据
- 通过formProvider.updateForm()方法异步更新卡片数据
- 卡片中通过localStorageProp接收
其过程如下图所示:
下面,我们通过代码来实现这个过程。
1、获取异步数据
我们在FormExtensionAbility中的onAddForm生命周期中使用 setTimeout 模拟异步请求获取数据。需要注意setTimeout的时间不能超过 5 秒,因为FormExtensionAbility进程不能常驻后台,在卡片生命周期回调函数中无法处理长时间的任务,在生命周期调度完成后会继续存在5秒,如果5秒内没有新的生命周期回调触发,进程就会自动退出。
javascript
import formInfo from '@ohos.app.form.formInfo';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formProvider from '@ohos.app.form.formProvider';
export default class EntryFormAbility extends FormExtensionAbility {
// 使用方创建卡片时触发,提供方需要返回卡片数据绑定类
onAddForm(want) {
let formData = {
title:'鸿蒙雄起',
desc:'鸿蒙千帆起,我要当舵手'
};
setTimeout(()=>{
formData = {
title:'鸿蒙雄起-刷新后',
desc:'鸿蒙千帆起,我要当舵手 - 刷新后'
};
// 1. 定义需要传递给卡片的数据对象
let bindData = formBindingData.createFormBindingData(formData);
},2000); // 注意:此处不能超过5000,即5秒钟,因为EntryFormAbility的生命周期为5秒
// 当异步更新时,onAddForm方法最后可以返回 null
return formBindingData.createFormBindingData(formData);
}
};
2、传入异步数据
formProvider模块提供了更新卡片,设置卡片更新时间,获取卡片信息,请求发布卡片等接口。其中formProvider.updateForm方法用来更新指定的卡片。我们使用formProvider.updateForm方法传入异步数据。
javascript
import formInfo from '@ohos.app.form.formInfo';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formProvider from '@ohos.app.form.formProvider';
export default class EntryFormAbility extends FormExtensionAbility {
// 使用方创建卡片时触发,提供方需要返回卡片数据绑定类
onAddForm(want) {
let formData = {
title:'鸿蒙雄起',
desc:'鸿蒙千帆起,我要当舵手'
};
// 1. 定义需要传递给卡片的数据对象
setTimeout(()=>{
formData = {
title:'鸿蒙雄起-刷新后',
desc:'鸿蒙千帆起,我要当舵手 - 刷新后'
};
// 1. 定义需要传递给卡片的数据对象
let bindData = formBindingData.createFormBindingData(formData);
// 2. 获取当前添加到桌面的卡片id
let formid = want.parameters[formInfo.FormParam.IDENTITY_KEY];
// 3. 更新数据到卡片上
formProvider.updateForm(formid,bindData).then(res=>{
console.log('mylog','更新成功');
}).catch(err=>{
console.log('mylog','更新失败');
});
},2000); // 注意:此处不能超过5000,即5秒钟,因为EntryFormAbility的生命周期为5秒
// 当异步更新时,onAddForm方法最后可以返回 null
return formBindingData.createFormBindingData(formData);
}
};
3、接收数据
在卡片页面WidgetCard.ets中通过localStorageProp接收数据。首先在WidgetCard.ets页面的最上面创建一个LocalStorage的实例,然后将实例传递给@Entry,在页面中就可以使用LocalStorageProp修饰符来接收数据了。
kotlin
const storage = new LocalStorage();
@Entry(storage)
@Component
struct WidgetCard {
// 接收 EntryFormAbility 传递过来的数据
@LocalStorageProp("title") title: string = '';
@LocalStorageProp("desc") desc: string = ''
build() {
Column() {
Text(this.title)
Text(this.desc)
}
.width("100%")
.height("100%")
.onClick(() => {
postCardAction(this, {
"action": "router",
"abilityName": "EntryAbility",
"params": {
"message": "add detail"
}
});
})
}
}
在主应用/元服务中更新数据到卡片
卡片添加到桌面上以后,用户在访问应用或者元服务中的页面时,可以从页面中主动向卡片推送数据,以此来更新卡片数据。我们也通过三个步骤来实现这个过程
- 在卡片添加到桌面时,在onAddForm中将卡片id保存到首选项中
- 在主应用或者元服务页面中调用formProvider.updateForm给卡片发送数据
- 卡片中通过localStorageProp接收数据
其过程如下图所示:
1、存储卡片id
在卡片添加到页面时,我们可以在FormExtensionAbility中的onAddForm生命周期中将卡片id存储到首选项中,在应用或者元服务页面中就可以通过首选项来获取卡片id了。
javascript
import formInfo from '@ohos.app.form.formInfo';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import { PreferencesUtil } from '../common/PreferencesUtil';
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(want) {
let formData = {
title:'鸿蒙雄起',
desc:'鸿蒙千帆起,我要当舵手'
};
// 1. 获取卡片id
let formId = want.parameters[formInfo.FormParam.IDENTITY_KEY];
//2. 将卡片id保存到首选项
PreferencesUtil.getInstance().addFormId(this.context,formId);
// 2. 将数据转为卡片识别的对象返回
return formBindingData.createFormBindingData(formData);
}
};
在上面的代码中,用到了PreferencesUtil这个工具类,其实现如下:
typescript
import dataPreferences from '@ohos.data.preferences';
import Logger from '@ohos.hilog';
const TAG = 1;
const MY_STORE = 'myStore';
const FORM_ID = 'formIds';
type ValueType = number | string | boolean | Array<number> | Array<string> | Array<boolean>;
export class PreferencesUtil {
private static preferencesUtil: PreferencesUtil;
public static getInstance(): PreferencesUtil {
if (!PreferencesUtil.preferencesUtil) {
PreferencesUtil.preferencesUtil = new PreferencesUtil();
}
return PreferencesUtil.preferencesUtil;
}
getPreferences(context: Context): Promise<dataPreferences.Preferences> {
return new Promise((resolve, reject) => {
dataPreferences.getPreferences(context, MY_STORE, (err, pref: dataPreferences.Preferences) => {
if (err) {
Logger.error(TAG, `Failed to get preferences. Code:${err.code},message:${err.message}`,"");
reject(err);
return;
}
resolve(pref);
})
})
}
preferencesFlush(preferences: dataPreferences.Preferences) {
preferences.flush((err) => {
if (err) {
Logger.error(TAG, `Failed to flush. Code:${err.code}, message:${err.message}`,"");
}
})
}
preferencesPut(preferences: dataPreferences.Preferences, formIds: Array<string>): Promise<boolean> {
return new Promise((resolve, reject) => {
try {
preferences.put(FORM_ID, formIds, (err) => {
if (err) {
reject(err);
Logger.error(TAG, `Failed to put data. Code:${err.code}, message:${err.message}`,"");
return;
}
Logger.info(TAG, `preferencesPut succeed,formIds: ${JSON.stringify(formIds)}`,"");
resolve(true);
})
} catch (error) {
Logger.error(TAG, `Failed to put data. Code: ${error.code},
message:${error.message}`,"");
}
});
}
async preferencesHas(preferences: dataPreferences.Preferences): Promise<boolean> {
return new Promise((resolve, reject) => {
preferences.has(FORM_ID, (err, value) => {
if (err) {
reject(err);
Logger.error(TAG, `WANG to check the key 'formIds'. Code:${err.code}, message:${err.message}`,"");
return;
}
resolve(value);
});
})
}
removePreferencesFromCache(context: Context): void {
dataPreferences.removePreferencesFromCache(context, MY_STORE);
}
async getFormIds(context: Context): Promise<Array<string>> {
try {
let preferences = await this.getPreferences(context);
return new Promise((resolve, reject) => {
if (preferences === null) {
Logger.error(TAG, `preferences is null`,"");
return;
}
preferences.get(FORM_ID, [''], (err, value: ValueType) => {
if (err) {
reject(err);
Logger.error(TAG, `Failed to get value of 'formIds'. Code:${err.code}, message:${err.message}`,"");
return;
}
resolve(value as Array<string>);
Logger.info(TAG, `Succeeded in getting value of 'formIds'. val: ${value}.`,"");
})
})
} catch (error) {
Logger.error(TAG, `WANG Failed to get value of 'formIds'. Code:${error.code},
message:${error.message}`,"");
}
return new Promise((resolve, reject) => {})
}
async addFormId(context: Context, formId: string) {
try {
let preferences = await this.getPreferences(context);
if (preferences === null) {
Logger.error(TAG, `preferences is null`,"");
return;
}
if (await this.preferencesHas(preferences)) {
let formIds = await this.getFormIds(context);
console.log('formIds:',formIds)
if (formIds.indexOf(formId) === -1) {
formIds.push(formId);
if (!await this.preferencesPut(preferences, formIds)) {
return;
}
this.preferencesFlush(preferences);
}
} else {
if (!await this.preferencesPut(preferences, [formId])) {
return;
}
this.preferencesFlush(preferences);
}
} catch (error) {
Logger.error(TAG, `Failed to check the key 'formIds'. Code:${error.code},
message:${error.message}`,"");
}
}
async removeFormId(context: Context, formId: string) {
try {
let preferences = await this.getPreferences(context);
if (preferences === null) {
Logger.error(TAG, `preferences is null`,"");
return;
}
if (await this.preferencesHas(preferences)) {
let formIds = await this.getFormIds(context);
let index = formIds.indexOf(formId);
if (index !== -1) {
formIds.splice(index, 1);
}
if (!await this.preferencesPut(preferences, formIds)) {
return;
}
this.preferencesFlush(preferences);
}
} catch (error) {
Logger.error(TAG, `WANG Failed to get preferences. Code:${error.code},
message:${error.message}`,"");
}
}
}
2、应用/元服务页面中发送数据
在主应用或者元服务页面中,首先从首选项中获取卡片id,然后调用formProvider.updateForm(formId,data)
给卡片发送数据。
javascript
async aboutToAppear() {
// 1. 从首选项获取卡片id
let cardids = await PreferencesUtil.getInstance().getFormIds(getContext(this));
// 2. 遍历卡片id,更新卡片数据
let data = {
title: '鸿蒙雄起- 主应用推送刷新'+Math.random(),
desc: '鸿蒙千帆起,我要当舵手-主应用推送刷新'+Math.random()
};
cardids.forEach(cardid => {
let bindData = formBindingData.createFormBindingData(data);
formProvider.updateForm(cardid, bindData)
.then(res => {
console.log('mylog',JSON.stringify(res))
})
.catch(err => {
console.log('mylog','err',JSON.stringify(err))
})
})
}
3、卡片页面接收数据
在卡片WidgetCard.ets中通过localStorageProp接收数据。首先在WidgetCard.ets文件的最上面创建一个LocalStorage的实例,然后将实例传递给@Entry,在页面中就可以使用LocalStorageProp修饰符来接收数据了。
kotlin
const storage = new LocalStorage();
@Entry(storage)
@Component
struct WidgetCard {
@LocalStorageProp("title") title: string = '';
@LocalStorageProp("desc") desc: string = ''
build() {
Column() {
Text(this.title)
Text(this.desc)
}
.width("100%")
.height("100%")
.onClick(() => {
postCardAction(this, {
"action": "router",
"abilityName": "EntryAbility",
"params": {
"message": "add detail"
}
});
})
}
}
主应用不能刷新卡片数据bug解决
在主应用中更新数据到卡片时,可能会遇到卡片数据无法更新的问题。其表现为:添加卡片到桌面后,在不杀掉主应用的情况下,通过主应用向卡片更新数据不成功,需要先杀掉主应用后再打开主应用才能正常。
原因是在主应用中获取不到在FormExtensionAbility中的onAddForm生命周期中保存到首选项的卡片id数据(预测卡片和主应用是两个进程,主应用不杀掉之前,2个进程间数据不能共享)。
解决方案很简单,就是在FormExtensionAbility中的onAddForm生命周期中通知主应用将卡片id也保存一份到自己的首选项中,自己存自己取就能解决进程间数据不能共享的问题。
1、在onAddForm生命周期事件中添加发布方法
在FormExtensionAbility中的onAddForm生命周期事件中,使用commonEventManager添加发布方法,将卡片id发送给页面。
javascript
import { PublishEventType } from '../common/Constants';
onAddForm(want) {
// Called to return a FormBindingData object.
let formData = {
title:'鸿蒙雄起',
desc:'鸿蒙千帆起,我要当舵手'
};
// 1. 定义需要传递给卡片的数据对象
setTimeout(()=>{
formData = {
title:'鸿蒙雄起-刷新后',
desc:'鸿蒙千帆起,我要当舵手 - 刷新后'
};
// 1. 定义需要传递给卡片的数据对象
let bindData = formBindingData.createFormBindingData(formData);
// 2. 获取当前添加到桌面的卡片id
let formid = want.parameters[formInfo.FormParam.IDENTITY_KEY];
PreferencesUtil.getInstance().addFormId(this.context,formid)
// 将卡片id发送给页面
SubscriberClass.publish(PublishEventType.APP_PUBLISH,formid)
// 3. 更新数据到卡片上
formProvider.updateForm(formid,bindData).then(res=>{
console.log('mylog','更新成功');
}).catch(err=>{
console.log('mylog','更新失败');
});
},2000); // 注意:此处不能超过5000,即5秒钟,因为EntryFormAbility的生命周期为5秒
return formBindingData.createFormBindingData(formData);
}
2、在index.ets中增加订阅方法
在主应用的index.ets页面中添加订阅方法,订阅卡片发送过来的事件,获取到卡片id后将卡片id存储到首选项中。
javascript
async aboutToAppear(){
// 判断如果用户登录过,则首选项中有数据,则获取到数据后再重新设置一下内存中的用户数据
// 否则就跳转到登录页面
let user = await AppStorageKit.GetLoginUser(getContext(this));
if(user && user.uid){
// 用户存在
AppStorageKit.SetLoginUser(user,getContext(this))
console.log('Index,判断用户是存在的')
}else{
router.pushUrl({url:Constants.PAGE_LOGIN})
}
// 接收通知保存卡片数据
SubscriberClass.subscribe(PublishEventType.APP_PUBLISH,null,(formid)=>{
PreferencesUtil.getInstance().addFormId(getContext(this),formid)
console.log('mylog->','publish',formid)
})
}
在上面的代码中,用到了SubscriberClass这个工具类,其实现如下:
typescript
import commonEventManager from '@ohos.commonEventManager'
import { PublishEventType } from './Constants'
// commonEventManager作用:可用于进程间通讯
export class SubscriberClass {
static publishCount:number = 1
// 发布者
static publish(eventType:PublishEventType,data:string){
// next中data支持导入类型
commonEventManager.publish(eventType,{data},(err)=>{
if(err){
// 失败只发3次
if(this.publishCount<=3){
this.publish(eventType,data)
}else{
this.publishCount = 1
}
}else{
this.publishCount = 1
}
})
}
// 订阅者
static subscribe(eventType:PublishEventType,subscriber,callback:(event:string)=>void){
commonEventManager.createSubscriber({ events: [eventType] }, (err, data) => {
if (err) {
return console.log('logData:', `创建订阅者error ${JSON.stringify(err)}`)
}
console.log('logData:', `创建订阅者success`)
subscriber = data
if (subscriber !== null) {
//订阅事件
commonEventManager.subscribe(subscriber, (err, data) => {
if (err) {
return console.error(`logData`, '订阅事件失败')
}
console.log('logData:',`接受订阅事件:${data.data}`)
callback(data.data)
})
} else {
console.error('logData:',`需要创建subscriber`);
}
})
}
}