Flutter 三方库 flutter_local_notifications 的鸿蒙化适配与实战指南

Flutter 三方库 flutter_local_notifications 的鸿蒙化适配与实战指南


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Hey 各位小伙伴们!,上海某大学计算机专业大一学生 👨‍💻。从大一上学期开始接触 Flutter,最近开始折腾 Flutter for OpenHarmony,发现这玩意儿真的香!但坑也是真的多......😭

今天来聊聊我在聊天 App 里集成本地通知推送的血泪史,这玩意儿在鸿蒙上适配起来真是一言难尽。

一、为什么需要本地通知?

在现代即时通讯 App 里,本地通知简直是刚需中的刚需!想象一下:

  • 朋友发消息了,但 App 在后台挂着
  • 你需要定时提醒功能(比如会议通知)
  • 消息分类展示(普通消息、群消息、系统通知)

没有通知推送,聊天 App 就等于残了一半!

在 Flutter for OpenHarmony 开发中,flutter_local_notifications 是最常用的本地通知库,虽然不是专门为鸿蒙写的,但经过适配后基本能用。

二、依赖配置

首先在 pubspec.yaml 里加上依赖:

yaml 复制代码
dependencies:
  flutter_local_notifications: ^18.0.1
  timezone: ^0.10.0

然后执行:

bash 复制代码
flutter pub get

AtomGit 适配仓库说明:该库在鸿蒙上的支持主要依赖于底层的通知通道实现,建议关注 AtomGit 上的 flutter_local_notifications 相关 issue 获得最新适配进度:https://atomgit.com/xxx/flutter_local_notifications_ohos

三、创建通知服务

我封装了一个 NotificationService,专门管理所有通知相关的操作:

dart 复制代码
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/timezone.dart' as tz;
import 'package:timezone/data/latest.dart' as tz_data;

/// 本地通知服务
/// 用于在App后台或前台显示消息推送通知
class NotificationService {
  // 单例模式,确保全局只有一个通知实例
  static NotificationService? _instance;
  static NotificationService get instance => _instance ??= NotificationService._();

  final FlutterLocalNotificationsPlugin _notifications = FlutterLocalNotificationsPlugin();
  bool _isInitialized = false;
  
  // 通知点击回调,当用户点击通知时触发
  void Function(String? payload)? onNotificationTap;

  NotificationService._();

  // 获取通知插件实例,供外部使用
  FlutterLocalNotificationsPlugin get plugin => _notifications;

  /// 初始化通知服务【重点方法】
  Future<bool> initialize() async {
    if (_isInitialized) return true;

    try {
      // 初始化时区数据库【鸿蒙坑点1】
      // 鸿蒙设备如果不初始化时区,定时通知会不准!
      tz_data.initializeTimeZones();

      // Android 平台配置
      // 图标资源放在 android/app/src/main/res/drawable 目录
      const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
      
      // iOS/macOS 平台配置
      // 需要请求通知权限
      const darwinSettings = DarwinInitializationSettings(
        requestAlertPermission: true,
        requestBadgePermission: true,
        requestSoundPermission: true,
      );

      const initSettings = InitializationSettings(
        android: androidSettings,
        iOS: darwinSettings,
        macOS: darwinSettings,
      );

      // 初始化插件并设置通知点击回调
      final result = await _notifications.initialize(
        initSettings,
        onDidReceiveNotificationResponse: _onNotificationResponse,
      );

      _isInitialized = result ?? false;
      
      if (_isInitialized) {
        debugPrint('通知服务初始化成功!');
      } else {
        debugPrint('通知服务初始化失败...');
      }

      return _isInitialized;
    } catch (e) {
      debugPrint('通知服务初始化异常: $e');
      return false;
    }
  }

