i'm having some trouble figuring out how to achive the result i want with firebase push notification on Flutter.
Basically i'm trying to send a set of push notifications with the device offline, then i'm reactivating the wifi but the device receive only one notification.
I've already read this but i'm not getting how is possible to receive all notification and show them to the user. Basically in the same way as whatsapp does. text
If in the notification message i set notification
(with or without data
) key then only one notification is received once the device is back online.
If in the notification message i set data
(without notification
) the title and body can't be set and thus the notification is not displayed to the user.
This is the code i'm using for handling firebase notification:
import 'dart:convert';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
Future handleBackgroundMessage(RemoteMessage message) async {
print("Title: ${message.notification?.title}");
print("Body: ${message.notification?.body}");
print("Payload: ${message.data}");
}
class FirebaseApi {
final _firebaseMessaging = FirebaseMessaging.instance;
final _androidChannel = const AndroidNotificationChannel(
'high_importance_channel',
'High Importance Notifications',
description: "This channel is used for important notifications",
importance: Importance.defaultImportance,
);
final _localNotification = FlutterLocalNotificationsPlugin();
void handleMessage(RemoteMessage? message) {
if (message == null) return;
print("Title: ${message.notification?.title}");
print("Body: ${message.notification?.body}");
print("Payload: ${message.data}");
//action after the user tapped on the notification
}
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 platform = _localNotification.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
await platform?.createNotificationChannel(_androidChannel);
}
Future initPushNotifications() async {
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: true, badge: true, sound: true);
FirebaseMessaging.instance.getInitialMessage().then(handleMessage);
FirebaseMessaging.onMessageOpenedApp.listen(handleMessage);
FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage);
FirebaseMessaging.onMessage.listen(
(message) {
final notification = message.notification;
_localNotification.show(
notification.hashCode,
notification?.title ?? "Default title",
notification?.body ?? "Default body",
NotificationDetails(
android: AndroidNotificationDetails(
_androidChannel.id, _androidChannel.name,
channelDescription: _androidChannel.description,
icon: "@drawable/ic_launcher"),
),
payload: jsonEncode(
message.toMap(),
),
);
},
);
}
Future<String?> initNotification() async {
await _firebaseMessaging.requestPermission();
String? fcmToken = await _firebaseMessaging.getToken();
//log
print(fcmToken);
//start notifications
initPushNotifications();
initLocalNotifications();
return fcmToken;
}
}
Any help would be very appreciated.
After lots of testing i've found a workaround, idk if it's the best solution but it's working fine.
I've tested it just on Android for the moment.
Basically i'm sending from my php server this payload to the Firebase V1 API:
{
"message":{
"token":"YOUR TOKEN",
"data":{
"title":"Title",
"body":"Body"
},
"android":{
"priority":"high"
},
"apns":{
"headers":{
"apns-priority":"10"
}
}
}
}
By omitting the notification
tag as the firebase documentation says a "silent message is sent" and the notification doesn't appear on the device, so a manual notification is needed, in my code i'm using flutter_local_notification package.
@pragma('vm:entry-point')
Future handleBackgroundMessage(RemoteMessage message) async {
/*print("background");
print(message.toMap());*/
try {
FirebaseApi firebaseApi = FirebaseApi();
await firebaseApi.initAndSendLocalNotification(message);
} catch (e) {
print(e);
}
}
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 {
/*print("handle message");
print(message?.toMap());*/
if (message == null) return;
//YOUR ACTION HERE
}
void handleInitialMessage(RemoteMessage? message) async {
/*print("initial message");
print(message?.toMap());*/
if (message == null) return;
//YOUR ACTION HERE
}
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;
_localNotification.show(
message.hashCode,
notification["title"],
notification["body"],
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(handleBackgroundMessage);
FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage);
FirebaseMessaging.onMessage.listen(
(message) {
//print(message.toMap());
sendLocalNotification(message);
},
);
}
Future<String?> initNotification() async {
final firebaseMessaging = FirebaseMessaging.instance;
await firebaseMessaging.requestPermission();
String? fcmToken = await firebaseMessaging.getToken();
//start notifications
initPushNotifications();
initLocalNotifications();
return fcmToken;
}
}
By doing this, foreground, background, terminated state app notifications are correclty handled, and even if the device receives notifications while it's offline, once it's online ALL notifications will be sent to the device as firebase won't collapse pending notifications.
Hope it will be useful for somebody.