How to implement FCM 9 to work correctly on IOS versions 14 ?
CodePudding user response:
AppDelegate.swift
import UIKit
import Flutter
import Firebase
import FirebaseMessaging
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
FirebaseApp.configure()
GeneratedPluginRegistrant.register(with: self)
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Info.plist
<key>FirebaseAppDelegateProxyEnabled</key>
<false/>
<key>FirebaseScreenReportingEnabled</key>
<true/>
Message Example (Callable function)
Your message must be sent with these options:
{
mutableContent: true,
contentAvailable: true,
apnsPushType: "background"
}
Just an example to use in callable function
exports.sendNotification = functions.https.onCall(
async (data) => {
console.log(data, "send notification");
var userTokens = [USERTOKEN1,USERTOKEN2,USERTOKEN3];
var payload = {
notification: {
title: '',
body: '',
image: '',
},
data: {
type:'',
},
};
for (const [userToken,userUID] of Object.entries(userTokens)) {
admin.messaging().sendToDevice(userToken, payload, {
mutableContent: true,
contentAvailable: true,
apnsPushType: "background"
});
}
return {code: 100, message: "notifications send successfully"};
});
Flutter Message Service
import 'dart:convert' as convert;
import 'dart:io' show Platform;
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_app_badger/flutter_app_badger.dart';
import 'package:octopoos/entities/notification.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:uuid/uuid.dart';
class MessagingService {
final Box prefs = Hive.box('preferences');
final FirebaseMessaging fcm = FirebaseMessaging.instance;
static final instance = MessagingService._();
bool debug = true;
/// Private Singleton Instance
MessagingService._();
/// Set FCM Presentation Options
Future<void> setPresentationOptions() async {
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
}
/// Check PUSH permissions for IOS
Future<bool> requestPermission({bool withDebug = true}) async {
NotificationSettings settings = await fcm.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
// if (withDebug) debugPrint('[ FCM ] Push: ${settings.authorizationStatus}');
bool authorized = settings.authorizationStatus == AuthorizationStatus.authorized;
return (Platform.isIOS && authorized || Platform.isAndroid) ? true : false;
}
/// Initialize FCM stream service
Future<void> initializeFcm() async {
final String? currentToken = await fcm.getToken();
final String storedToken = prefs.get('fcmToken', defaultValue: '');
/// Refresh Device token & resubscribe topics
if (currentToken != null && currentToken != storedToken) {
prefs.put('fcmToken', currentToken);
/// resubscribeTopics();
}
if (debug) {
debugPrint('[ FCM ] token: $currentToken');
debugPrint('[ FCM ] service initialized');
}
}
/// Store messages to Hive Storage
void store(RemoteMessage message) async {
final FirebaseAuth auth = FirebaseAuth.instance;
final Map options = message.data['options'] != null && message.data['options'].runtimeType == String
? convert.json.decode(message.data['options'])
: message.data['options'];
final AppNotification notificationData = AppNotification(
id: const Uuid().v4(),
title: message.data['title'] ?? '',
body: message.data['body'] ?? '',
image: message.data['image'] ?? '',
type: message.data['type'] ?? 'notification',
options: options,
createdAt: DateTime.now().toString(),
);
late Box storage;
switch (message.data['type']) {
default:
storage = Hive.box('notifications');
break;
}
try {
String id = const Uuid().v4();
storage.put(id, notificationData.toMap());
updateAppBadge(id);
if (debug) debugPrint('Document $id created');
} catch (error) {
if (debug) debugPrint('Something wrong! $error');
}
}
/// Update app badge
Future<void> updateAppBadge(String id) async {
final bool badgeIsAvailable = await FlutterAppBadger.isAppBadgeSupported();
if (badgeIsAvailable && id.isNotEmpty) {
final int count = Hive.box('preferences').get('badgeCount', defaultValue: 0) 1;
Hive.box('preferences').put('badgeCount', count);
FlutterAppBadger.updateBadgeCount(count);
}
}
/// Subscribe topic
Future<void> subscribeTopic({required String name}) async {
await fcm.subscribeToTopic(name);
}
/// Unsubscribe topic
Future<void> unsubscribeTopic({required String name}) async {
await fcm.unsubscribeFromTopic(name);
}
/// Resubscribe to topics
Future<int> resubscribeTopics() async {
final List topics = prefs.get('topics', defaultValue: []);
if (topics.isNotEmpty) {
for (String topic in topics) {
subscribeTopic(name: topic);
}
}
return topics.length;
}
}
AppNotification Model
class AppNotification {
String id;
String title;
String body;
String image;
String type;
Map options;
String createdAt;
AppNotification({
this.id = '',
this.title = '',
this.body = '',
this.image = '',
this.type = 'notification',
this.options = const {},
this.createdAt = '',
});
AppNotification.fromMap(Map snapshot, this.id)
: title = snapshot['title'],
body = snapshot['body'],
image = snapshot['image'],
type = snapshot['type'] ?? 'notification',
options = snapshot['options'] ?? {},
createdAt = (DateTime.parse(snapshot['createdAt'])).toString();
Map<String, dynamic> toMap() => {
"id": id,
"title": title,
"body": body,
"image": image,
"type": type,
"options": options,
"createdAt": createdAt,
};
}
main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:provider/provider.dart';
import 'package:octopoos/services/messaging.dart';
import 'package:timezone/data/latest.dart' as tz;
Future<void> fcm(RemoteMessage message) async {
MessagingService.instance.store(message);
/// Show foreground Push notification
/// !!! Flutter Local Notification Plugin REQUIRED
await notificationsPlugin.show(
0,
message.data['title'],
message.data['body'],
NotificationDetails(android: androidChannelSpecifics, iOS: iOSChannelSpecifics),
);
}
Future<void> main() async {
/// Init TimeZone
tz.initializeTimeZones();
/// Init Firebase Core Application
await Firebase.initializeApp();
/// FCM Permissions & Background Handler
MessagingService.instance.setPresentationOptions();
FirebaseMessaging.onBackgroundMessage(fcm);
runApp(
MultiProvider(
providers: kAppProviders,
child: App(),
),
);
}
app.dart
@override
void initState() {
super.initState();
initFcmListeners();
}
Future<void> initFcmListeners() async {
MessagingService.instance.initializeFcm();
FirebaseMessaging.instance.getInitialMessage().then((message) {
if (message != null) _handleMessage(message);
});
FirebaseMessaging.onMessage.listen(_handleMessage);
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
}
void _handleMessage(RemoteMessage message) {
MessagingService.instance.store(message);
}
That's all. Don't forget to test on a real IOS device. FCM will not work on IOS Simulator.