Home > OS >  Flutter FCM 9 on iOS 14
Flutter FCM 9 on iOS 14

Time:10-11

How to implement FCM 9 to work correctly on IOS versions 14 ?

CodePudding user response:

My previous answer about enter image description here

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.

  • Related