Flutter - firebase FCM 消息根本不适用于 Testflight 版本构建

2024-04-22

Preface:

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

GitHub问题 #154 https://github.com/ConnectyCube/connectycube-flutter-samples/issues/154以供参考。


我在 iOS 上获取 FCM 通知时遇到了巨大的困难,特别是在我发布到 Testflight 的应用程序上。我已经被这个问题困扰了一个星期,完全不知道如何继续。

Problem

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

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

问题与解决方案?

我发现了 2 个问题(here https://stackoverflow.com/questions/39550615/firebase-notification-not-working-on-testflight & here https://stackoverflow.com/questions/41140631/firebase-push-notifications-not-working-on-testflight-adhoc-release),两者似乎都表明这是 APNS 证书问题(APNS -> Firebase)。我已经重新创建了我的证书并将其添加到 Firebase 控制台(使用相同的.csr所有证书生成操作的文件)

设置/配置:

  • APNS生成密钥并添加到 Firebase

  • 能力:

尝试过:

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

with:

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

教程/来源:

  • 设置 FCM 和 APNS https://firebase.flutter.dev/docs/messaging/apple-integration/
  • ConnectyCube P2P 会话源 https://github.com/ConnectyCube/connectycube-flutter-samples/tree/master/p2p_call_sample

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

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

Testing:

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

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


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

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 被视为发布(生产)模式而不是沙箱模式

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Flutter - firebase FCM 消息根本不适用于 Testflight 版本构建 的相关文章

  • 在哪里实现 Swift 协议?

    在 Swift 中实现协议一致性时 我有两个选择 具有相同的最终结果 在类中实现协议 也就是说 在类定义的顶部声明一致性 并将实现放在类体内 或者 在扩展中实现协议 也就是说 完全在类之外编写符合协议的代码 这是一个例子 public cl
  • 如何使用 WKWebView 正确实施身份验证质询?

    我正在构建一个网络浏览器 但在网络方面我真的是新手 我想测试下面的代码示例 但我没有现实生活中的示例可以使用 void webView WKWebView webView didReceiveAuthenticationChallenge
  • UITableViewCell 的 viewDidAppear

    我通常使用viewDidAppear方法在视图完成出现后在视图上执行一些 UI 操作 我在各种情况下使用了此方法 它非常有用 但是 我需要在视图上进行一些 UI 更改UITableViewCell当它完成出现后 SDK中是否有任何可用的方法
  • Cocos2D复杂动画[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在使用 Cocos2D 将我的游戏从 Flash 移植到 iOS 我现在有一个工作版本 我很高兴我
  • 如何获取核心数据中现有实体(表)的列表

    如何获取核心数据中特定模式 托管对象模型 的现有实体 表 列表 我刚刚开始实施核心数据概念并坚持这些要点 就像是 SELECT COUNT FROM information schema tables WHERE table schema
  • Xcode 6 仪器冻结

    在 Xcode 6 Instruments 中分析我的 iOS 8 应用程序将运行该应用程序约 5 秒 然后冻结 此后探查器仍将运行 但应用程序已冻结且无法使用 发生在设备和模拟器上 无论我使用哪个分析器 计时器 泄漏等 从调试器或临时构建
  • 确定是否向 Firebase 实时数据库添加或删除数据

    每当添加新帖子时 我都会尝试将通知推送到 Android 应用程序 但是 只要数据 更改 即即使帖子被删除 我不需要 通知也会到达 我如何设置一个条件 以便 FCM 仅在添加帖子时才发送通知 这是我的 index js 文件 const f
  • 在运行时动态创建核心数据模型

    是否可以在运行时从服务器上的一组实体生成核心数据模型 例如SharePoint 列表或 SQL MySQL Parse 我正在尝试采用动态路线 因为 SharePoint 列表 SQL Parse 中的字段可能会在将来随时添加 这意味着应用
  • 是否可以将 Swifts 自动数值桥接复制到 (U)Int8/16/32/64 类型的 Foundation (NSNumber)?

    Question 是否可以将 Swifts 数值桥接复制到 Foundation sNSNumber参考类型 例如Int32 UInt32 Int64 and UInt64类型 具体来说 复制下面介绍的自动按分配桥接 这种解决方案的预期用法
  • 在 SKScene 上运行 SKTransition 是否会破坏原始 SKScene?

    在 SKScene 上运行 SKTransition 是否会破坏原始 SKScene 例如 SKTransition reveal SKTransition revealWithDirection SKTransitionDirection
  • iOS:如何在不降低 fps 的情况下播放音频?

    我正在使用 Sprite Kit 最好使用 Swift 库 为 iOS 9 开发游戏 目前 我正在使用 Singleton 在其中预加载音频文件 每个文件都连接到一个单独的 AVAudioPlayer 实例 这是一个简短的代码片段来了解这个
  • 类型“QueryFn”中不存在“查询”|角火2

    类型参数 query limitTolast number orderByKey 布尔值 不可分配给 QueryFn 类型的参数 对象文字只能指定已知属性 并且 QueryFn 类型中不存在 query 包 json angularfire
  • Swift 相当于 Objective-C FourCharCode 单引号文字(例如 'TEXT')

    我正在尝试在 Swift 中复制一些 Objective C cocoa 一切都很好 直到我遇到以下情况 Set a new type and creator unsigned long type TEXT unsigned long cr
  • 如何以编程方式设置设备(UI)方向?

    希望屏幕 UI 上的所有内容都能够从横向左向右旋转 反之亦然 我该怎么做呢 这是私人的吗 我知道 BOOL shouldAutorotateToInterfaceOrientation UIInterfaceOrientation inte
  • 使用远程图像创建 MSSticker

    我正在尝试找出使用网络上托管的图像创建 MSStickers 的方法 我可以使用本地图像创建 MSStickers 例如 NSString imagePath NSBundle mainBundle pathForResource imag
  • NSCFData isRessized 崩溃?

    我目前在控制台中收到此崩溃日志 2011 08 23 19 18 40 064 App 1697 707 NSCFData isResizable unrecognized selector sent to instance 0x11f1c
  • 使用 Protobuf-net,我收到有关 List 未知线路类型的异常

    我已经开始将 Unity iOS 游戏转换为使用 Protobuf net 保存状态 看起来一切正常 直到我将此实例变量添加到GameState ProtoMember 10 public List
  • mgwt - 以编程方式改变方向

    是否可以在 gwt mgwt 应用程序中更改强制执行特定的屏幕方向 可以说我希望用户始终以横向模式使用应用程序 这取决于 是作为phonegap应用程序 而不是在浏览器内部 如果您作为 Web 应用程序运行 则不需要t get any co
  • iOS Safari Mobile 禁用上一个和下一个选择输入

    上周五我发现了关于此问题的类似问题 但似乎无法再次找到它 如果有人能指出我正确的方向 那就太好了 本质上我在一个页面上有多个选择菜单 第一个在加载时填充 第二个在第一个选择时填充 够简单的 但是 在 iOS 设备中 当您点击选择元素时 它会
  • 如何在 Swift 中将文件名与文件扩展名分开?

    给定包中文件的名称 我想将该文件加载到我的 Swift 应用程序中 所以我需要使用这个方法 let soundURL NSBundle mainBundle URLForResource fname withExtension ext 无论

随机推荐