问题描述
前言:
我的应用是基于 Flutter 的 - 但需要实现本机代码才能使 FCM 消息正常工作,请参阅下文了解更多详情
My App is Flutter based - but native code implementation is required to get FCM messages working, see below for more details
GitHub 问题 #154供参考.
我无法在 iOS 上获取 FCM 通知,特别是在我发布到 Testflight 的应用上.我已经被这个问题困住了一个星期,完全不知道如何继续.
I'm having immense trouble getting FCM notifications working on iOS, specifically on my app published to Testflight. I have been stuck on this problem for a week and have absolutely no idea how to proceed.
问题
使用 Xcode/Android Studio 在我的设备上使用调试/发布版本在本地运行时,会在后台、前台等接收通知.将完全相同的应用程序上传到 Testflight 时,而不是单个通知将通过 FCM 发送.
When running locally using debug/release builds on my devices using Xcode/Android Studio, notifications are received in the background, foreground, etc. When uploading the exact same app to Testflight, not a single notification will come through via FCM.
这很重要,因为 FCM 提供 VoIP 通知,但在 Testflight 上没有收到这些通知,这非常令人沮丧
问题&解决方案?
我发现了 2 个问题(此处 & 这里),两者似乎都表明这是一个 APNS 证书问题(APNS -> Firebase).我重新创建了我的证书并将其添加到 Firebase 控制台(对所有证书生成操作使用相同的 .csr
文件)
There are 2 questions I found (here & here), both seemed to indicate it is a APNS certificate problem (APNS -> Firebase). I have recreated my certificate and added it to the Firebase console (using the same .csr
file for all certificate generating operations)
设置/配置:
APNS密钥生成&添加到 Firebase
APNSKey generated & added to Firebase
能力:
尝试过:
<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)
Swift Code: (targeting >=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);
}
测试:
测试是在 2 部实体 iPhone(6 和 8)上完成的.在使用(调试和发布)模式直接从 Mac(Android Studio 和 XCode)构建时,两者都与 Firebase FCM 一起使用.从 TestFlight 下载相同的文件时,两者都不起作用.
Testing is done on 2 physical iPhones (6s & 8). Both work with Firebase FCM when building directly from Mac (Android Studio & XCode) using (debug & release) modes. Neither works when downloading the same from TestFlight.
如果有人能提供有关错误配置、设置错误或缺少/不正确的 Swift 代码,或者仅仅是错误或遗漏的见解,我们将不胜感激.
If any can provide insight into a misconfiguration, an error in setup or missing/incorrect Swift code, or simply a mistake or omission, it would be much appreciated.
推荐答案
前言:是我的问题.
TL;DR仅将 CubeEnvironment
的 1 个引用更改为 PRODUCTION
.
TL;DRchanged only 1 reference of CubeEnvironment
to PRODUCTION
.
有多个位置可以更改CubeEnvironment
:
使用建议,最好将其添加到CallManagerService"的init()
方法中:
Suggestion to use, even better to add this in your "CallManagerService"'s init()
method:
bool isProduction = bool.fromEnvironment('dart.vm.product');
parameters.environment = isProduction ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT;
调试(过程):调试过程(对 Swift 和 XCode 有点陌生)本来可以更好.我考虑了各种配置文件、aps-environment
设置等.
Debugging (process):The debugging process (being somewhat unfamiliar with Swift & XCode) could have been better. I considered various provisioning profiles, aps-environment
settings, etc.
由于该问题仅发生在 Testflight,它使调试更具挑战性和耗时,因为上传调试版本有其自身的一系列问题
Since the issue only occurred on Testflight, it made debugging alot more challenging and time consuming as uploading a debug build had its own set of issues
最后我添加了一堆日志,其中最重要的是 CB-SDK 调试入口(收到通知时):
Finally I added a bunch of logging, the one that was crucial was the CB-SDK debug entry (when a notification is received):
[
{
"subscription": {
"id": sub id,
"_id": "insert sub id",
"user_id": cube_user_id,
"bundle_identifier": "insert bundle id",
"client_identification_sequence": "insert client id",
"notification_channel_id": 6,
"udid": "insert-uuid",
"platform_id": 1,
"environment": "development",
"notification_channel": {
"name": "apns_voip"
},
"device": {
"udid": "insert-uuid",
"platform": {
"name": "ios"
}
}
}
}
]
特别是以下条目.
environment": "development
这是因为 APS 使用了 2 个不同的推送通知环境,每个环境都有自己的证书(证书被分配给推送通知可以来自的唯一 URL).这个,aps-environment
设置为 'production
(在开始上传之前查看上传存档屏幕)但我正在接收 development
环境通知 - 需要修复.
This is due to APS used 2 different push notification environments, each with its own certificates (certificate is assigned to unique URL's where push notifications can come from). This, aps-environment
is set to 'production
(see on upload Archive screen right before you start uploading) but I'm receiving development
environment notifications - that needed fixing.
查看我的代码,我终于找到了问题(并修复了上面提到的问题).
Reviewing my code, I finally found the issue (and fix mentioned above).
这篇关于Flutter - firebase FCM 消息根本不适用于 Testflight 发布版本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!