Firebase Messaging won't call BackgroundHandler on iOS (11.4.1) - ios

I have been trying for few days now but I can't figure it out. The Firebase Messaging plugin just won't call the function that was set in onBackgroundMessage, not even when the application is in the background (and not terminated). I've tried this in debug and release builds, but nothing.
I'm trying to send the notifications via flutter_local_notifications but I don't think it matters as the callback doesn't even called.
I know for sure that my app is connected to Firebase because it does get notifications in onMessage.
The things I have done:
1) Connecting the app through the Firebase console, adding GoogleService-Info to the app.
2) Uploading to the Firebase console the APNs auth key.
3) Adding this function to my app as a top-level function:
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// Not called in background\terminated state
print("Handling a background message: ${message.messageId}");
...
}
and in the main():
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
4) Adding the following line to the AppDelegate:
import UIKit
import Flutter
import Firebase
import FirebaseMessaging
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
5) Making sure my app has push notifications and background modes enabled (in all builds):
6) Giving the app notifications permissions:
7) Sending the notification as follows (without the notification tag, only the data tag) using the node.js admin SDK:
let message = {
apns: {
headers: {
'apns-priority': '5',
},
payload: {
aps: {
contentAvailable: true
},
},
},
android: {
priority: 'normal',
},
data: {
title: "Test",
message: "Test",
url: "https://www.google.com/"
},
topic: topic
};
Debugging using the Console app on Mac brings the following result:
default 20:48:06.069282+0300 SpringBoard Received incoming message on topic com.matkonit at priority 5
default 20:48:06.082948+0300 SpringBoard [com.matkonit] Received remote notification request 3823-43DB [ waking: 0, hasAlertContent: 0, hasSound: 0 hasBadge: 0 hasContentAvailable: 1 hasMutableContent: 0 pushType: Background]
default 20:48:06.083005+0300 SpringBoard [com.matkonit] Process delivery of push notification 3823-43DB
So the notification does received by the OS and yet the Firebase handler is not called.
I have also tried countless solutions that were suggested on other StackOverlow posts but nothing helped...
Any help would be appreciated.

I have the following firebase version and it works for me in debug mode
so please double check this steps:
Ensure you add background and remote notification capabilities from xcode
Don't use firebase console to test background notification as it won't work
build a small server or use postman to send notification , you can find that in firebase console docs https://firebase.google.com/docs/cloud-messaging/send-message#node.js
or use this repo https://github.com/SHAHIDNX/NODEJS_pushnotifications
Send a silent notification
{
"to": "fcm_token",
"data": {
"clear_data": true
},
"content_available": true,
"apns-priority": 5
};
notitce the different in content available

In the app delegate have you tried checking if launchOptions contains "UIApplicationLaunchOptionsRemoteNotificationKey". Once I was not receiving notifications in my app because it was being attached in the launchOptions so I would have to check and handle them there. Not sure if it will help but its an edge case.
https://developer.apple.com/documentation/uikit/uiapplicationlaunchoptionsremotenotificationkey

You need to look at the initialization of FirebaseMessaging Instance.
https://firebase.flutter.dev/docs/messaging/usage/
Once you received the notification you will get that message in _firebaseMessagingBackgroundHandler see attached screenshot for the sample code.

Related

Notification delegate not being called when app is in background when notification is received

