Firebase 通知在前台时在 ios 上重复

问题描述 投票:0回答:1

在前台时,iOS 上的通知会重复,但如果我将前台警报设置为 false,我根本不会收到任何通知,但会进入控制台日志

等待 FirebaseMessaging.instance.setForegroundNotificationPresentationOptions( Alert: false, ///如果我将其设置为 false,则在前台时根本不会收到通知 徽章:真实, 声音:真实, );

import 'dart:developer';
import 'dart:async';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'dart:io';

class NotificationService {
  static final NotificationService _instance = NotificationService._init();
  factory NotificationService() => _instance;

  NotificationService._init();

  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
  bool _initialized = false;

  Future<String?> runFirebase() async {
    if (_initialized) {
      log("NotificationService is already initialized.");
      return null;
    }
    _initialized = true;

    log("Initializing NotificationService...");

    // Request notification permissions
    await FirebaseMessaging.instance.requestPermission(
      alert: true,
      announcement: false,
      badge: true,
      carPlay: false,
      criticalAlert: false,
      provisional: false,
      sound: true,
    );

    // Get the FCM token
    String? fcmToken = await FirebaseMessaging.instance.getToken();
    log("FCM Token: $fcmToken");

    // Initialization settings for Android
    const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');

    // Initialization settings for iOS
    const DarwinInitializationSettings initializationSettingsIOS = DarwinInitializationSettings(
      requestAlertPermission: true,
      requestBadgePermission: true,
      requestSoundPermission: true,
    );

    // Combine initialization settings
    const InitializationSettings initializationSettings = InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsIOS,
    );

    // Initialize the plugin
    await flutterLocalNotificationsPlugin.initialize(
      initializationSettings,
      onDidReceiveNotificationResponse: (NotificationResponse response) {
        log("Notification Tapped");
        // Handle notification tap logic here
      },
    );

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

    // Subscribe to a topic if needed
    await FirebaseMessaging.instance.subscribeToTopic('all');
    log("Subscribed to topic 'all'");

    // 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) {
            // Image for 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(
                message.notification.hashCode,
                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) {
            // Image for iOS
            final attachmentPath = await _downloadAndSaveImage(imageUrl, 'notification_image.jpg');

            if (attachmentPath != null) {
              log("Displaying iOS notification with image.");
              await flutterLocalNotificationsPlugin.show(
                message.notification.hashCode,
                message.notification?.title ?? '',
                message.notification?.body ?? '',
                NotificationDetails(
                  iOS: DarwinNotificationDetails(
                    presentAlert: true,
                    presentBadge: true,
                    presentSound: true,
                    attachments: [
                      DarwinNotificationAttachment(
                        attachmentPath,
                        identifier: 'notification_image',
                      )
                    ],
                  ),
                ),
              );
            }
          }
        } else {
          // Handle notification without image
          log("Displaying standard notification without image.");

          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(
            message.notification.hashCode,
            message.notification?.title ?? '',
            message.notification?.body ?? '',
            platformDetails,
          );
        }
      }
    });

    // Listen for messages opened from background state
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      log("A message was opened from background state: ${message.messageId}");
      // Handle navigation or other logic here
    });

    // Handle messages when the app is opened from a terminated state
    FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message) {
      if (message != null) {
        log("The app was opened from a terminated state by tapping on a notification.");
        // Handle navigation or other logic here
      }
    });

    return fcmToken;
  }

  Future<String?> _downloadAndSaveImage(String url, String fileName) async {
    try {
      final response = await http.get(Uri.parse(url));
      if (response.statusCode == 200) {
        final directory = await getTemporaryDirectory();
        final filePath = '${directory.path}/$fileName';
        final file = File(filePath);
        await file.writeAsBytes(response.bodyBytes);
        log("Image saved to $filePath");
        return filePath;
      } else {
        log("Failed to download image. Status code: ${response.statusCode}");
      }
    } catch (e) {
      log("Failed to download image: $e");
    }
    return null;
  }
}

预计在前台时仅收到 1 条通知。适用于安卓

flutter notifications firebase-cloud-messaging flutter-local-notification
1个回答
0
投票

当应用程序位于 iOS 前台时,重复通知的问题源于 FirebaseMessaging 和 flutter_local_notifications 一起处理通知的方式。以下是解决此问题的方法:

设置演示选项:在setForegroundNotificationPresentationOptions中设置alert: false会在应用程序位于前台时禁用所有通知,因此请保持alert: true。为了控制可见性,我们将专注于根据您的代码逻辑过滤通知。

过滤前台通知:在 FirebaseMessaging.onMessage 中,仅当应用位于前台时使用 flutterLocalNotificationsPlugin 显示通知。通过有条件地处理通知的显示时间和方式,确保 iOS 不会显示重复的通知。

这是一种重构方法:

