How to implement FCM 9+ to work correctly on IOS versions 14+?
-
While we appriciate knowledge, the formally correct way is to write a question yourself and the self-answer it with the information. This way, you are staying inside the guidelines. Otherwise, your question will probably be closed as not a question/unclear. – nvoigt Oct 09 '21 at 08:01
-
The bureaucracy is a great way to stop distributing useful tips. But you are right. Thank you for your advice. – Mayvas Oct 09 '21 at 08:10
-
1Well, look at it this way... if someone likes it, you can get points on the questions *and* answer :) – nvoigt Oct 09 '21 at 08:11
2 Answers
My previous answer about Flutter FCM 7 implementation was helpful, so I decided to write the same instructions for the new FCM 9+ versions and show how to implement smooth messages delivery in our Flutter App in some minutes.
After migrating to null safety and FCM version 9+ (IOS 14+) situation does not look better. We got the same issues but in a new wrapper :).
The instruction described below can help with FCM 9+ implementation & provide some code examples. Maybe these instructions can help someone & prevent wasting time.
XCode Setting
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.
Here is whole process of how to Implement FCM in flutter
First of setup your app on firebase console by follow this link Add Firebase to your Flutter app
Add dependencys
firebase_core: ^1.12.0
firebase_messaging: ^11.2.6
Add configurations to both app side.
Android
Add in your Application class
import io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingBackgroundService;
public class Application extends FlutterApplication implements PluginRegistrantCallback {
// ...
@Override
public void onCreate() {
super.onCreate();
FlutterFirebaseMessagingBackgroundService.setPluginRegistrant(this);
}
@Override
public void registerWith(PluginRegistry registry) {
GeneratedPluginRegistrant.registerWith(registry);
}
// ...
}
FlutterFirebaseMessagingBackgroundService a callback to call your application's onCreate method.
iOS Integration
for iOS folow this document setup iOS or macOS with Firebase Cloud Messaging.
Adding functionality
here is your main.dart file and Replace the entire code with the following:
import 'dart:async';
import 'dart:convert';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:firebase_messaging_example/firebase_config.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'message.dart';
import 'message_list.dart';
import 'permissions.dart';
import 'token_monitor.dart';
/// Define a top-level named handler which background/terminated messages will
/// call.
///
/// To verify things are working, check out the native platform logs.
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// If you're going to use other Firebase services in the background, such as Firestore,
// make sure you call `initializeApp` before using other Firebase services.
await Firebase.initializeApp(options: DefaultFirebaseConfig.platformOptions);
print('Handling a background message ${message.messageId}');
}
/// Create a [AndroidNotificationChannel] for heads up notifications
late AndroidNotificationChannel channel;
/// Initialize the [FlutterLocalNotificationsPlugin] package.
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: const FirebaseOptions(
apiKey: 'AIzaSyAHAsf51D0A407EklG1bs-5wA7EbyfNFg0',
appId: '1:448618578101:ios:0b11ed8263232715ac3efc',
messagingSenderId: '448618578101',
projectId: 'react-native-firebase-testing',
),
);
// Set the background messaging handler early on, as a named top-level function
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
if (!kIsWeb) {
channel = const AndroidNotificationChannel(
'high_importance_channel', // id
'High Importance Notifications', // title
'This channel is used for important notifications.', // description
importance: Importance.high,
);
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
/// Create an Android Notification Channel.
///
/// We use this channel in the `AndroidManifest.xml` file to override the
/// default FCM channel to enable heads up notifications.
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
/// Update the iOS foreground notification presentation options to allow
/// heads up notifications.
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
}
runApp(MessagingExampleApp());
}
/// Entry point for the example application.
class MessagingExampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Messaging Example App',
theme: ThemeData.dark(),
routes: {
'/': (context) => Application(),
'/message': (context) => MessageView(),
},
);
}
}
// Crude counter to make messages unique
int _messageCount = 0;
/// The API endpoint here accepts a raw FCM payload for demonstration purposes.
String constructFCMPayload(String? token) {
_messageCount++;
return jsonEncode({
'token': token,
'data': {
'via': 'FlutterFire Cloud Messaging!!!',
'count': _messageCount.toString(),
},
'notification': {
'title': 'Hello FlutterFire!',
'body': 'This notification (#$_messageCount) was created via FCM!',
},
});
}
/// Renders the example application.
class Application extends StatefulWidget {
@override
State<StatefulWidget> createState() => _Application();
}
class _Application extends State<Application> {
String? _token;
@override
void initState() {
super.initState();
FirebaseMessaging.instance
.getInitialMessage()
.then((RemoteMessage? message) {
if (message != null) {
Navigator.pushNamed(
context,
'/message',
arguments: MessageArguments(message, true),
);
}
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
if (notification != null && android != null && !kIsWeb) {
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channel.description,
// TODO add a proper drawable resource to android, for now using
// one that already exists in example app.
icon: 'launch_background',
),
),
);
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('A new onMessageOpenedApp event was published!');
Navigator.pushNamed(
context,
'/message',
arguments: MessageArguments(message, true),
);
});
}
Future<void> sendPushMessage() async {
if (_token == null) {
print('Unable to send FCM message, no token exists.');
return;
}
try {
await http.post(
Uri.parse('https://api.rnfirebase.io/messaging/send'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: constructFCMPayload(_token),
);
print('FCM request for device sent!');
} catch (e) {
print(e);
}
}
Future<void> onActionSelected(String value) async {
switch (value) {
case 'subscribe':
{
print(
'FlutterFire Messaging Example: Subscribing to topic "fcm_test".',
);
await FirebaseMessaging.instance.subscribeToTopic('fcm_test');
print(
'FlutterFire Messaging Example: Subscribing to topic "fcm_test" successful.',
);
}
break;
case 'unsubscribe':
{
print(
'FlutterFire Messaging Example: Unsubscribing from topic "fcm_test".',
);
await FirebaseMessaging.instance.unsubscribeFromTopic('fcm_test');
print(
'FlutterFire Messaging Example: Unsubscribing from topic "fcm_test" successful.',
);
}
break;
case 'get_apns_token':
{
if (defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.macOS) {
print('FlutterFire Messaging Example: Getting APNs token...');
String? token = await FirebaseMessaging.instance.getAPNSToken();
print('FlutterFire Messaging Example: Got APNs token: $token');
} else {
print(
'FlutterFire Messaging Example: Getting an APNs token is only supported on iOS and macOS platforms.',
);
}
}
break;
default:
break;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Cloud Messaging'),
actions: <Widget>[
PopupMenuButton(
onSelected: onActionSelected,
itemBuilder: (BuildContext context) {
return [
const PopupMenuItem(
value: 'subscribe',
child: Text('Subscribe to topic'),
),
const PopupMenuItem(
value: 'unsubscribe',
child: Text('Unsubscribe to topic'),
),
const PopupMenuItem(
value: 'get_apns_token',
child: Text('Get APNs token (Apple only)'),
),
];
},
),
],
),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
onPressed: sendPushMessage,
backgroundColor: Colors.white,
child: const Icon(Icons.send),
),
),
body: SingleChildScrollView(
child: Column(
children: [
MetaCard('Permissions', Permissions()),
MetaCard(
'FCM Token',
TokenMonitor((token) {
_token = token;
return token == null
? const CircularProgressIndicator()
: Text(token, style: const TextStyle(fontSize: 12));
}),
),
MetaCard('Message Stream', MessageList()),
],
),
),
);
}
}
/// UI Widget for displaying metadata.
class MetaCard extends StatelessWidget {
final String _title;
final Widget _children;
// ignore: public_member_api_docs
MetaCard(this._title, this._children);
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: const EdgeInsets.only(left: 8, right: 8, top: 8),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Container(
margin: const EdgeInsets.only(bottom: 16),
child: Text(_title, style: const TextStyle(fontSize: 18)),
),
_children,
],
),
),
),
);
}
}
Folow this docs: Cloud Messaging
- 37,512
- 17
- 201
- 182