flutter_app_icon_badge 插件鸿蒙适配:实现跨平台应用图标角标管理
flutter_app_icon_badge 插件鸿蒙适配:实现跨平台应用图标角标管理
本项目作者:坚果
您可以使用这个Flutter插件来更改应用程序图标上的角标
作者仓库:https://github.com/badver/flutter_app_icon_badge/
在数字化浪潮的推动下,跨平台开发框架如 Flutter 凭借其高效、便捷的特性,成为了开发者们的宠儿。而鸿蒙系统的崛起,更是为跨平台开发注入了新的活力。为了助力开发者在鸿蒙生态中快速实现 flutter_app_icon_badge更改应用程序图标上的角标功能,本文将深入浅出地为大家解析如何适配 flutter_app_icon_badge 三方库至鸿蒙平台。
一、适配鸿蒙版 flutter_app_icon_badge 三方库
(一)版本选择与仓库简介
我们先去 pub 上查看最新版本,我们选择以 0.0.10版本为基础进行适配。flutter_app_icon_badge 是一个用于在 Flutter 应用中更改应用程序图标上的角标功能,其 GitHub 仓库为https://github.com/badver/flutter_app_icon_badge/ ,我们的目标是将这个插件适配到鸿蒙平台。
(二)引入背景与使用场景
在 OpenHarmony 北向生态的发展过程中,许多已经适配了 Flutter 的厂商在接入 OpenHarmony 时,都希望能够继续使用 FlutterToast 来实现通知功能。因此,我们提供了这个适配方案,采用插件化的适配器模式,帮助生态伙伴快速实现产品化。
本方案适用于已经支持 Flutter 框架的设备在移植到 OpenHarmony 系统过程中,作为一个备选方案。
(三)使用文档与插件库使用
适配 OpenHarmony 平台的详细使用指导可以参考:Flutter使用指导文档
在项目中使用该插件库时,只需在 pubspec.yaml 文件的 dependencies 中新增如下配置:
dependencies:
  flutter_app_icon_badge:
    git:
      url: "https://gitcode.com/nutpi/flutter_app_icon_badge.git"
      path: ""
然后在项目根目录运行 flutter pub get,即可完成依赖添加
接下来是具体的适配过程。
二、适配过程详解
(一)准备工作
确保已经配置好了 Flutter 开发环境,具体可参考 Flutter 配置指南。同时,从 官方插件库 下载待适配的三方插件。本指导书, 以适配 flutter_app_icon_badge 为例

(二)插件目录结构
下载并解压插件后,我们会看到以下目录结构:
- lib :对接 Dart 端代码的入口,由此文件接收到参数后,通过 channel 将数据发送到原生端。
- android :安卓端代码实现目录。
- ios :iOS 原生端实现目录。
- example :一个依赖于该插件的 Flutter 应用程序,用于说明如何使用它。
- README.md :介绍包的文件。
- CHANGELOG.md :记录每个版本中的更改。
- LICENSE :包含软件包许可条款的文件。
(三)创建插件的鸿蒙模块
在插件目录下,打开 Terminal,执行以下命令来创建一个鸿蒙平台的 Flutter 模块:
flutter create . --org dev.badver.flutter_app_icon_badge --template=plugin --platforms=ohos
步骤:
- 
  用vscode/trae打开刚刚下载好的插件。 
- 
  打开Terminal,cd到插件目录下。 
- 
  执行命令 flutter create . --org dev.badver.flutter_app_icon_badge --template=plugin --platforms=ohos创建一个ohos平台的flutter模块。
第一个问题,修改sdk的版本,适配旧版本。
我们做好修改就好。
(四)在根目录下添加鸿蒙平台配置
在项目根目录的 pubspec.yaml 文件中,添加鸿蒙平台的相关配置:
name: flutter_app_icon_badge
description: Flutter app icon badge plugin to change the badge on the icon of your app.
version: 2.0.0
homepage: https://github.com/badver/flutter_app_icon_badge
environment:
  sdk: '>=2.12.0 <4.0.0'
  flutter: ">=2.3.0"
dependencies:
  flutter:
    sdk: flutter
dev_dependencies:
  flutter_test:
    sdk: flutter
