首页 > 解决方案 > Flutter - Firebase FCM 消息根本不适用于 Testflight 发布版本

问题描述

前言:

我的应用程序是基于 Flutter 的 - 但需要实现本机代码才能使 FCM 消息正常工作,请参阅下文了解更多详细信息

GitHub 问题 #154供参考。


我在获取 FCM 通知在 iOS 上工作时遇到了巨大的麻烦,特别是在我发布到 Testflight 的应用程序上。我已经在这个问题上停留了一个星期,完全不知道如何进行。

问题

当使用 Xcode/Android Studio 在我的设备上使用调试/发布版本在本地运行时,会在后台、前台等中收到通知。将完全相同的应用程序上传到 Testflight 时,不会通过 FCM 发送任何通知。

这一点至关重要,因为 FCM 提供 VoIP 通知,这些通知在 Testflight 上没有收到,这非常令人痛苦

问题和解决方案?

我发现了 2 个问题(此处此处),似乎都表明这是一个 APNS 证书问题(APNS -> Firebase)。我重新创建了我的证书并将其添加到 Firebase 控制台(.csr对所有证书生成操作使用相同的文件)

设置/配置:

试过:

<key>FirebaseAppDelegateProxyEnabled</key>
<string>NO</string>

和:

<key>FirebaseAppDelegateProxyEnabled</key>
<string>0</string>

与 :

<key>FirebaseAppDelegateProxyEnabled</key>
<boolean>false</boolean>
    <key>UIBackgroundModes</key>
    <array>
        <string>audio</string>
        <string>bluetooth-central</string>
        <string>external-accessory</string>
        <string>fetch</string>
        <string>location</string>
        <string>processing</string>
        <string>remote-notification</string>
        <string>voip</string>
        <string>remote-notification</string>
    </array>

教程/来源:

Swift 代码:(目标 >=10.0)


import UIKit
import CallKit
import Flutter
import Firebase
import UserNotifications
import GoogleMaps
import PushKit
import flutter_voip_push_notification
import flutter_call_kit

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        
        // run firebase app
        FirebaseApp.configure()
        
        // setup Google Maps
        GMSServices.provideAPIKey("google-maps-api-key")
        
        // register notification delegate
        UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
        
        GeneratedPluginRegistrant.register(with: self)
        
        // register VOIP
        self.voipRegistration()
        
        // register notifications
        application.registerForRemoteNotifications();
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    // Handle updated push credentials
    public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
        // Process the received pushCredentials
        FlutterVoipPushNotificationPlugin.didUpdate(pushCredentials, forType: type.rawValue);
    }
    
    // Handle incoming pushes
    public func pushRegistry(_ registry: PKPushRegistry,
                             didReceiveIncomingPushWith payload: PKPushPayload,
                             for type: PKPushType,
                             completion: @escaping () -> Swift.Void){
        
        FlutterVoipPushNotificationPlugin.didReceiveIncomingPush(with: payload, forType: type.rawValue)
        
        let signalType = payload.dictionaryPayload["signal_type"] as! String
        if(signalType == "endCall" || signalType == "rejectCall"){
            return
        }
        
        let uuid = payload.dictionaryPayload["session_id"] as! String
        let uID = payload.dictionaryPayload["caller_id"] as! Int
        let callerName = payload.dictionaryPayload["caller_name"] as! String
        let isVideo = payload.dictionaryPayload["call_type"] as! Int == 1;
        FlutterCallKitPlugin.reportNewIncomingCall(
            uuid,
            handle: String(uID),
            handleType: "generic",
            hasVideo: isVideo,
            localizedCallerName: callerName,
            fromPushKit: true
        )
        completion()
    }
    
    // Register for VoIP notifications
    func voipRegistration(){
        // Create a push registry object
        let voipRegistry: PKPushRegistry = PKPushRegistry(queue: DispatchQueue.main)
        // Set the registry's delegate to self
        voipRegistry.delegate = self
        // Set the push type to VoIP
        voipRegistry.desiredPushTypes = [PKPushType.voIP]
    }
}

public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    if #available(iOS 14.0, *) {
        completionHandler([ .banner, .alert, .sound, .badge])
    } else {
        completionHandler([.alert, .sound, .badge])
    }
}

func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    print(deviceToken)
    Messaging.messaging().apnsToken = deviceToken;
}

main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  await initializeDateFormatting();
  setupLocator();
  var fcmService = locator<FCMService>();

  FirebaseMessaging.onBackgroundMessage(FCMService.handleFirebaseBackgroundMessage);
  FirebaseMessaging.onMessage.listen((event) {
    print("Foreground message");
    Fluttertoast.showToast(msg: "Received onMessage event");
    FCMService.processCallNotification(event.data);
  });
  FirebaseMessaging.onMessageOpenedApp.listen((event) {
    print("On message opened app");
    Fluttertoast.showToast(msg: "Received onMessageOpenedAppEvent");
    FCMService.handleInitialMessage(event);
  });
  FirebaseMessaging.instance.getInitialMessage().then((value) {
    Fluttertoast.showToast(msg: "Received onLaunch event");
    if (value != null) {
      FCMService.handleInitialMessage(value);
    }
  });

  initConnectyCube();
  runApp(AppProviders());
}

FCMService.dart

  // handle any firebase message
  static Future<void> handleFirebaseBackgroundMessage(RemoteMessage message) async {
    print("Received background message");
    Fluttertoast.showToast(msg: "Received Firebase background message");
    await Firebase.initializeApp();
    setupLocator();
    var fcmService = locator<FCMService>();
    fcmService.init();

    _handleMessage(message, launchMessage: true);
  }

测试:

测试是在 2 部物理 iPhone(6s 和 8)上完成的。在使用(调试和发布)模式直接从 Mac(Android Studio 和 XCode)构建时,两者都可以与 Firebase FCM 一起使用。从 TestFlight 下载相同的内容时两者都不起作用。

如果有任何可以提供对错误配置、设置错误或缺少/不正确的 Swift 代码,或者仅仅是错误或遗漏的见解,我们将不胜感激。

标签: iosswiftfirebasefirebase-cloud-messagingapple-push-notifications

解决方案


即使他们在正确的场景中应用所有内容,这个问题有时也会让人发疯,所以请尝试检查以下内容:

1-在您的苹果开发者帐户中确保您只有一个Apple Push Services Certificate分配给应用标识符(Bundle ID),请避免重复。

2- 如果您使用 APNs 密钥来接收通知,您必须确保在您的应用上传到 TestFlight 或 AppStore 时将其设置为生产模式

func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
    
    print("APNs Device Token: \(token)")
    Messaging.messaging().apnsToken = deviceToken
    Messaging.messaging().setAPNSToken(deviceToken, type: .prod)
    
}

注意:TestFlight 被视为发布(生产)模式而不是沙盒模式


推荐阅读