鸿蒙开发之卡片数据交互

由于卡片不支持导入模块,因此我们无法通过引入@ohos.net.http等包来进行数据请求,所以只能借助外部能力获取数据,然后再传入卡片内部进行展示。外部能力有以下两种:

  1. 卡片的EntryFormAbility或者主应用的EntryAbility
  2. 应用或者元服务的各个页面(包括组件)

添加卡片到桌面时初始化数据

当用户把卡片添加到桌面上时,会触发FormExtensionAbility中的onAddForm生命周期方法,此时我们可以给卡片传递初始数据,我们通过三个步骤来实现这个过程:

  1. 在FormExtensionAbility中的onAddForm中异步获取数据
  2. 通过formProvider.updateForm()方法异步更新卡片数据
  3. 卡片中通过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"
        }
      });
    })
  }
}

在主应用/元服务中更新数据到卡片

卡片添加到桌面上以后,用户在访问应用或者元服务中的页面时,可以从页面中主动向卡片推送数据,以此来更新卡片数据。我们也通过三个步骤来实现这个过程

  1. 在卡片添加到桌面时,在onAddForm中将卡片id保存到首选项中
  2. 在主应用或者元服务页面中调用formProvider.updateForm给卡片发送数据
  3. 卡片中通过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`);
      }
    })
  }
}
相关推荐
枫叶丹41 小时前
【HarmonyOS之旅】HarmonyOS开发基础知识(三)
华为od·华为·华为云·harmonyos
SoraLuna6 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
AORO_BEIDOU10 小时前
单北斗+鸿蒙系统+国产芯片,遨游防爆手机自主可控“三保险”
华为·智能手机·harmonyos
博览鸿蒙11 小时前
鸿蒙操作系统(HarmonyOS)的应用开发入门
华为·harmonyos
Damon小智18 小时前
HarmonyOS NEXT 技术实践-基于基础视觉服务的多目标识别
华为·harmonyos
爱笑的眼睛112 天前
uniapp 极速上手鸿蒙开发
华为·uni-app·harmonyos
K.P2 天前
鸿蒙元服务从0到上架【第三篇】(第二招有捷径)
华为·harmonyos·鸿蒙系统
K.P2 天前
鸿蒙元服务从0到上架【第二篇】
华为·harmonyos·鸿蒙系统
敲代码的小强2 天前
Flutter项目兼容鸿蒙Next系统
flutter·华为·harmonyos
程序猿会指北2 天前
纯血鸿蒙APP实战开发——Text实现部分文本高亮和超链接样式
移动开发·harmonyos·arkts·openharmony·arkui·组件化·鸿蒙开发