flutter:
  plugin:
    platforms:
      android:
        package: dev.badver.flutter_app_icon_badge
        pluginClass: FlutterAppIconBadgePlugin
      ios:
        pluginClass: FlutterAppIconBadgePlugin
      linux:
        pluginClass: FlutterAppIconBadgePlugin
      macos:
        pluginClass: FlutterAppIconBadgePlugin
      windows:
        pluginClass: FlutterAppIconBadgePlugin
      ohos:
        package: dev.badver.flutter_app_icon_badge
        pluginClass: FlutterAppIconBadgePlugin
(五)编写鸿蒙插件的原生 ArkTS模块
1. 创建鸿蒙插件模块
使用 DevEco Studio 打开鸿蒙项目。
2. 修改相关配置文件
在 ohos 目录内的 oh-package.json5 文件中添加 libs/flutter.har 依赖,并创建 .gitignore 文件,添加以下内容以忽略 libs 目录:
/node_modules
/oh_modules
/local.properties
/.preview
/.idea
/build
/libs
*.har
/.cxx
/.test
/BuildProfile.ets
/oh-package-lock.json5
oh-package.json5 文件内容如下:
{
  "name": "flutter_app_icon_badge",
  "version": "1.0.0",
  "description": "Flutter app icon badge plugin to change the badge on the icon of your app",
  "main": "index.ets",
  "author": "nutpi",
  "license": "Apache-2.0",
  "dependencies": {
    "@ohos/flutter_ohos": "file:./har/flutter.har"
  }
}
在 ohos 目录下创建 index.ets 文件,导出配置:
import FlutterAppIconBadgePlugin from './src/main/ets/components/plugin/FlutterAppIconBadgePlugin';
export default FlutterAppIconBadgePlugin;
3. 编写 ETS 代码
文件结构和代码逻辑可以参考安卓或 iOS 的实现,鸿蒙的 API 文档可以参考 :https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-notificationmanager
ohos的api可以参考:https://gitcode.com/openharmony/docs
以下是 FlutterAppIconBadgePlugin.ets 文件的代码示例:
import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
} from '@ohos/flutter_ohos';
import { BusinessError } from '@kit.BasicServicesKit';
import { notificationManager } from '@kit.NotificationKit';
/** FlutterAppIconBadgePlugin **/
export default class FlutterAppIconBadgePlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;
  constructor() {
  }
  getUniqueClassName(): string {
    return "FlutterAppIconBadgePlugin"
  }
  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(binding.getBinaryMessenger(), "flutter_app_icon_badge");
    this.channel.setMethodCallHandler(this)
  }
  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    if (this.channel != null) {
      this.channel.setMethodCallHandler(null)
    }
  }
  onMethodCall(call: MethodCall, result: MethodResult): void {
    if (call.method == "updateBadge") {
      try {
        // 获取参数
        const countArg :string= call.argument("count");
        if (countArg === null || countArg === undefined) {
          console.error('Badge count argument is missing.');
          result.error("MISSING_ARGUMENT", "Badge count argument is required.", null);
          return;
        }
        // 确保 countArg 是数字类型
        let badgeNumber: number = Number(countArg);
        if (isNaN(badgeNumber)) {
          console.error('Invalid badge number received:', countArg);
          result.error("INVALID_ARGUMENT", "Badge count must be a number.", null);
          return;
        }
        notificationManager.setBadgeNumber(badgeNumber).then(() => {
          console.info(`Succeeded in setting badge number to ${badgeNumber}.`);
          result.success(null);
        }).catch((err: BusinessError) => {
          console.error(`Failed to set badge number. Code is ${err.code}, message is ${err.message}`);
          result.error(err.code.toString(), err.message, null);
        });
      } catch (e) {
        const error = e as Error;
        console.error(`Error processing updateBadge: ${error.message}`);
        result.error("UNKNOWN_ERROR", error.message, null);
      }
    }
    else if (call.method == "removeBadge") {
      try {
        notificationManager.setBadgeNumber(0).then(() => {
          console.info(`Succeeded in removing badge number.`);
          result.success(null);
        }).catch((err: BusinessError) => {
          console.error(`Failed to remove badge number. Code is ${err.code}, message is ${err.message}`);
          result.error(err.code.toString(), err.message, null);
        });
      } catch (e) {
        const error = e as Error;
        console.error(`Error processing removeBadge: ${error.message}`);
        result.error("UNKNOWN_ERROR", error.message, null);
      }
    } else if (call.method == "isAppBadgeSupported") {
      // 鸿蒙平台默认支持角标
      result.success(true);
    } else {
      result.notImplemented();
    }
  }
}
这里我主要参考的是
三、NotificationManager模块
本模块提供通知管理的能力,包括发布、取消发布通知,创建、获取、移除通知渠道,获取通知的使能状态、角标使能状态,获取通知的相关信息等。
1.导入
import { notificationManager } from '@kit.NotificationKit';
2.notificationManager.setBadgeNumber
setBadgeNumber(badgeNumber: number): Promise
设定角标个数,在应用的桌面图标上呈现。使用Promise异步回调。
当角标设定个数取值小于或等于0时,表示清除角标。取值大于99时,通知角标将显示99+。
系统能力:SystemCapability.Notification.Notification
参数:
| 参数名 | 类型 | 必填 | 说明 | 
|---|---|---|---|
| badgeNumber | number | 是 | 角标个数。 | 
返回值:
| 类型 | 说明 | 
|---|---|
| Promise | 无返回结果的Promise对象。 | 
错误码:
| 错误码ID | 错误信息 | 
|---|---|
| 401 | Parameter error. Possible causes: 1. Mandatory parameters are left unspecified. 2. Incorrect parameter types. 3.Parameter verification failed. | 
| 1600001 | Internal error. | 
| 1600002 | Marshalling or unmarshalling error. | 
| 1600003 | Failed to connect to the service. | 
| 1600012 | No memory space. | 
示例:
import { BusinessError } from '@kit.BasicServicesKit';
let badgeNumber: number = 10;
notificationManager.setBadgeNumber(badgeNumber).then(() => {
  console.info(`Succeeded in setting badge number.`);
}).catch((err: BusinessError) => {
  console.error(`Failed to set badge number. Code is ${err.code}, message is ${err.message}`);
});
3.dart端是这样定义的
import 'dart:async';
import 'package:flutter/services.dart';
class FlutterAppIconBadge {
  static const MethodChannel _channel =
      const MethodChannel('flutter_app_icon_badge');
  /// Change badge on app icon
  static Future<void> updateBadge(int count) async {
    await _channel.invokeMethod('updateBadge', {"count": count});
  }
  /// Remove badge on app icon
  static Future<void> removeBadge() async {
    await _channel.invokeMethod('removeBadge');
  }
  /// Check if app badge is supported
  static Future<bool> isAppBadgeSupported() async {
    bool? appBadgeSupported =
        await _channel.invokeMethod('isAppBadgeSupported');
    return appBadgeSupported ?? false;
  }
  /// Check if app window is focused.
  static Future<bool> isAppFocused() async {
    bool? isAppFocused = await _channel.invokeMethod('isAppFocused');
    return isAppFocused ?? false;
  }
}
4.ios侧处理
类似ios侧处理这边的信息,并返回
    switch call.method {
        case "updateBadge":
          if let args = call.arguments as? Dictionary<String, Any>,
            let count = args["count"] as? Int {
            UIApplication.shared.applicationIconBadgeNumber = count
            result(nil)
          } else {
            result(FlutterError.init(code: "bad args", message: nil, details: nil))
          }
        case "removeBadge":
          UIApplication.shared.applicationIconBadgeNumber = 0
          result(nil)
        case "isAppBadgeSupported":
          result(true)
        default:
          result(FlutterMethodNotImplemented)
    }