I am using firebase cloud messaging to send push notifications to our iOS application. When the application is in the foreground everything works fine.
The problem is when i send the app to the background and then send a notification.
I expect the following delegate to be called, but it is not:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult)-> Void) {
I have ticked the "remote notifications" background mode in my apps signing & capabilities tab.
The payload i send to firebase looks like this:
{"to":"--TOKEN--",
"priority":"high",
"content_available":true,
"mutable_content": true,
"notification":
{"body":"Test Notification",
"title":"New Notification",
"sound":"default",
"click_action":"notification"
}
}
This payload is sent to firebase via https://fcm.googleapis.com/fcm/send
When i click an notification it is then processed, and i can see the apns version i receive looks like this:
[AnyHashable("google.c.sender.id"): 8xxxxxxxx, AnyHashable("gcm.message_id"): 1xxxxxxxxxx, AnyHashable("google.c.a.e"): 1, AnyHashable("google.c.fid"): fxxxxxxxxx, AnyHashable("aps"): {
alert = {
body = "Test Notification";
title = "New Notification";
};
category = feedback;
"content-available" = 1;
"mutable-content" = 1;
sound = default;
}]
I am not sure why content-available and mutable-content appear in quotes? I know firebase convers its payload to apns format, is there something wrong here?
I want the delegate to be called so that i can execute some code to maintain various data items, so it is important that i can run some code when my app is in the background and a notification is received.
I am not sure what config i am missing as everything i read seems to say this is all i need to do?

Flutter - firebase FCM messages not working on Testflight release builds at all

Preface:
My App is Flutter based - but native code implementation is required to get FCM messages working, see below for more details
GitHub issue #154 for reference.
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.
Problem
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.
This is crucial as FCM delivers VoIP notifications, these aren't being received on Testflight which is extremely distressing
Questions & Solutions?
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)
Setup/Configuration:
APNS
Key generated & added to Firebase
Capabilities:
Tried with:
<key>FirebaseAppDelegateProxyEnabled</key>
<string>NO</string>
with:
<key>FirebaseAppDelegateProxyEnabled</key>
<string>0</string>
and with :
<key>FirebaseAppDelegateProxyEnabled</key>
<boolean>false</boolean>
Background modes:
<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>
Tutorials/sources:
Setup FCM & APNS
ConnectyCube P2P Session source
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);
}
Testing:
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.
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.
This problem sometimes drive people crazy even they apply everything in the correct scenario, so please try to check the following:
1- in your apple developer account make sure that you have only one Apple Push Services Certificate assigned to the app identifier ( Bundle ID ), please avoid duplication.
2- If you are using the APNs key to receive notification you have to make sure its set on the production mode when your app is uploaded to TestFlight or 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)
}
Note: TestFlight considered as a release (Production) mode not as sandbox mode
Preface:
the issue was mine.
TL;DR
changed only 1 reference of CubeEnvironment to PRODUCTION.
There are multiple locations to change CubeEnvironment:
push_notifications_manager.dart#111
push_notifications_manager.dart#111
call_manager.dart#111
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;
Debugging (process):
The debugging process (being somewhat unfamiliar with Swift & XCode) could have been better. I considered various provisioning profiles, aps-environment settings, etc.
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
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"
}
}
}
}
]
specifically, the following entry.
environment": "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).
I tried everything but what worked for me was discovering that App Flyer SDK was preventing my push notifications from coming through. Removing this from pubspec solved it for me. End of a 30 hour debugging journey.

iOS OneSignal handleNotificationReceived callback not called after app restart