import 'dart:async';
import 'dart:developer';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'dart:io';

class NotificationService {
  static final NotificationService _instance = NotificationService._init();
  factory NotificationService() => _instance;
  NotificationService._init();

  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
  bool _initialized = false;

  Future<String?> runFirebase() async {
    if (_initialized) return null;
    _initialized = true;

    // Request permissions
    await FirebaseMessaging.instance.requestPermission(
      alert: true, badge: true, sound: true,
    );

    // Setup initialization for Android and iOS
    const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');
    const DarwinInitializationSettings initializationSettingsIOS = DarwinInitializationSettings(
      requestAlertPermission: true, requestBadgePermission: true, requestSoundPermission: true,
    );

    const InitializationSettings initializationSettings = InitializationSettings(
      android: initializationSettingsAndroid, iOS: initializationSettingsIOS,
    );

    await flutterLocalNotificationsPlugin.initialize(initializationSettings, onDidReceiveNotificationResponse: (response) {
      log("Notification tapped.");
    });

    // Set options to display notifications in foreground
    await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(alert: true, badge: true, sound: true);

    // Subscribe to topic
    await FirebaseMessaging.instance.subscribeToTopic('all');

    // Listen to foreground messages
    FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
      log("Foreground notification received.");

      // Display notification with image if provided
      if (message.notification != null) {
        String? imageUrl = message.data['image'];
        await _showNotification(message, imageUrl);
      }
    });

    // Handle messages opened from background
    FirebaseMessaging.onMessageOpenedApp.listen((message) {
      log("Notification opened from background: ${message.messageId}");
    });

    FirebaseMessaging.instance.getInitialMessage().then((message) {
      if (message != null) {
        log("Notification opened from terminated state.");
      }
    });

    // Get FCM token
    String? fcmToken = await FirebaseMessaging.instance.getToken();
    log("FCM Token: $fcmToken");
    return fcmToken;
  }

  Future<void> _showNotification(RemoteMessage message, String? imageUrl) async {
    final NotificationDetails platformChannelSpecifics;

    if (Platform.isAndroid) {
      platformChannelSpecifics = await _buildAndroidNotificationDetails(message, imageUrl);
    } else {
      platformChannelSpecifics = await _buildIOSNotificationDetails(message, imageUrl);
    }

    await flutterLocalNotificationsPlugin.show(
      message.notification.hashCode,
      message.notification?.title ?? '',
      message.notification?.body ?? '',
      platformChannelSpecifics,
    );
  }

  Future<NotificationDetails> _buildAndroidNotificationDetails(RemoteMessage message, String? imageUrl) async {
    if (imageUrl != null) {
      final bigPicture = await _downloadAndSaveImage(imageUrl, 'bigPicture');
      final largeIcon = await _downloadAndSaveImage(imageUrl, 'largeIcon');

      return NotificationDetails(
        android: AndroidNotificationDetails(
          'channelid', 'channelname',
          channelDescription: 'channel description',
          importance: Importance.high,
          priority: Priority.high,
          largeIcon: largeIcon != null ? FilePathAndroidBitmap(largeIcon) : null,
          styleInformation: bigPicture != null
              ? BigPictureStyleInformation(
                  FilePathAndroidBitmap(bigPicture),
                  largeIcon: largeIcon != null ? FilePathAndroidBitmap(largeIcon) : null,
                  contentTitle: message.notification?.title,
                  summaryText: message.notification?.body,
                )
              : null,
        ),
      );
    } else {
      return NotificationDetails(
        android: AndroidNotificationDetails(
          'channelid', 'channelname',
          channelDescription: 'channel description',
          importance: Importance.high,
          priority: Priority.high,
        ),
      );
    }
  }

  Future<NotificationDetails> _buildIOSNotificationDetails(RemoteMessage message, String? imageUrl) async {
    if (imageUrl != null) {
      final attachmentPath = await _downloadAndSaveImage(imageUrl, 'notification_image.jpg');
      return NotificationDetails(
        iOS: DarwinNotificationDetails(
          presentAlert: true, presentBadge: true, presentSound: true,
          attachments: [DarwinNotificationAttachment(attachmentPath!, identifier: 'notification_image')],
        ),
      );
    } else {
      return const NotificationDetails(
        iOS: DarwinNotificationDetails(
          presentAlert: true, presentBadge: true, presentSound: true,
        ),
      );
    }
  }

  Future<String?> _downloadAndSaveImage(String url, String fileName) async {
    try {
      final response = await http.get(Uri.parse(url));
      if (response.statusCode == 200) {
        final directory = await getTemporaryDirectory();
        final filePath = '${directory.path}/$fileName';
        final file = File(filePath);
        await file.writeAsBytes(response.bodyBytes);
        return filePath;
      }
    } catch (e) {
      log("Error downloading image: $e");
    }
    return null;
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.