5.ArkTS处理图标
import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
} from '@ohos/flutter_ohos';
import { BusinessError } from '@kit.BasicServicesKit';
import { notificationManager } from '@kit.NotificationKit';
/** FlutterAppIconBadgePlugin **/
export default class FlutterAppIconBadgePlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;
  constructor() {
  }
  getUniqueClassName(): string {
    return "FlutterAppIconBadgePlugin"
  }
  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(binding.getBinaryMessenger(), "flutter_app_icon_badge");
    this.channel.setMethodCallHandler(this)
  }
  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    if (this.channel != null) {
      this.channel.setMethodCallHandler(null)
    }
  }
  onMethodCall(call: MethodCall, result: MethodResult): void {
    if (call.method == "updateBadge") {
      try {
        // 获取参数
        const countArg :string= call.argument("count");
        if (countArg === null || countArg === undefined) {
          console.error('Badge count argument is missing.');
          result.error("MISSING_ARGUMENT", "Badge count argument is required.", null);
          return;
        }
        // 确保 countArg 是数字类型
        let badgeNumber: number = Number(countArg);
        if (isNaN(badgeNumber)) {
          console.error('Invalid badge number received:', countArg);
          result.error("INVALID_ARGUMENT", "Badge count must be a number.", null);
          return;
        }
        notificationManager.setBadgeNumber(badgeNumber).then(() => {
          console.info(`Succeeded in setting badge number to ${badgeNumber}.`);
          result.success(null);
        }).catch((err: BusinessError) => {
          console.error(`Failed to set badge number. Code is ${err.code}, message is ${err.message}`);
          result.error(err.code.toString(), err.message, null);
        });
      } catch (e) {
        const error = e as Error;
        console.error(`Error processing updateBadge: ${error.message}`);
        result.error("UNKNOWN_ERROR", error.message, null);
      }
    }
    else if (call.method == "removeBadge") {
      try {
        notificationManager.setBadgeNumber(0).then(() => {
          console.info(`Succeeded in removing badge number.`);
          result.success(null);
        }).catch((err: BusinessError) => {
          console.error(`Failed to remove badge number. Code is ${err.code}, message is ${err.message}`);
          result.error(err.code.toString(), err.message, null);
        });
      } catch (e) {
        const error = e as Error;
        console.error(`Error processing removeBadge: ${error.message}`);
        result.error("UNKNOWN_ERROR", error.message, null);
      }
    } else if (call.method == "isAppBadgeSupported") {
      // 鸿蒙平台默认支持角标
      result.success(true);
    } else {
      result.notImplemented();
    }
  }
}
6.优化通知
应用需要获取用户授权才能发送通知。在通知发布前调用requestEnableNotification()方法,弹窗让用户选择是否允许发送通知,后续再次调用requestEnableNotification()方法时,则不再弹窗。
通知授权接口功能介绍
| 接口名 | 描述 | 
|---|---|
| isNotificationEnabled():Promise | 查询通知是否授权。 | 
| requestEnableNotification(context: UIAbilityContext): Promise | 请求发送通知的许可,第一次调用会弹窗让用户选择。 | 
开发步骤
1.导入NotificationManager模块。
import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { common } from '@kit.AbilityKit';
const TAG: string = '[PublishOperation]';
const DOMAIN_NUMBER: number = 0xFF00;
2.请求通知授权。
可通过requestEnableNotification的错误码判断用户是否授权。若返回的错误码为1600004,即为拒绝授权。
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
notificationManager.isNotificationEnabled().then((data: boolean) => {
  hilog.info(DOMAIN_NUMBER, TAG, "isNotificationEnabled success, data: " + JSON.stringify(data));
  if(!data){
    notificationManager.requestEnableNotification(context).then(() => {
      hilog.info(DOMAIN_NUMBER, TAG, `[ANS] requestEnableNotification success`);
    }).catch((err : BusinessError) => {
      if(1600004 == err.code){
        hilog.error(DOMAIN_NUMBER, TAG, `[ANS] requestEnableNotification refused, code is ${err.code}, message is ${err.message}`);
      } else {
        hilog.error(DOMAIN_NUMBER, TAG, `[ANS] requestEnableNotification failed, code is ${err.code}, message is ${err.message}`);
      }
    });
  }
}).catch((err : BusinessError) => {
    hilog.error(DOMAIN_NUMBER, TAG, `isNotificationEnabled fail, code is ${err.code}, message is ${err.message}`);
});
7.完整的代码
import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
} from '@ohos/flutter_ohos';
import { BusinessError } from '@kit.BasicServicesKit';
import { notificationManager } from '@kit.NotificationKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { common } from '@kit.AbilityKit';
import { AbilityAware, AbilityPluginBinding } from '@ohos/flutter_ohos';
const TAG: string = '[PublishOperation]';
const DOMAIN_NUMBER: number = 0xFF00;
/** FlutterAppIconBadgePlugin **/
export default class FlutterAppIconBadgePlugin implements FlutterPlugin, MethodCallHandler, AbilityAware {
  private channel: MethodChannel | null = null;
  private static _context: common.UIAbilityContext | null = null;
  constructor() {
  }
  static get context(): common.Context | null {
    return FlutterAppIconBadgePlugin._context;
  }
  get context(): common.UIAbilityContext | null {
    return FlutterAppIconBadgePlugin._context;
  }
  getUniqueClassName(): string {
    return "FlutterAppIconBadgePlugin"
  }
  onAttachedToAbility(binding: AbilityPluginBinding): void {
    FlutterAppIconBadgePlugin._context = binding.getAbility().context;
    // Called when the plugin is attached to an Ability.
  }
  onDetachedFromAbility(): void {
    // this._uiContext = null;
  }
  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(binding.getBinaryMessenger(), "flutter_app_icon_badge");
    this.channel.setMethodCallHandler(this)
  }
  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    if (this.channel != null) {
      this.channel.setMethodCallHandler(null)
    }
  }
  onMethodCall(call: MethodCall, result: MethodResult): void {
    if (call.method == "updateBadge") {
      if (FlutterAppIconBadgePlugin._context) { // Add null check here
        try {
          notificationManager.isNotificationEnabled().then((data: boolean) => {
            hilog.info(DOMAIN_NUMBER, TAG, "isNotificationEnabled success, data: " + JSON.stringify(data));
            try {
              // 获取参数
              const countArg: string = call.argument("count");
              if (countArg === null || countArg === undefined) {
                console.error('Badge count argument is missing.');
                result.error("MISSING_ARGUMENT", "Badge count argument is required.", null);
                return;
              }
              // 确保 countArg 是数字类型
              let badgeNumber: number = Number(countArg);
              if (isNaN(badgeNumber)) {
                console.error('Invalid badge number received:', countArg);
                result.error("INVALID_ARGUMENT", "Badge count must be a number.", null);
                return;
              }
              notificationManager.setBadgeNumber(badgeNumber).then(() => {
                console.info(`Succeeded in setting badge number to ${badgeNumber}.`);
                result.success(null);
              }).catch((err: BusinessError) => {
                console.error(`Failed to set badge number. Code is ${err.code}, message is ${err.message}`);
                result.error(err.code.toString(), err.message, null);
              });
            } catch (e) {
              const error = e as Error;
              console.error(`Error processing updateBadge: ${error.message}`);
              result.error("UNKNOWN_ERROR", error.message, null);
            }
            if (!data) {
              notificationManager.requestEnableNotification(FlutterAppIconBadgePlugin._context).then(() => {
                hilog.info(DOMAIN_NUMBER, TAG, `[ANS] requestEnableNotification success`);
              }).catch((err: BusinessError) => {
                if (1600004 == err.code) {
                  hilog.error(DOMAIN_NUMBER, TAG,
                    `[ANS] requestEnableNotification refused, code is ${err.code}, message is ${err.message}`);
                } else {
                  hilog.error(DOMAIN_NUMBER, TAG,
                    `[ANS] requestEnableNotification failed, code is ${err.code}, message is ${err.message}`);
                }
              });
            }
          }).catch((err: BusinessError) => {
            hilog.error(DOMAIN_NUMBER, TAG,
              `isNotificationEnabled fail, code is ${err.code}, message is ${err.message}`);
          });
        } catch (err) {
          // 捕获同步的参数错误
          let code = (err as BusinessError).code;
          let message = (err as BusinessError).message;
          console.error(`terminateSelf failed, code is ${code}, message is ${message}`);
          result.error("TERMINATE_ERROR", `terminateSelf error: ${message}`, null);
        }
      } else {
        console.error("UIContext is null, cannot terminate self.");
        result.error("CONTEXT_NULL", "UIContext is null", null); // Inform Flutter about the error
      }
    } else if (call.method == "removeBadge") {
      try {
        notificationManager.setBadgeNumber(0).then(() => {
          console.info(`Succeeded in removing badge number.`);
          result.success(null);
        }).catch((err: BusinessError) => {
          console.error(`Failed to remove badge number. Code is ${err.code}, message is ${err.message}`);
          result.error(err.code.toString(), err.message, null);
        });
      } catch (e) {
        const error = e as Error;
        console.error(`Error processing removeBadge: ${error.message}`);
        result.error("UNKNOWN_ERROR", error.message, null);
      }
    } else if (call.method == "isAppBadgeSupported") {
      // 鸿蒙平台默认支持角标
      result.success(true);
    } else {
      result.notImplemented();
    }
  }
}
四、编写 Example
1. 创建 Example 应用
在插件根目录下创建一个名为 example 的文件夹,用于存放示例应用。在 example 文件夹中,创建一个鸿蒙平台的 Flutter 应用,用于验证插件功能。
2. 签名与运行
使用 Deveco Studio 打开 example > ohos 目录,单击 File > Project Structure > Project > Signing Configs,勾选 Automatically generate signature,等待自动签名完成。然后运行以下命令:
flutter pub get
 flutter build hap --debug