  /// 处理通知点击事件
  void _onNotificationResponse(NotificationResponse response) {
    debugPrint('用户点击了通知: ${response.payload}');
    // 这里可以实现跳转到对应的聊天页面
    onNotificationTap?.call(response.payload);
  }

鸿蒙适配踩坑纪实

踩坑1:时区问题 💀

刚开始写定时通知功能的时候,定时通知时间总是差8小时!查了半天发现是时区没初始化。鸿蒙设备默认时区和我们东八区不一致,必须手动调用 tz_data.initializeTimeZones() 才能正确处理定时通知。

踩坑2:权限请求时机

在鸿蒙上请求通知权限的时机很重要!我一开始在初始化的时候直接请求,结果用户拒绝后就没办法再请求了。后来改成在用户首次触发通知操作时才请求,这样体验好很多。

四、请求通知权限

dart 复制代码
/// 请求通知权限【重要】
Future<bool> requestPermissions() async {
  try {
    if (Platform.isIOS) {
      // iOS 平台需要弹窗请求权限
      final result = await _notifications
          .resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
          ?.requestPermissions(
            alert: true,
            badge: true,
            sound: true,
          );
      return result ?? false;
    } else if (Platform.isAndroid) {
      // Android/鸿蒙平台
      final android = _notifications.resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>();
      final result = await android?.requestNotificationsPermission();
      return result ?? false;
    }
    return false;
  } catch (e) {
    debugPrint('请求通知权限失败: $e');
    return false;
  }
}

五、发送通知

dart 复制代码
/// 显示简单通知
Future<void> showNotification({
  required int id,
  required String title,
  required String body,
  String? payload,
}) async {
  try {
    // Android 通知渠道配置
    const androidDetails = AndroidNotificationDetails(
      'chat_channel',        // 渠道ID
      '聊天消息',             // 渠道名称
      channelDescription: '聊天消息通知',
      importance: Importance.high,  // 高优先级
      priority: Priority.high,      // 高优先级
      showWhen: true,              // 显示发布时间
    );

    // iOS 通知配置
    const darwinDetails = DarwinNotificationDetails(
      presentAlert: true,
      presentBadge: true,
      presentSound: true,
    );

    const details = NotificationDetails(
      android: androidDetails,
      iOS: darwinDetails,
      macOS: darwinDetails,
    );

    await _notifications.show(id, title, body, details, payload: payload);
    debugPrint('通知发送成功: $title');
  } catch (e) {
    debugPrint('通知发送失败: $e');
  }
}

/// 显示聊天消息通知【封装方法】
Future<void> showChatNotification({
  required String senderName,
  required String message,
  required String conversationId,
}) async {
  final id = conversationId.hashCode;
  await showNotification(
    id: id,
    title: senderName,
    body: message.length > 50 
        ? '${message.substring(0, 50)}...' 
        : message,
    payload: conversationId,
  );
}

六、定时通知

这个功能做消息提醒特别有用,比如"5分钟后提醒我开会":

dart 复制代码
/// 定时通知
Future<void> scheduleNotification({
  required int id,
  required String title,
  required String body,
  required DateTime scheduledTime,
  String? payload,
}) async {
  try {
    const androidDetails = AndroidNotificationDetails(
      'schedule_channel',
      '定时通知',
      channelDescription: '定时提醒通知',
      importance: Importance.high,
      priority: Priority.high,
    );

    const darwinDetails = DarwinNotificationDetails(
      presentAlert: true,
      presentBadge: true,
      presentSound: true,
    );

    const details = NotificationDetails(
      android: androidDetails,
      iOS: darwinDetails,
      macOS: darwinDetails,
    );

    await _notifications.zonedSchedule(
      id,
      title,
      body,
      tz.TZDateTime.from(scheduledTime, tz.local),
      details,
      androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
      payload: payload,
      // 【鸿蒙坑点2】必须添加这个参数!
      uiLocalNotificationDateInterpretation:
          UILocalNotificationDateInterpretation.absoluteTime,
    );
    debugPrint('定时通知已设置: ${scheduledTime.toString()}');
  } catch (e) {
    debugPrint('定时通知设置失败: $e');
  }
}

踩坑3:zonedSchedule 缺少参数 🔥

这是我在鸿蒙上遇到的最大坑!zonedSchedule 方法在鸿蒙上必须传 uiLocalNotificationDateInterpretation 参数,否则会直接报错。如果没看到这个参数就复制别人的代码,很可能会踩坑!

七、取消通知

dart 复制代码
/// 取消指定通知
Future<void> cancelNotification(int id) async {
  await _notifications.cancel(id);
}

/// 取消所有通知
Future<void> cancelAllNotifications() async {
  await _notifications.cancelAll();
}

八、Android 清单配置

android/app/src/main/AndroidManifest.xml 中添加必要的权限:

xml 复制代码
<!-- 通知权限 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<!-- 精确闹钟权限(定时通知需要)-->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>

<!-- 接收开机广播(确保App被kill后通知仍能工作)-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

<!-- 震动权限 -->
<uses-permission android:name="android.permission.VIBRATE"/>

<!-- 在 Application 标签内添加 -->
<application>
    <!-- 通知Receiver -->
    <receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
    <receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
            <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
            <action android:name="android.intent.action.QUICKBOOT_POWERON" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </receiver>
</application>

九、效果展示

功能验证结果:

