您可以使用这个Flutter[1]插件来更改应用程序图标上的角标
作者仓库:https://github.com/badver/flutter_app_icon_badge/
在数字化浪潮的推动下,跨平台开发框架如 Flutter 凭借其高效、便捷的特性,成为了开发者们的宠儿。而鸿蒙系统的崛起,更是为跨平台开发注入了新的活力。为了助力开发者在鸿蒙生态中快速实现 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 使用指导文档[2]
在项目中使用该插件库时,只需在 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 配置指南[3]。同时,从 官方插件库[4] 下载待适配的三方插件。本指导书, 以适配 flutter_app_icon_badge[5] 为例
image-20250417200546042
下载并解压插件后,我们会看到以下目录结构:
在插件目录下,打开 Terminal,执行以下命令来创建一个鸿蒙平台的 Flutter 模块:
flutter create . --org dev.badver.flutter_app_icon_badge --template=plugin --platforms=ohos
步骤:
flutter create . --org dev.badver.flutter_app_icon_badge --template=plugin --platforms=ohos
创建一个 ohos 平台的 flutter 模块。第一个问题,修改 sdk 的版本,适配旧版本。
我们做好修改就好。
在项目根目录的 pubspec.yaml
文件中,添加鸿蒙平台的相关配置:
name: flutter_app_icon_badge
description:Flutterappiconbadgeplugintochangethebadgeontheiconofyourapp.
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
使用 DevEco Studio 打开鸿蒙项目。
在 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;
文件结构和代码逻辑可以参考安卓或 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 **/
exportdefaultclass 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 asError;
console.error(`Error processing updateBadge: ${error.message}`);
result.error("UNKNOWN_ERROR", error.message, null);
}
}
elseif (call.method == "removeBadge") {
try {
notificationManager.setBadgeNumber().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 asError;
console.error(`Error processing removeBadge: ${error.message}`);
result.error("UNKNOWN_ERROR", error.message, null);
}
} elseif (call.method == "isAppBadgeSupported") {
// 鸿蒙平台默认支持角标
result.success(true);
} else {
result.notImplemented();
}
}
}
这里我主要参考的是
本模块提供通知管理的能力,包括发布、取消发布通知,创建、获取、移除通知渠道,获取通知的使能状态、角标使能状态,获取通知的相关信息等。
import { notificationManager } from '@kit.NotificationKit';
setBadgeNumber(badgeNumber: number): Promise
设定角标个数,在应用的桌面图标上呈现。使用 Promise 异步回调。
当角标设定个数取值小于或等于 0 时,表示清除角标。取值大于 99 时,通知角标将显示 99+。
系统能力:SystemCapability.Notification.Notification
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
badgeNumber | number | 是 | 角标个数。 |
返回值:
类型 | 说明 |
---|---|
Promise | 无返回结果的 Promise 对象。 |
错误码:
以下错误码的详细介绍请参见通用错误码[6]和通知错误码[7]。
错误码 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 = ;
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}`);
});
import 'dart:async';
import'package:flutter/services.dart';
class FlutterAppIconBadge {
staticconst 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;
}
}
类似 ios 侧处理这边的信息,并返回
switch call.method {
case"updateBadge":
iflet args = call.arguments as? Dictionary<String, Any>,
letcount = 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 =
result(nil)
case"isAppBadgeSupported":
result(true)
default:
result(FlutterMethodNotImplemented)
}
import {
FlutterPlugin,
FlutterPluginBinding,
MethodCall,
MethodCallHandler,
MethodChannel,
MethodResult,
} from '@ohos/flutter_ohos';
import { BusinessError } from '@kit.BasicServicesKit';
import { notificationManager } from '@kit.NotificationKit';
/** FlutterAppIconBadgePlugin **/
export defaultclass 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('Badgecount 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(`Succeededin setting badge number to ${badgeNumber}.`);
result.success(null);
}).catch((err: BusinessError) => {
console.error(`Failed to set badge number. Codeis ${err.code}, message is ${err.message}`);
result.error(err.code.toString(), err.message, null);
});
} catch (e) {
const error = e asError;
console.error(`Error processing updateBadge: ${error.message}`);
result.error("UNKNOWN_ERROR", error.message, null);
}
}
elseif (call.method == "removeBadge") {
try {
notificationManager.setBadgeNumber().then(() => {
console.info(`Succeededin removing badge number.`);
result.success(null);
}).catch((err: BusinessError) => {
console.error(`Failed to remove badge number. Codeis ${err.code}, message is ${err.message}`);
result.error(err.code.toString(), err.message, null);
});
} catch (e) {
const error = e asError;
console.error(`Error processing removeBadge: ${error.message}`);
result.error("UNKNOWN_ERROR", error.message, null);
}
} elseif (call.method == "isAppBadgeSupported") {
// 鸿蒙平台默认支持角标
result.success(true);
} else {
result.notImplemented();
}
}
}
请求通知权限[8]
应用需要获取用户授权才能发送通知。在通知发布前调用requestEnableNotification()[9]方法,弹窗让用户选择是否允许发送通知,后续再次调用requestEnableNotification()[10]方法时,则不再弹窗。
通知授权接口功能介绍
接口名 | 描述 |
---|---|
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( == 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}`);
});
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 **/
exportdefaultclass FlutterAppIconBadgePlugin implements FlutterPlugin, MethodCallHandler, AbilityAware {
private channel: MethodChannel | null = null;
private static _context: common.UIAbilityContext | null = null;
constructor() {
}
staticget 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 asError;
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 ( == 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
}
} elseif (call.method == "removeBadge") {
try {
notificationManager.setBadgeNumber().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 asError;
console.error(`Error processing removeBadge: ${error.message}`);
result.error("UNKNOWN_ERROR", error.message, null);
}
} elseif (call.method == "isAppBadgeSupported") {
// 鸿蒙平台默认支持角标
result.success(true);
} else {
result.notImplemented();
}
}
}
在插件根目录下创建一个名为 example
的文件夹,用于存放示例应用。在 example
文件夹中,创建一个鸿蒙平台的 Flutter 应用,用于验证插件功能。
使用 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 鸿蒙适配的开发者们,让大家在鸿蒙生态的开发中更加得心应手。
坚果派由坚果等人创建,团队拥有若干华为 HDE,以及若干其他领域的三十余位万粉博主运营。专注于分享的技术包括 HarmonyOS/OpenHarmony,ArkUI-X,元服务,服务卡片,华为自研语言,BlueOS 操作系统、团队成员聚集在北京、上海、广州、深圳、南京、杭州、苏州、宁夏等地。 聚焦“鸿蒙原生应用”、“智能物联”和“AI 赋能”、“人工智能”四大业务领域,依托华为开发者专家等强大的技术团队,以及涵盖需求、开发、测试、运维于一体的综合服务体系,赋能文旅、媒体、社交、家居、消费电子等行业客户,满足社区客户数字化升级转型的需求,帮助客户实现价值提升。 目前上架鸿蒙原生应用 18 款,三方库 72 个。
地址:https://atomgit.com/nutpi
https://gitcode.com/nutpi
在刚开始适配完成后,并没有成功在桌面上生成角标,原因是没有申请权限。
参考资料
[1]
Flutter: https://flutter.io/
[2]
Flutter使用指导文档: https://gitcode.com/openharmony-sig/flutter_samples/blob/master/ohos/docs/07_plugin/ohos%E5%B9%B3%E5%8F%B0%E9%80%82%E9%85%8Dflutter%E4%B8%89%E6%96%B9%E5%BA%93%E6%8C%87%E5%AF%BC.md
[3]
Flutter 配置指南: https://gitcode.com/openharmony-sig/flutter_flutter/blob/master/README.md
[4]
官方插件库: https://pub.dev/
[5]
flutter_app_icon_badge: https://pub-web.flutter-io.cn/packages/flutter_app_icon_badge
[6]
通用错误码: https://developer.huawei.com/consumer/cn/doc/harmonyos-references/errorcode-universal
[7]
通知错误码: https://developer.huawei.com/consumer/cn/doc/harmonyos-references/errorcode-notification
[8]
请求通知权限: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/notification-enable
[9]
requestEnableNotification(): https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-notificationmanager#notificationmanagerrequestenablenotification10-1
[10]
requestEnableNotification(): https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-notificationmanager#notificationmanagerrequestenablenotification10-1
[11]
开发package: https://gitcode.com/openharmony-sig/flutter_samples/blob/master/ohos/docs/04_development/开发package.md
[12]
开发plugin: https://gitcode.com/openharmony-sig/flutter_samples/blob/master/ohos/docs/04_development/开发plugin.md
[13]
developing-packages: https://docs.flutter.cn/packages-and-plugins/developing-packages
[14]
适配仓库地址: https://gitcode.com/nutpi/flutter_app_icon_badge