如果应用正常启动,说明插件适配成功。如果没有,欢迎大家联系坚果派一起支持。
五、总结
通过以上步骤,我们成功地将 flutter_app_icon_badge 三方库适配到了鸿蒙平台。这个过程涉及到了解插件的基本信息、配置开发环境、创建鸿蒙模块、编写原生代码以及测试验证等多个环节。希望这篇博客能够帮助到需要进行 flutter_app_icon_badge 鸿蒙适配的开发者们,让大家在鸿蒙生态的开发中更加得心应手。
六、参考
- [如何使用Flutter与OpenHarmony通信 FlutterChannel](https://gitcode.com/openharmony-sig/flutter_samples/blob/master/ohos/docs/04_development/如何使用Flutter与OpenHarmony通信 FlutterChannel.md)
- [开发module](https://gitcode.com/openharmony-sig/flutter_samples/blob/master/ohos/docs/04_development/如何使用混合开发 module.md)
- 开发package
- 开发plugin
- [开发FFI plugin](https://gitcode.com/openharmony-sig/flutter_samples/blob/master/ohos/docs/04_development/开发FFI plugin.md)
- developing-packages
- 适配仓库地址
七、坚果派
坚果派由坚果等人创建,团队拥有若干华为HDE,以及若干其他领域的三十余位万粉博主运营。专注于分享的技术包括HarmonyOS/OpenHarmony,ArkUI-X,元服务,服务卡片,华为自研语言,BlueOS操作系统、团队成员聚集在北京、上海、广州、深圳、南京、杭州、苏州、宁夏等地。 聚焦“鸿蒙原生应用”、“智能物联”和“AI赋能”、“人工智能”四大业务领域,依托华为开发者专家等强大的技术团队,以及涵盖需求、开发、测试、运维于一体的综合服务体系,赋能文旅、媒体、社交、家居、消费电子等行业客户,满足社区客户数字化升级转型的需求,帮助客户实现价值提升。 目前上架鸿蒙原生应用18款,三方库72个。
八、FAQ
在刚开始适配完成后,并没有成功在桌面上生成角标,原因是没有申请权限。
- 17回答
- 28粉丝
- 12关注
- Flutter库OpenHarmony平台适配进度
- 跨平台开发鸿蒙原生应用
- (二五)ArkTS 跨平台应用开发策略
- 鸿蒙Flutter实战:06-使用ArkTs开发Flutter鸿蒙插件
- ArkUI-X跨平台应用改造指南
- HBuilderX 中适配鸿蒙插件的安装使用指南
- 鸿蒙跨平台框架来了ArkUI-X
- 【HarmonyOS 5】鸿蒙跨平台开发方案详解(一)
- 【HarmonyOS 5】鸿蒙跨平台开发方案详解(二)
- Flutter到鸿蒙的跨越:flutter_native_contact_picker库的鸿蒙适配之旅
- Flutter到鸿蒙的跨越:flutter_native_contact_picker库的鸿蒙适配之旅
- uniappx插件nutpi-idcard 开发与使用指南(适配鸿蒙)
- 如何快速判断 Flutter 库是否需要适配鸿蒙?纯 Dart 库无需适配!
- 鸿蒙Next使用ArkUI-X跨平台开发体验
- flutter_exit_app 三方库鸿蒙适配之旅:从零到一的深度实践