  • ✅ 通知发送成功
  • ✅ 通知点击回调正常
  • ✅ 定时通知准确触发
  • ✅ 通知权限请求正常
  • ✅ 通知取消功能正常

十、总结心得

作为一个刚学编程的大一学生,搞定这个通知功能真的让我很有成就感!虽然过程很曲折(主要是被各种坑虐),但最后看到通知弹出来的那一刻,真的超级满足!

主要收获:

  1. 学会了看官方文档和源码,遇到问题先 Google 再看源码
  2. 鸿蒙平台的适配确实比 Android/iOS 更复杂,需要更多耐心
  3. 单例模式在实际项目中的使用场景

踩坑反思:

  • 不要直接复制网上的代码,每个平台可能略有不同
  • 时区问题一定要重视,不然定时通知会出大问题
  • 权限请求要做好容错处理,用户拒绝也要有备选方案

后续计划:

  • 继续完善聊天功能,比如音视频通话
  • 尝试自己写一个鸿蒙原生的通知插件

小明同学的 Flutter 鸿蒙开发之路还在继续,如果你也有类似的踩坑经历,欢迎在评论区分享!一起加油!💪

往期回顾:

  • Flutter 三方库 dio 的鸿蒙化适配与实战指南
  • Flutter 三方库 image_picker 的鸿蒙化适配与实战指南
相关推荐
李李李勃谦2 小时前
基于鸿蒙PC多窗口特性的笔记管理工具开发实践
笔记·华为·harmonyos
小雨青年2 小时前
鸿蒙 HarmonyOS 6 | Swiper滑动状态变化事件回调开发实战续篇
华为·harmonyos
Hello__77772 小时前
开源鸿蒙 Flutter 实战|用户详情页布局优化与字体大小调节功能全流程实现
flutter·开源·harmonyos
IntMainJhy3 小时前
Flutter 三方库 url_launcher + link_preview 的鸿蒙化适配与实战指南
flutter·华为·harmonyos
拉拉尼亚3 小时前
flutter轻量级本地存储shared_preferences 教程
flutter·安卓
心走3 小时前
记录鸿蒙相机输出预览流报错问题(CAMERA_SERVICE_FATAL_ERROR)
harmonyos
jiejiejiejie_4 小时前
自定义导航栏组件
flutter·华为·harmonyos
云_杰4 小时前
拒绝社死!旁边有人偷瞄?教你给App加上鸿蒙系统级“防窥”黑科技!
安全·harmonyos
想你依然心痛4 小时前
HarmonyOS 6(API 23)实战:基于 Face AR 疼痛评估与 Body AR 姿态追踪的“智能康复训练助手“
华为·ar·harmonyos·悬浮导航·沉浸光感