iOS OneSignal handleNotificationReceived callback not called after app restart - ios

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.

Related

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

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.

Log OneSignal Push Notification with Firebase Analytics Event

So my goal is to log event any push notification that comes up on my app. I'm using Firebase Analytics to log any event and I already know how to use it using :
Analytics.logEvent(FirebaseEventName, parameters: [:])
But is it possible to log event a push notification? I'm using OneSignal as my Push Notification service.
I've found my answer. But my method required Backend to be involved. Which I don't have the answer because I don't handle the Backend part. The following answer is only just for iOS part only.
So, the method is using OneSignal method, and it's implemented in
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
If you're already using OneSignal for iOS you should already write below code in didFinishLaunchingWithOptions
OneSignal.initWithLaunchOptions(launchOptions, appId: ONE_SIGNAL_APPID, handleNotificationReceived: { (notification) in
let additionalData = notification?.payload.additionalData // Get additional data such as custom flaging from backend
if additionalData != nil { // A condition to avoid crash if additionalData is empty
let customFlag:String = additionalData!["tipe"] as! String // Get value of the custom flag, in this case mine is "tipe", and store the value to customFlag
Analytics.logEvent(customFlag, parameters: [:]) // Send Log Event to Firebase
}
}, handleNotificationAction: { (result) in
}, settings: [kOSSettingsKeyInFocusDisplayOption: OSNotificationDisplayType.none.rawValue,kOSSettingsKeyAutoPrompt : true])
For example, if you print additionalData the value should be looking like
[AnyHashable("tipe"): inbox]
And this value is set/declared in Backend part.

CLCircularRegion and wake up app

In application we have mechanism like native Reminder app in iOS with firing notifications when user enter or exit in some region.
But two devices behave differently (5 and 5s) in same time. All devices have enable notifications, and allow use locations.
Two devices have a some "travel" and in the route created 10 points. First device (5) when came to finish received only 6 notifications, (5s) don't receive any notification.
But my question is how I can know when my app is restart in background or continue working. Because, all log in app I redirect into a file, and after download container and analyze what happened in app in travel time.
I noticed app restart in same times when device is enter to region and my log marks fired in the file but notifications don't receive. This is happended when app try to get some information from web service in didFinishLaunchingWithOptions
And maybe this is problem. How to know distinguish restart app or continue working. Thx.
Are you checking UIApplicationLaunchOptionsLocationKey in didFinishLaunchingWithOptions similar to (sorry, Swift is what I have now):
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if launchOptions?[UIApplicationLaunchOptionsLocationKey] != nil {
// app was launched in response to incoming location event
}
}
Additionally, if you're not already doing this you may need to create notifications differently if app is in background:
// Show an alert if application is active
if UIApplication.sharedApplication().applicationState == .Active {
if let message = notefromRegionIdentifier(region.identifier) {
if let viewController = window?.rootViewController {
showSimpleAlertWithTitle(nil, message: message, viewController: viewController)
}
}
}
else {
// Otherwise present a local notification:
let notification = UILocalNotification()
notification.alertBody = notefromRegionIdentifier(region.identifier)
notification.soundName = "Default";
UIApplication.sharedApplication().presentLocalNotificationNow(notification)
}

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)

Handling push notifications payload data

I am searching the way about how to handle push notification payload data as soon as the notification reaches to the client app without opening or tapping it.And I am still not getting the data unless the user tap or open it from notification center or banner or alert.The function didReceiveRemoteNotification only triggered when the user click the notification on the screen.So,how to get the notification payload data when the notification arrive to client app even the user ignore(without open or tap) it.
INFO : I heard that GCM(Google Cloud Messaging) can make notification handler if the client app user tapped the notification or not.It can catch the notification payload json data as soon as it reach the client app without even need user to tap or open it.Is that right?
I really need a hand to pick me up with getting notification payload data on ios without even need a user to open or tap it.
Update : The app is still running on device which mean it was active.I can get the payload data when i click my notification which was "{aps:} json i get it.But,I still cant get the data when i don't open the notification"
Here is my state
When the app was at foreground,I get the data.
1.I run the App,
2.Send Notification,
3.Get the notification with an alert,
4.I get the data(payload).
Work fine when app is active.
But,when the app reach to background
1.Run The app,
2.Close The App by pressing home button,
3.Send Notification,
4.Get the notification.
5.But,cant get the data until i click notification that I was receive at banner
or notification center.
But,when i click the notification at banner or notification it went to app and then i get the data.
Is there anyway that i can get the data if the app in background when the notification received.
Here is the code :
import UIKit
import RealmSwift
let realm = try! Realm()
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var data : [NSObject : AnyObject]!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//one singnal is the push notification service that i use for push notification.
let oneSignal = OneSignal(launchOptions: launchOptions, appId: "__app_id__") { (message, additionalData, isActive) in
NSLog("OneSignal Notification opened:\nMessage: %#", message)
if additionalData != nil {
NSLog("additionalData: %#", additionalData)
}
}
oneSignal.enableInAppAlertNotification(true)
return true
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
print("User Info : \(userInfo)")
if let custom = userInfo["custom"] as? NSDictionary{
if let a = custom["a"] as? NSDictionary{
print("A : \(a)")
}
}
}
I came across the same problem. As mentioned in the previous comments, this post is quite helpful.
According to Apple,
When a remote notification arrives, the system displays the
notification to the user and launches the app in the background (if
needed) so that it can call this method. Launching your app in the
background gives you time to process the notification and download any
data associated with it, minimizing the amount of time that elapses
between the arrival of the notification and displaying that data to
the user.
The first thing you have to do is to allow your app to do something when in background. You do this by adding Required background mode in your info.plist, then add it App downloads content in response to push notifications. Your info.plist should look something like this:
Now this is done, your app should awake when it receive a notification. You can execute a small code inside didReceiveRemoteNotification. Such as this.
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
//do some code here
UIBackgroundFetchResult.NewData
}
Note that you have to pay attention to the completionHandler:
As soon as you finish processing the notification, you must call the
block in the handler parameter or your app will be terminated. Your
app has up to 30 seconds of wall-clock time to process the
notification and call the specified completion handler block.
Let me know if everything is clear :)

Resources