9.4 推送通知

推送通知是 App 召回用户、传递信息的重要手段。Flutter 中推送通知分为本地通知和远程推送两种,通常结合使用。


一、本地通知(flutter_local_notifications)

yaml 复制代码
dependencies:
  flutter_local_notifications: ^17.2.1

1.1 初始化

dart 复制代码
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

class LocalNotificationService {
  static final _plugin = FlutterLocalNotificationsPlugin();

  static Future<void> initialize() async {
    const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
    const iosSettings = DarwinInitializationSettings(
      requestAlertPermission: true,
      requestBadgePermission: true,
      requestSoundPermission: true,
    );

    const settings = InitializationSettings(
      android: androidSettings,
      iOS: iosSettings,
    );

    await _plugin.initialize(
      settings,
      onDidReceiveNotificationResponse: _onNotificationTapped,
    );

    // 请求 Android 13+ 通知权限
    await _plugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.requestNotificationsPermission();
  }

  // 用户点击通知时的回调
  static void _onNotificationTapped(NotificationResponse response) {
    final payload = response.payload;
    if (payload != null) {
      final data = jsonDecode(payload);
      router.push('/order/${data['orderId']}'); // 跳转到对应页面
    }
  }
}

1.2 显示通知

dart 复制代码
// 简单通知
static Future<void> showSimple({
  required String title,
  required String body,
  String? payload,
}) async {
  const androidDetails = AndroidNotificationDetails(
    'default_channel',
    '默认通知',
    channelDescription: '应用默认通知渠道',
    importance: Importance.high,
    priority: Priority.high,
    icon: '@mipmap/ic_launcher',
    largeIcon: DrawableResourceAndroidBitmap('@mipmap/ic_launcher'),
  );

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

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

  await _plugin.show(
    DateTime.now().millisecond, // 通知 ID
    title,
    body,
    details,
    payload: payload,
  );
}

// 带进度条的通知(文件下载)
static Future<void> showProgress({
  required int id,
  required String title,
  required int progress,
  required int maxProgress,
}) async {
  final androidDetails = AndroidNotificationDetails(
    'download_channel',
    '下载通知',
    channelDescription: '文件下载进度',
    importance: Importance.low,
    showProgress: true,
    maxProgress: maxProgress,
    progress: progress,
    ongoing: true, // 不可滑动删除
    autoCancel: false,
  );

  await _plugin.show(id, title, '$progress%', NotificationDetails(android: androidDetails));
}

// 定时通知
static Future<void> scheduleNotification({
  required String title,
  required String body,
  required DateTime scheduledTime,
}) async {
  await _plugin.zonedSchedule(
    0,
    title,
    body,
    tz.TZDateTime.from(scheduledTime, tz.local),
    const NotificationDetails(
      android: AndroidNotificationDetails('reminder', '提醒通知'),
      iOS: DarwinNotificationDetails(),
    ),
    androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
    uiLocalNotificationDateInterpretation:
        UILocalNotificationDateInterpretation.absoluteTime,
  );
}

二、Firebase Cloud Messaging(远程推送)

yaml 复制代码
dependencies:
  firebase_core: ^2.30.0
  firebase_messaging: ^14.8.0

2.1 初始化 FCM

dart 复制代码
import 'package:firebase_messaging/firebase_messaging.dart';

class PushNotificationService {
  static final _messaging = FirebaseMessaging.instance;

  static Future<void> initialize() async {
    // 请求推送权限
    final settings = await _messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
      provisional: false,
    );
    debugPrint('Push permission: ${settings.authorizationStatus}');

    // 获取 FCM Token(发送给后端保存)
    final token = await _messaging.getToken();
    debugPrint('FCM Token: $token');
    await _sendTokenToServer(token);

    // 监听 Token 刷新
    _messaging.onTokenRefresh.listen(_sendTokenToServer);

    // 前台消息处理
    FirebaseMessaging.onMessage.listen(_handleForegroundMessage);

    // 后台点击通知(App 从后台唤起)
    FirebaseMessaging.onMessageOpenedApp.listen(_handleNotificationTap);