When I run my app from Xcode everything works fine. I get the handleNotificationReceived callback is called and I can read the data from the notification and handle it depending on the data. If I click on a notification the handleNotificationAction callback is called and the same here. If I then minimize the app it works the same - callbacks are called and I can handle the notification.
The problems begin when I terminate the app from the iPhone through the recent app menu and start it from the icon on the desktop. If the app is in the foreground everything works fine like when I start the app from Xcode. When I minimize it I still get the notification but the handleNotificationReceived callback doesn't get called anymore. If I click on a notification the app comes into foreground and then handleNotificationAction is called and shortly after that handleNotificationReceived is also called. As long as the app is in foreground it continues working fine and the callbacks get called. As soon as I minimize the app again handleNotificationReceived doesn't get called anymore.
If I attach the debugger everything starts to work fine again.
Why isn't it called? I am receiving some data in the notifications that I have to save in Core Data.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
AppDelegate.configOneSignal(launchOptions)
return true
}
class func configOneSignal(_ launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Void {
let onesignalInitSettings = [kOSSettingsKeyAutoPrompt: false]
OneSignal.initWithLaunchOptions(launchOptions,
appId: "MY_APP_ID",
handleNotificationReceived: { notification in
print("notification received")
AppDelegate.handleNotification(notification)
},
handleNotificationAction: { (result) in
print("notification action")
AppDelegate.handleNotification(result?.notification)
},
settings: onesignalInitSettings)
OneSignal.inFocusDisplayType = OSNotificationDisplayType.notification;
OneSignal.promptForPushNotifications(userResponse: { accepted in
print("User accepted notifications: \(accepted)")
})
}
Xcode version: 10.1
Tested iOS versions: 10, 11, 12
handleNotificationReceived is triggered in background only if you set in the body the following flags (see doc, under section-content-language):
content_available: 1
mutable_content: 1
With the above two flags in the body of the POST, the OS wakes up your application in order to set the state as BACKGROUND. When you application is marked with these state, the handler is triggered.
You can test through Postman.
{ "app_id" : "<your one signal app id>", "included_segments" : ["All"], -> or whatever you want "content_available": "1", "mutable_content": "1", "data": {
"custom_key" : "custom_value" }, "contents": {"en":"notification title"} }
Don't forget to set the headers (Content-Type & Authorization)
Here is an example of usage.

Is it possible to open my iOS app remotely?

I'm pretty sure I already know what the outcome of this attempt is going to be, but before I start going through a lot of effort for nothing, I should probably just ask someone about this. Here's what I want to try:
I'm developing an iOS app in Swift, and I just wrote a PHP script to send a silent push notification to my device. In this silent notification, I'm passing along instructions to have my app delegate open the app using the UIApplication.shared.openURL(url:) method. This PHP script will only be run if a user taps a certain button on an online web page, which can only be accessed when the user is using Safari web browser on his iPhone device with my app already running in the background, so there's no chance that anyone will be able to trigger the PHP script from any other device than an iPhone which already has my app installed and running in the background. If you're wondering why I would use this workaround while I can also just use something as simple as URL schemes or deep linking, well, it's quite simple: first of all, deep linking requires the user to confirm that he wants my app to open before it actually opens. I don't want this, instead, I want my app to open automatically as soon as the button is tapped. Second, after the app is opened through deep linking or universal links, there's this really annoying breadcrumb button in the status bar, which shouldn't be there anymore after my user transitions from the web page to my app. I've tried everything to get rid of the confirmation prompt and breadcrumb button, and this is the last thing I can come up with. Is it possible to trigger the openURL method using a remote notification, even when the app is already open and running in the background?
you can't open / bring to foreground an app without user interaction
You can use silent push notification ( Pushkit ).
It will also work in background and with app terminated mode.
Once you receive silent push notification, you have to schedule local notification with interactive buttons in notification.
As per user tap, your app will be open and with tracking on didFinishLaunchingWithOption you can open specific URL
Note - Without user interaction, you would not be able to open specific URL in safari.
You can refer below code for same.
import UIKit
import PushKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,PKPushRegistryDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let types: UIRemoteNotificationType = [.Alert, .Badge, .Sound]
application.registerForRemoteNotificationTypes(types)
self.PushKitRegistration()
return true
}
//MARK: - PushKitRegistration
func PushKitRegistration()
{
let mainQueue = dispatch_get_main_queue()
// Create a push registry object
if #available(iOS 8.0, *) {
let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
// Set the registry's delegate to self
voipRegistry.delegate = self
// Set the push type to VoIP
voipRegistry.desiredPushTypes = [PKPushTypeVoIP]
} else {
// Fallback on earlier versions
}
}
#available(iOS 8.0, *)
func pushRegistry(registry: PKPushRegistry!, didUpdatePushCredentials credentials: PKPushCredentials!, forType type: String!) {
// Register VoIP push token (a property of PKPushCredentials) with server
let hexString : String = UnsafeBufferPointer<UInt8>(start: UnsafePointer(credentials.token.bytes),
count: credentials.token.length).map { String(format: "%02x", $0) }.joinWithSeparator("")
print(hexString)
}
#available(iOS 8.0, *)
func pushRegistry(registry: PKPushRegistry!, didReceiveIncomingPushWithPayload payload: PKPushPayload!, forType type: String!) {
// Process the received push
}
}
Refer some more material for push kit integration.

How to get notification additionaldata(payloadData) that was at AppDelegate' didFinishLunchingWithOptions if the user didn't open the notification

I am currently using OneSignal for notification service to my app.I really need a help with accessing notification additionaldata(payload data) from AppDelegate inside didFinishLunchingWithOption where OneSignal API can give me like this.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var data : [NSObject : AnyObject]!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let oneSignal = OneSignal(launchOptions: launchOptions, appId: "48755d3d-abc0-4bac-8f71-095729bb3a65") { (message, additionalData, isActive) in
NSLog("OneSignal Notification opened:\nMessage: %#", message)
if additionalData != nil {
NSLog("additionalData: %#", additionalData)
self.data = additionalData
print(self.data)
}
}
oneSignal.enableInAppAlertNotification(true)
return true
}
}
but,I can only get the data if user click notification when appear or open it from notification center.So,if user neglect that notification without tapping when appear or without swiping or tapping from notification center,how do i get the additional data?
Actually,I want to store all payload data every time it comes into my device at realm database and fetch the data from my server according to that payload data.
You should use application(_:didReceiveRemoteNotification:fetchCompletionHandler:).
If you have enabled remote notifications background mode most of your notifications will be delivered even if the app is not running in the foreground. The only caveat being that the app must have been launched (since notifications are being pushed the user has done this) and it mustn't have been force-quit by the user.
More info in Apple's docs about that specific method.
Or in the "Local and Remote Notification Programming Guide's" notification handling chapter
You can extract all the payload in did finishLaunching by following method..
Let data = launchOptions.objectForKey(UIApplicationLaunchOptionsUIApplicationLaunchOptionsRemoteNotificationUIApplicationLaunchOptionsUIApplicationLaunchOptionsRemoteNotificationKey)

Resources