iOS 14+ 上的 Flutter FCM 9+

Posted

技术标签:

【中文标题】iOS 14+ 上的 Flutter FCM 9+【英文标题】:Flutter FCM 9+ on iOS 14+ 【发布时间】:2021-11-28 22:32:15 【问题描述】:

如何实现 FCM 9+ 以在 ios 版本 14+ 上正常工作?

【问题讨论】:

虽然我们学习知识,但形式上正确的方法是自己写一个问题,然后用信息自我回答。这样,您就可以遵守准则。否则,您的问题可能会因为不是问题/不清楚而被关闭。 官僚作风是停止分发有用提示的好方法。但你是对的。谢谢你的建议。 嗯,这样看吧...如果有人喜欢,你可以在问题回答中获得积分:) 【参考方案1】:

我之前关于Flutter FCM 7 implementation 的回答很有帮助,因此我决定为新的 FCM 9+ 版本编写相同的说明,并在几分钟内展示如何在我们的 Flutter 应用中实现平滑的消息传递。

迁移到 null 安全和 FCM 版本 9+ (IOS 14+) 后情况看起来并没有好转。我们遇到了同样的问题,但使用了新的包装器:)。

下面描述的指令可以帮助实现 FCM 9+ 并提供一些代码示例。也许这些说明可以帮助某人并防止浪费时间。

XCode 设置

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/>

消息示例(可调用函数)

您的消息必须使用以下选项发送:


   mutableContent: true,
   contentAvailable: true,
   apnsPushType: "background"

只是在可调用函数中使用的示例

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 消息服务

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 模型

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);
  
  

就是这样。不要忘记在真正的 IOS 设备上进行测试。 FCM 不适用于 IOS 模拟器。

【讨论】:

以上是关于iOS 14+ 上的 Flutter FCM 9+的主要内容,如果未能解决你的问题,请参考以下文章

Flutter FCM iOS 问题 - 在检索 FCM 令牌之前未设置 APNS 设备令牌

锁定屏幕上的 FCM 通知 Flutter

Flutter / FCM 通知未到达 Codemagic iOS 版本

Flutter - 在 IOS 上使用 FCM 的后台通知

Flutter iOS 和 Android 中的 FCM 自定义声音

Flutter 应用程序的 FCM 通知未显示在 iOS 系统托盘中