I'm doing a Flutter example app for FirebaseMessaging to test out how server notification would work. So I've followed the guide on FlutterFire and setup everything and it works great on Android.
As far as iOS goes, it seems to work great when the app is backgrounded. When I do send a notification with the app in the foreground, it shows the notification twice. If it includes an image, one notification will include the image and the second won't.
I'm quite lost, I don't understand why it would do that. Whether I use the test notification from the firebase console or my own backend using FirebaseMessaging.send
, the behavior is the same.
Here are snippets of the code:
main.dart
import 'dart:developer';
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:googlenotifpoc/notification_manager.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
name: "...",
options: DefaultFirebaseOptions.currentPlatform,
);
await FirebaseMessaging.instance.setAutoInitEnabled(true);
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'high_importance_channel', // id
'High Importance Notifications', // title
description:
'This channel is used for important notifications.', // description
importance: Importance.max,
);
const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
'high_importance_channel', // id
'High Importance Notifications',
channelDescription: 'This channel is used for important notifications.',
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker',
icon: "ic_white",
playSound: true,
);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true, // Required to display a heads up notification
badge: true,
sound: true,
);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
FirebaseMessaging.onMessage.listen((event) => NotificationManager.showNotification(event, androidDetails),);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
FirebaseMessaging messaging = FirebaseMessaging.instance;
messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: true,
criticalAlert: false,
provisional: false,
sound: true,
);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FutureBuilder<String?>(
future: getFMCToken(),
builder: (BuildContext context, AsyncSnapshot<String?> snapshot) {
if (snapshot.hasData) {
log(snapshot.data ?? "-");
print(snapshot.data ?? "-");
return Text(
"FMC TOKEN: \n ${snapshot.data}",
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
);
} else {
return const Text("Loading your FCM token...");
}
},
),
],
),
),
);
}
Future<String?> getFMCToken() async {
String? apnsToken;
if(Platform.isIOS) {
apnsToken = await FirebaseMessaging.instance.getAPNSToken();
log("Got APNS token: $apnsToken");
print("Got APNS token: $apnsToken");
}
if(Platform.isAndroid || apnsToken != null) {
return FirebaseMessaging.instance.getToken();
}
return Future.value(null);
}
}
NotificationManaging class
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class NotificationManager {
static Future<void> showNotification(RemoteMessage payload, AndroidNotificationDetails androidDetails) async {
const android = AndroidInitializationSettings("ic_white");
const initializationSettingsIOS = DarwinInitializationSettings();
const initialSetting = InitializationSettings(android: android, iOS: initializationSettingsIOS);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
flutterLocalNotificationsPlugin.initialize(initialSetting);
const iOSDetails = DarwinNotificationDetails();
NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidDetails, iOS: iOSDetails);
await flutterLocalNotificationsPlugin.show(0, payload.notification!.title, payload.notification!.body, platformChannelSpecifics);
}
}
ios notificationService.m
//
// NotificationService.m
// ImageNotification
//
#import "NotificationService.h"
#import "FirebaseMessaging.h"
@interface NotificationService ()
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@end
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// Modify the notification content here...
//self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
//self.contentHandler(self.bestAttemptContent);
[[FIRMessaging extensionHelper] populateNotificationContent:self.bestAttemptContent withContentHandler:contentHandler];
}
- (void)serviceExtensionTimeWillExpire {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
self.contentHandler(self.bestAttemptContent);
}
@end
Firebase dependencies:
firebase_messaging: ^14.7.9
firebase_core: ^2.24.2
flutter_local_notifications: ^16.2.0
firebase_analytics: ^10.7.4
I have found a solution workaround. It seems the problem comes from the fact both the firebase plugin and my code try to show the same notification, so I had to separate the iOS and Android cases as to not throw a local notification on iOS:
FirebaseMessaging.onMessage.listen(
(event) {
if (Platform.isIOS) {
log("received event for iOS : $event");
return;
}
if (event.notification == null) return;
NotificationManager.showNotification(event, androidDetails);
},
);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With