在前台时,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 条通知。适用于安卓
当应用程序位于 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;
}
}