鸿蒙开发之卡片数据交互

由于卡片不支持导入模块,因此我们无法通过引入@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`);
      }
    })
  }
}
相关推荐
zhanshuo8 小时前
在鸿蒙里优雅地处理网络错误:从 Demo 到实战案例
harmonyos
zhanshuo8 小时前
在鸿蒙中实现深色/浅色模式切换:从原理到可运行 Demo
harmonyos
whysqwhw13 小时前
鸿蒙分布式投屏
harmonyos
whysqwhw14 小时前
鸿蒙AVSession Kit
harmonyos
whysqwhw16 小时前
鸿蒙各种生命周期
harmonyos
whysqwhw17 小时前
鸿蒙音频编码
harmonyos
whysqwhw17 小时前
鸿蒙音频解码
harmonyos
whysqwhw17 小时前
鸿蒙视频解码
harmonyos
whysqwhw18 小时前
鸿蒙视频编码
harmonyos
ajassi200018 小时前
开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器
华为·开源·harmonyos