鸿蒙实战开发:卡片数据交互

由于卡片不支持导入模块,因此我们无法通过引入@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秒内没有新的生命周期回调触发,进程就会自动退出。

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方法传入异步数据。

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修饰符来接收数据了。

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了。

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这个工具类,其实现如下:

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) 给卡片发送数据。

 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修饰符来接收数据了。

 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))
        })
    })
  }
  
  

主应用不能刷新卡片数据bug解决

在主应用中更新数据到卡片时,可能会遇到卡片数据无法更新的问题。其表现为:添加卡片到桌面后,在不杀掉主应用的情况下,通过主应用向卡片更新数据不成功,需要先杀掉主应用后再打开主应用才能正常。

原因是在主应用中获取不到在FormExtensionAbility中的onAddForm生命周期中保存到首选项的卡片id数据(预测卡片和主应用是两个进程,主应用不杀掉之前,2个进程间数据不能共享)。

解决方案很简单,就是在FormExtensionAbility中的onAddForm生命周期中通知主应用将卡片id也保存一份到自己的首选项中,自己存自己取就能解决进程间数据不能共享的问题。

1、在onAddForm生命周期事件中添加发布方法

在FormExtensionAbility中的onAddForm生命周期事件中,使用commonEventManager添加发布方法,将卡片id发送给页面。

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存储到首选项中。

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这个工具类,其实现如下:

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`);
      }
    })
  }
}

最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员,可以直接领取这份资料

 获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

鸿蒙(HarmonyOS NEXT)最新学习路线

  •  HarmonOS基础技能

  • HarmonOS就业必备技能 
  •  HarmonOS多媒体技术

  • 鸿蒙NaPi组件进阶

  • HarmonOS高级技能

  • 初识HarmonOS内核 
  • 实战就业级设备开发

 有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

《鸿蒙 (OpenHarmony)开发入门教学视频》

《鸿蒙生态应用开发V2.0白皮书》

图片

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

图片

 《鸿蒙开发基础》

  • ArkTS语言
  • 安装DevEco Studio
  • 运用你的第一个ArkTS应用
  • ArkUI声明式UI开发
  • .……

图片

 《鸿蒙开发进阶》

  • Stage模型入门
  • 网络管理
  • 数据管理
  • 电话服务
  • 分布式应用开发
  • 通知与窗口管理
  • 多媒体技术
  • 安全技能
  • 任务管理
  • WebGL
  • 国际化开发
  • 应用测试
  • DFX面向未来设计
  • 鸿蒙系统移植和裁剪定制
  • ……

图片

《鸿蒙进阶实战》

  • ArkTS实践
  • UIAbility应用
  • 网络案例
  • ……

图片

 获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

总结

总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值