-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Description
Hi every one, I'm trying to handle notification in my Flutter App, I'll start showing all the code and then telling you more about the problem I'm facing.
Inside my main() function :
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
await FirebaseApi().initNotification();
FirebaseApi.dart (Handler class with all the functions called to handle notification):
import 'dart:convert';
import 'dart:io';
import 'package:eraser/eraser.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:logger/logger.dart';
import 'package:palladio_mobile/controllers/notification_handlers/notification_handler.dart';
import 'package:palladio_mobile/controllers/persistent_data_handlers/login_shared_preferences.dart';
const bool debugPrint = true;
@pragma('vm:entry-point')
Future handleBackgroundMessage(RemoteMessage message) async {
if (debugPrint) {
print("background");
print(message.toMap());
}
try {
FirebaseApi firebaseApi = FirebaseApi();
await firebaseApi.initAndSendLocalNotification(message);
} catch (e) {
print(e);
}
}
@pragma('vm:entry-point')
Future handleBackgroundMessage2(RemoteMessage message) async {
print("BACKGROUND: ${message.toMap()}");
}
class FirebaseApi {
final _androidChannel = const AndroidNotificationChannel(
'high_importance_channel',
'High Importance Notifications',
description: "This channel is used for important notifications",
importance: Importance.max,
);
final _localNotification = FlutterLocalNotificationsPlugin();
void handleMessage(RemoteMessage? message) async {
if (debugPrint) {
print("handle message");
print(message?.toMap());
}
if (message == null) return;
//azione da fare al ricevimento della notifica
NotificationHandler notificationHandler = NotificationHandler();
await notificationHandler.updateNotificationProvider(message.data);
notificationHandler.handleRoutes(message.data);
}
void handleInitialMessage(RemoteMessage? message) async {
if (debugPrint) {
print("initial message");
print(message?.toMap());
}
if (message == null) return;
//azione da fare al ricevimento della notifica
NotificationHandler notificationHandler = NotificationHandler();
await notificationHandler.updateNotificationProvider(message.data);
}
Future initLocalNotifications() async {
const iOS = DarwinInitializationSettings();
const android = AndroidInitializationSettings("@drawable/ic_launcher");
const settings = InitializationSettings(android: android, iOS: iOS);
await _localNotification.initialize(settings,
onDidReceiveNotificationResponse: (response) {
final message = RemoteMessage.fromMap(jsonDecode(response.payload!));
handleMessage(message);
});
final appLaunchDetails =
await _localNotification.getNotificationAppLaunchDetails();
if (appLaunchDetails != null && appLaunchDetails.didNotificationLaunchApp) {
if (appLaunchDetails.notificationResponse != null) {
final message = RemoteMessage.fromMap(
jsonDecode(appLaunchDetails.notificationResponse!.payload!));
handleInitialMessage(message);
}
}
final platform = _localNotification.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
await platform?.createNotificationChannel(_androidChannel);
}
initAndSendLocalNotification(RemoteMessage message) async {
await initLocalNotifications();
sendLocalNotification(message);
}
sendLocalNotification(RemoteMessage message) {
final notification = message.data;
NotificationHandler notificationHandler = NotificationHandler();
notificationHandler.handleLocalUpdates(notification);
//crea in autonomia la notifica locale quindi non serve fare nulla
if (Platform.isIOS) return;
if (debugPrint) {
print("sendLocalNotification");
print(message.toMap());
}
_localNotification.show(
message.hashCode,
notification["title_local_notification"],
notification["body_local_notification"],
NotificationDetails(
android: AndroidNotificationDetails(
_androidChannel.id, _androidChannel.name,
channelDescription: _androidChannel.description,
icon: "@drawable/ic_launcher"),
),
payload: jsonEncode(
message.toMap(),
),
);
}
Future initPushNotifications() async {
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: true, badge: true, sound: true);
FirebaseMessaging.instance.getInitialMessage().then(handleInitialMessage);
FirebaseMessaging.onMessageOpenedApp.listen(handleMessage);
FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage2);
FirebaseMessaging.onMessage.listen(
(message) {
if (debugPrint) {
print("onMessage");
print(message.toMap());
}
sendLocalNotification(message);
},
);
}
Future<String?> initNotification() async {
try {
final firebaseMessaging = FirebaseMessaging.instance;
await firebaseMessaging.requestPermission();
String? fcmToken = await firebaseMessaging.getToken();
await LoginSharedPreferences.setFcmToken(fcmToken);
//log
Logger log = Logger();
log.w("FCM TOKEN: $fcmToken");
//cancella tutte le notifiche
Eraser.clearAllAppNotifications();
//start notifications
initPushNotifications();
initLocalNotifications();
return fcmToken;
} catch (e) {
print(
"Impossibile inizializzare il gestore delle notifiche: ${e.toString()}");
}
return null;
}
}
Payload sent from my php backend:
$android = array("collapseKey"=>$collapseKey, "priority"=>"high", "notification"=>array("sound" => "default", "tag"=>$tag));
$ios = array("headers"=>array("apns-priority"=>"10", "apns-collapse-id"=>$tag), "payload" => array("aps"=>array("sound" => "default")));
// !!! inside data there're also other parameters that are needed to be able to navigate through my app after a notification is opened
// I've added title and body just to make some testing in the past and my code is still like this
$data["title_local_notification"] = $title;
$data["body_local_notification"] = $body;
$payload = ["message" => [
"token" => $toToken,
"notification"=>[
"title" => $title,
"body"=> $body,
],
"data"=>$data,
"android"=>$android,
"apns"=>$iOS,
]
];
The problem I'm facing:
-On iOS everything works (or at least for all the device I've been able to test)
-On android in debug mode works fine in any device, but on release mode I've noticed that sometimes a double notification is showed.
It happens just for a device I'm testing: One Plus 8t, Android 14. Oxygen OS KB2003_14.0.0.1311.
When the app is killed or in background a double notification is sent (just in this device, just in release mode, i've tested in debug mode and it works just like any other device), while in any other device I've tried it's not happening.
I've read the documentation talking about not sending both "notification" and "data" in the payload, but it has worked without any problem and if I don't send one of them I've other problems while handling the notification actions or the notification is not showed at all.
NOTICE THAT:
Inside sendLocalNotification there's a if (Platform.isIOS) return; this because in the past I've noticed that iOS was already creating a notification in some cases by itself, and thus the local notification was not needed, if I hadn't add this return a double notification on iOS would have been sent, but on iOS every device had this behavior.
I'm attaching some debug logs to show you what is happening in debug, unfortunately I'm not able to save the same logs on release mode to see what is happening.
# foreground Samsung - OK
[DEBUG] 2026-03-20T10:21:59.236505 [D] TIME: 2026-03-20T10:21:59.235309 onMessage
[DEBUG] 2026-03-20T10:21:59.254128 [D] TIME: 2026-03-20T10:21:59.253149 {senderId: null, category: null, collapseKey: com.sasoftware.palladio.palladio_mobile, contentAvailable: false, data: {prog_dis: DIS038, notification_tag: 1187, body_local_notification: a, title_local_notification: Messaggio da magazzino, rif: 85}, from: 473524389672, messageId: 0:1773998518625541%bb1efa7fbb1efa7f, messageType: null, mutableContent: false, notification: {title: Messaggio da magazzino, titleLocArgs: [], titleLocKey: null, body: a, bodyLocArgs: [], bodyLocKey: null, android: {channelId: null, clickAction: null, color: null, count: null, imageUrl: null, link: null, priority: 0, smallIcon: null, sound: default, ticker: null, tag: 1187, visibility: 0}, apple: null, web: null}, sentTime: 1773998518618, threadId: null, ttl: 2419200}
[DEBUG] 2026-03-20T10:21:59.267411 [D] TIME: 2026-03-20T10:21:59.266836 sendLocalNotification
[DEBUG] 2026-03-20T10:21:59.273072 [D] TIME: 2026-03-20T10:21:59.272313 {senderId: null, category: null, collapseKey: com.sasoftware.palladio.palladio_mobile, contentAvailable: false, data: {prog_dis: DIS038, notification_tag: 1187, body_local_notification: a, title_local_notification: Messaggio da magazzino, rif: 85}, from: 473524389672, messageId: 0:1773998518625541%bb1efa7fbb1efa7f, messageType: null, mutableContent: false, notification: {title: Messaggio da magazzino, titleLocArgs: [], titleLocKey: null, body: a, bodyLocArgs: [], bodyLocKey: null, android: {channelId: null, clickAction: null, color: null, count: null, imageUrl: null, link: null, priority: 0, smallIcon: null, sound: default, ticker: null, tag: 1187, visibility: 0}, apple: null, web: null}, sentTime: 1773998518618, threadId: null, ttl: 2419200}
# background - Samsung - OK
[DEBUG] 2026-03-20T10:24:35.258697 [D] TIME: 2026-03-20T10:24:35.241310 BACKGROUND: {senderId: null, category: null, collapseKey: com.sasoftware.palladio.palladio_mobile, contentAvailable: false, data: {prog_dis: DIS038, notification_tag: 1188, body_local_notification: e, title_local_notification: Messaggio da magazzino, rif: 85}, from: 473524389672, messageId: 0:1773998674718397%bb1efa7fbb1efa7f, messageType: null, mutableContent: false, notification: {title: Messaggio da magazzino, titleLocArgs: [], titleLocKey: null, body: e, bodyLocArgs: [], bodyLocKey: null, android: {channelId: null, clickAction: null, color: null, count: null, imageUrl: null, link: null, priority: 0, smallIcon: null, sound: default, ticker: null, tag: 1188, visibility: 0}, apple: null, web: null}, sentTime: 1773998674712, threadId: null, ttl: 2419200}
# killed - Samsung - OK
[DEBUG] 2026-03-20T10:25:43.092453 [D] TIME: 2026-03-20T10:25:43.040567 BACKGROUND: {senderId: null, category: null, collapseKey: com.sasoftware.palladio.palladio_mobile, contentAvailable: false, data: {prog_dis: DIS038, notification_tag: 1189, body_local_notification: f, title_local_notification: Messaggio da magazzino, rif: 85}, from: 473524389672, messageId: 0:1773998733488763%bb1efa7fbb1efa7f, messageType: null, mutableContent: false, notification: {title: Messaggio da magazzino, titleLocArgs: [], titleLocKey: null, body: f, bodyLocArgs: [], bodyLocKey: null, android: {channelId: null, clickAction: null, color: null, count: null, imageUrl: null, link: null, priority: 0, smallIcon: null, sound: default, ticker: null, tag: 1189, visibility: 0}, apple: null, web: null}, sentTime: 1773998733482, threadId: null, ttl: 2419200}
Any help would really be appreciated.
Originally posted by @matteoberla in #18127