    // 冷启动点击通知(App 完全关闭时)
    final initialMessage = await _messaging.getInitialMessage();
    if (initialMessage != null) {
      _handleNotificationTap(initialMessage);
    }
  }

  // 前台收到消息:显示本地通知
  static void _handleForegroundMessage(RemoteMessage message) {
    debugPrint('前台消息: ${message.notification?.title}');

    if (message.notification != null) {
      LocalNotificationService.showSimple(
        title: message.notification!.title ?? '',
        body: message.notification!.body ?? '',
        payload: jsonEncode(message.data),
      );
    }
  }

  // 用户点击通知:跳转到对应页面
  static void _handleNotificationTap(RemoteMessage message) {
    final data = message.data;
    switch (data['type']) {
      case 'order':
        router.push('/order/${data['orderId']}');
        break;
      case 'chat':
        router.push('/chat/${data['roomId']}');
        break;
      case 'promotion':
        router.push('/promotion/${data['promotionId']}');
        break;
    }
  }

  static Future<void> _sendTokenToServer(String? token) async {
    if (token == null) return;
    await ApiClient.post('/device/register', body: {'fcmToken': token});
  }

  // 主题订阅
  static Future<void> subscribeToTopic(String topic) =>
      _messaging.subscribeToTopic(topic);

  static Future<void> unsubscribeFromTopic(String topic) =>
      _messaging.unsubscribeFromTopic(topic);
}

2.2 后台消息处理

dart 复制代码
// main.dart --- 后台消息处理器(必须是顶层函数)
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  debugPrint('后台消息: ${message.messageId}');
  // 可以在此更新本地数据库、显示本地通知等
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  // 注册后台处理器
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  await PushNotificationService.initialize();
  runApp(const MyApp());
}

三、通知渠道管理(Android)

dart 复制代码
static Future<void> createNotificationChannels() async {
  final androidPlugin = _plugin
      .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();

  // 订单通知(高优先级,有声音)
  await androidPlugin?.createNotificationChannel(
    const AndroidNotificationChannel(
      'order_channel',
      '订单通知',
      description: '订单状态变更通知',
      importance: Importance.high,
      sound: RawResourceAndroidNotificationSound('order_sound'),
    ),
  );

  // 活动通知(低优先级,静默)
  await androidPlugin?.createNotificationChannel(
    const AndroidNotificationChannel(
      'promotion_channel',
      '活动通知',
      description: '促销活动推送',
      importance: Importance.low,
    ),
  );
}

四、角标管理

yaml 复制代码
dependencies:
  flutter_app_badger: ^1.5.0
dart 复制代码
import 'package:flutter_app_badger/flutter_app_badger.dart';

// 设置角标
FlutterAppBadger.updateBadgeCount(unreadCount);

// 清除角标
FlutterAppBadger.removeBadge();

小结

功能 方案
本地通知 flutter_local_notifications
远程推送 Firebase Cloud Messaging(FCM)
前台通知 用本地通知展示远程消息
通知跳转 解析 payload/data → 路由跳转
后台处理 onBackgroundMessage(顶层函数)
角标 flutter_app_badger

👉 下一节继续阅读后续章节

相关推荐
星释2 小时前
鸿蒙Flutter实战:30.在Pub上发布鸿蒙化插件
flutter·harmonyos·鸿蒙
nice先生的狂想曲2 小时前
flutter布局(列表组件)
flutter
见山是山-见水是水2 小时前
鸿蒙flutter第三方库适配 - 动态工作流
flutter·华为·harmonyos
yeziyfx2 小时前
Flutter SingleChildScrollView内部ListView滑动不了
flutter
Zender Han2 小时前
VS Code 开发 Flutter 常用快捷键和插件工具详解
android·vscode·flutter·ios
于慨3 小时前
flutter安卓调试工具
android·flutter
见山是山-见水是水3 小时前
鸿蒙flutter第三方库适配 - 动态布局库
flutter·华为·harmonyos
欧达克3 小时前
vibe coding:2 天用 AI 鼓捣一个 APP
flutter·app
空中海5 小时前
9.3 多端支持
flutter