You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS前台Firebase通知重复,关闭alert则无法接收通知求助

问题分析

iOS前台通知重复的核心原因:

  • 当setForegroundNotificationPresentationOptions(alert: true)时,FCM会自动在前台弹出系统通知
  • 同时代码里onMessage监听又通过flutter_local_notifications手动弹出一次通知,两者叠加导致重复

而设alert: false后收不到通知,是因为当通知包含图片但下载失败时,代码没有兜底展示无图通知,系统又不自动展示,所以完全看不到。

解决方案

1. 关闭FCM前台自动展示alert,避免重复

修改前台展示选项,让系统不再自动弹出alert,完全由手动展示控制:

await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
  alert: false,
  badge: true,
  sound: true,
);

2. 修复iOS端通知展示的兜底逻辑

当图片下载失败时,也要展示无图通知,避免收不到的情况:
修改onMessage中iOS带图通知的逻辑:

else if (Platform.isIOS) {
  final attachmentPath = await _downloadAndSaveImage(imageUrl, 'notification_image.jpg');

  log("Displaying iOS notification (with image: ${attachmentPath != null})");
  // 不管图片是否下载成功,都展示通知
  NotificationDetails platformDetails;
  if (attachmentPath != null) {
    platformDetails = NotificationDetails(
      iOS: DarwinNotificationDetails(
        presentAlert: true,
        presentBadge: true,
        presentSound: true,
        attachments: [
          DarwinNotificationAttachment(
            attachmentPath,
            identifier: 'notification_image',
          )
        ],
      ),
    );
  } else {
    // 图片下载失败,展示无图通知
    platformDetails = const NotificationDetails(
      iOS: DarwinNotificationDetails(
        presentAlert: true,
        presentBadge: true,
        presentSound: true,
      ),
    );
  }
  await flutterLocalNotificationsPlugin.show(
    int.parse(message.messageId ?? DateTime.now().millisecondsSinceEpoch.toString()),
    message.notification?.title ?? '',
    message.notification?.body ?? '',
    platformDetails,
  );
}

3. 优化通知ID,确保唯一性

原代码用message.notification.hashCode作为通知ID,可能出现重复覆盖的情况,建议使用FCM提供的唯一ID或时间戳:

// 替换原通知ID生成逻辑
int.parse(message.messageId ?? DateTime.now().millisecondsSinceEpoch.toString())

关键代码修改后的完整片段

// Set foreground notification presentation options
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
  alert: false,
  badge: true,
  sound: true,
);

// Listen for foreground messages
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
  log("onMessage called: ${message.notification?.title ?? ''}");

  if (message.notification != null) {
    String? imageUrl = message.data['image'] ?? message.notification?.android?.imageUrl ?? message.notification?.apple?.imageUrl;

    if (imageUrl != null) {
      log("Image URL: $imageUrl");

      if (Platform.isAndroid) {
        // 保留原Android逻辑不变
        final bigPicture = await _downloadAndSaveImage(imageUrl, 'bigPicture');
        final largeIcon = await _downloadAndSaveImage(imageUrl, 'largeIcon');

        if (bigPicture != null) {
          log("Displaying Android notification with image.");

          await flutterLocalNotificationsPlugin.show(
            int.parse(message.messageId ?? DateTime.now().millisecondsSinceEpoch.toString()),
            message.notification?.title ?? '',
            message.notification?.body ?? '',
            NotificationDetails(
              android: AndroidNotificationDetails(
                'channelid',
                'channelname',
                channelDescription: 'channel description',
                importance: Importance.high,
                priority: Priority.high,
                largeIcon: largeIcon != null ? FilePathAndroidBitmap(largeIcon) : null,
                styleInformation: BigPictureStyleInformation(
                  FilePathAndroidBitmap(bigPicture),
                  largeIcon: largeIcon != null ? FilePathAndroidBitmap(largeIcon) : null,
                  contentTitle: message.notification?.title,
                  summaryText: message.notification?.body,
                ),
              ),
            ),
          );
        }
      } else if (Platform.isIOS) {
        final attachmentPath = await _downloadAndSaveImage(imageUrl, 'notification_image.jpg');

        log("Displaying iOS notification (with image: ${attachmentPath != null})");
        NotificationDetails platformDetails;
        if (attachmentPath != null) {
          platformDetails = NotificationDetails(
            iOS: DarwinNotificationDetails(
              presentAlert: true,
              presentBadge: true,
              presentSound: true,
              attachments: [
                DarwinNotificationAttachment(
                  attachmentPath,
                  identifier: 'notification_image',
                )
              ],
            ),
          );
        } else {
          platformDetails = const NotificationDetails(
            iOS: DarwinNotificationDetails(
              presentAlert: true,
              presentBadge: true,
              presentSound: true,
            ),
          );
        }
        await flutterLocalNotificationsPlugin.show(
          int.parse(message.messageId ?? DateTime.now().millisecondsSinceEpoch.toString()),
          message.notification?.title ?? '',
          message.notification?.body ?? '',
          platformDetails,
        );
      }
    } else {
      // 处理无图通知,同时优化通知ID
      NotificationDetails platformDetails;
      if (Platform.isAndroid) {
        platformDetails = const NotificationDetails(
          android: AndroidNotificationDetails(
            'daiwa_food_notification',
            'daiwa_food_notification',
            channelDescription: 'Daiwa Food Channel',
            icon: '@mipmap/ic_launcher',
            importance: Importance.high,
            priority: Priority.high,
          ),
        );
      } else {
        platformDetails = const NotificationDetails(
          iOS: DarwinNotificationDetails(
            presentAlert: true,
            presentBadge: true,
            presentSound: true,
          ),
        );
      }

      await flutterLocalNotificationsPlugin.show(
        int.parse(message.messageId ?? DateTime.now().millisecondsSinceEpoch.toString()),
        message.notification?.title ?? '',
        message.notification?.body ?? '',
        platformDetails,
      );
    }
  }
});

内容的提问来源于stack exchange,提问作者adeli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 14:38:11