Ionic capacitor push-notifications duplicate tokens - ionic-framework

I have an Ionic 5 angular app with push notifications, using the #capacitor/push-notifications plugin. Set up correctly as described here, and running on iOS.
PushNotificationService:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Capacitor } from '#capacitor/core';
import {
ActionPerformed,
PushNotificationSchema,
PushNotifications,
Token,
} from '#capacitor/push-notifications';
import TXTS from '../../assets/strings.json';
import CFG from '../../app-config.json';
import { AppError } from './../error-handlers/app-errors';
#Injectable({ providedIn: 'root' })
export class PushNotificationService {
private registerationToken: Token;
constructor(
private http: HttpClient
) {}
/** initialize push plugin and add event listers to push notification events
*/
init() {
if (!this.isPushAvailable())
return;
// On success, we should be able to receive notifications
PushNotifications.addListener('registration', (token: Token) => {
console.log('Push registration success, token: ' + token.value);
this.registerationToken = token;
this.updateUserRegisterationToken();
});
PushNotifications.addListener('registrationError', (error: any) => {
console.log('Error on registration: ' + JSON.stringify(error));
});
PushNotifications.addListener('pushNotificationReceived',
(notification: PushNotificationSchema) => {
// Show us the notification payload if the app is open on our device
console.log('Push received: ' + JSON.stringify(notification));
}
);
// Method called when tapping on a notification
PushNotifications.addListener('pushNotificationActionPerformed',
(notification: ActionPerformed) => {
console.log('Push action performed: ' + JSON.stringify(notification));
}
);
}
/** Request permission to use push notifications
*/
requestPermissions() {
if (!this.isPushAvailable())
return;
PushNotifications.requestPermissions().then((result) => {
if (result.receive === 'granted') {
// Register with Apple / Google to receive push via APNS/FCM
PushNotifications.register();
} else {
throw new AppError(TXTS.userWarnings.noPushPermissions);
}
});
}
private isPushAvailable() {
return Capacitor.isPluginAvailable('PushNotifications');
}
private updateUserRegisterationToken() {
this.http
.post(
CFG.apiEndpoints.userPushRegistrationToken,
{ token: this.registerationToken.value }
)
.subscribe(
() => this.handleSuccessfulRegistrationTokenUpdate(),
(error) => this.handleFailedRegistrationTokenUpdate(error)
);
}
private handleSuccessfulRegistrationTokenUpdate() {
console.log('Registration token was updated on server');
}
private handleFailedRegistrationTokenUpdate(error) {
throw new Error('Error updating push registration token on server. Error: ' + error); // Not AppError! Not to show error to the user.
}
}
The push service is initialized by the app by calling:
this.pushService.requestPermissions();
this.pushService.init();
The problem:
Push Notifications 'registration' event is fired twice, each with a different token:
// from xCode logs:
Push registration success, token: 2014C60FE7FB063F2ED833E7B4DF3D0220EE31EEF3350E56C3C0CE3006BD62BE
Push registration success, token: dx7g0rZPZUBCkxFFpgPaQr:APA91bF6xCk9oZje7NNrAu1-_OB1y2Kek9RxOQMGVrxahqgCDh8YZv5W_aekKgJJtFAQqA0e__K-qqtB5c9T28PFc542R7MuYQYuKkztOet3gWpPU-zF3lUsLLeXwU45On7KDpEuG5I1
I understand that the first token is an APN token, and the second is a firebase (FCM) token. My project needs only the firebase token, and the APN token causes confusion.
My AppDeligate.swift looks like his:
import UIKit
import Firebase
import Capacitor
import Firebase
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken)
Messaging.messaging().apnsToken = deviceToken
Messaging.messaging().token(completion: { (token, error) in
if let error = error {
NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
} else if let token = token {
NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: token)
}
})
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
// Called when the app was launched with a url. Feel free to add additional processing here,
// but if you want the App API to support tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Called when the app was launched with an activity, including Universal Links.
// Feel free to add additional processing here, but if you want the App API to support
// tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let statusBarRect = UIApplication.shared.statusBarFrame
guard let touchPoint = event?.allTouches?.first?.location(in: self.window) else { return }
if statusBarRect.contains(touchPoint) {
NotificationCenter.default.post(name: .capacitorStatusBarTapped, object: nil)
}
}
}
Two questions:
Gow to prevent those duplicate 'registration' events without using another
FCM plugin as described here?
How to diffrentiate APN from the FCM token in an elgant way?

You have two NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications lines, that's what sends the push token to the JS side.
Remove the NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken) one as that's the one sending the APNS token.

Related

Google Sign In Button error when trying to login

I have followed all the steps of the Firebase instructions for adding a Google Sign In Button. I have two problems when I open the app I have to wait a couple of minutes before pressing the Google button otherwise it gives me an error, after the first sign in if I try more still gives me the error.
(I added all the pods, all the app delegate function requested, the url in the target section of the app, the google.key-value list, and tried all possible solutions from other similar questions)
The errors I get are
Optional(Error Domain=com.google.GIDSignIn Code=-5 "The user canceled the sign-in flow." UserInfo={NSLocalizedDescription=The user canceled the sign-in flow.})
Please if someone could help, cause I tried all stack overflow and google possibilities, and still not working.
Here is snippet of my code:
import Firebase
import GoogleSignIn
class ViewController: UIViewController {
var googleButton : GIDSignInButton!
viewDidLoad(){
self.googleButton = GIDSignInButton()
self.googleButton.style = .iconOnly
self.googleButton.frame = CGRect(x: 120, y: 300, width: 75, height: 75)
self.googleButton.addTarget(self, action: #selector(configureGoogleButton), for: .allTouchEvents)
self.view.addSubview(self.googleButton)
}
function configureGoogleButton(){
//get client Id
guard let clientID = FirebaseApp.app()?.options.clientID else {return}
let config = GIDConfiguration(clientID: clientID)
GIDSignIn.sharedInstance.signIn(with: config, presenting: self)
{ user,error in
guard error == nil else {
print("The app is not working")
print(error)
return }
guard
let authentication = user?.authentication,
let idToken = authentication.idToken
else {
return
}
let credential = GoogleAuthProvider.credential(withIDToken: idToken,
accessToken: authentication.accessToken)
self.googleCredential = credential
print(user?.profile?.email)
print(user?.profile?.name)
}
Here is part of the app delegate:
import Firebase
import UIKit
import GoogleSignIn
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
func application(
_ app: UIApplication,
open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]
) -> Bool {
var handled: Bool
handled = GIDSignIn.sharedInstance.handle(url)
if handled {
return true
}
// Handle other custom URL types.
// If not handled by this app, return false.
return false
}
}

AWS Cognito API `AWSMobileClient.default().addUserStateListener` only fires when user authentication state changes

I am using xcode 11 with swift 4. I am following the docs for aws cognito here https://aws-amplify.github.io/docs/ios/authentication and in AppDelegate.swift, I have:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// initialize AWS amplify
let apiPlugin = AWSAPIPlugin(modelRegistration: AmplifyModels())
do {
try Amplify.add(plugin: apiPlugin)
try Amplify.configure()
} catch {
// Navigate to page showing bad network connection
print("Failed to configure Amplify \(error)")
}
AWSMobileClient.default().addUserStateListener(self) { (userState, info) in
switch (userState) {
case .signedOut:
// user clicked signout button and signedout
print("AppDelegate.application state: user signed out")
case .signedIn:
print("AppDelegate.application state: user signed in: \(userState)")
case .signedOutUserPoolsTokenInvalid:
print("need to login again.")
default:
print("unsupported")
}
}
return true
}
The AWSMobileClient.default().addUserStateListener only fires when the user is logging in or signing up for the first time. if the user is already authenticated. then addUserStateListener does not fire. This is an issue because I need to get the user's account token to access his/her information. Am I using the wrong Cognito API? Or is addUserStateListener used in the wrong function.
If I use the other Cognito API
AWSMobileClient.default().initialize { (userState, error) ... }
It fires each time, but I cannot access the user token from this api.
You can access the user token by using the AWSAppSync api and this method in you app delegate either by extending appDelegate or using your own custom class as I did.
class CognitoPoolProvider : AWSCognitoUserPoolsAuthProviderAsync {
func getLatestAuthToken(_ callback: #escaping (String?, Error?) -> Void) {
AWSMobileClient.default().getTokens { (token, error) in
if let error = error {
callback(nil,error)
}
callback(token?.accessToken?.tokenString, error)
}
}
}
So, in my appDelegate I did something like this
var appSyncClientBridge : AWSAppSyncClient?
/// Sets the configuration settings for application's AWSAppSync Client.
func appSyncSetup()throws -> AWSAppSyncClientConfiguration{
let cache = try AWSAppSyncCacheConfiguration()
let serviceConfig = try AWSAppSyncServiceConfig()
let appSyncConfiguration = try AWSAppSyncClientConfiguration(appSyncServiceConfig: serviceConfig,
userPoolsAuthProvider: CognitoPoolProvider(),
cacheConfiguration: cache)
return appSyncConfiguration
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
do {
let configFile = try appSyncSetup()
appSyncClientBridge = try AWSAppSyncClient(appSyncConfig: configFile)
}
catch(let error){
print(error.localizedDescription)
}
return true
}
UPDATE:
If the user is already logged in. You can create a method like this one.
func getTokens(){
let getPool = CognitoPoolProvider()
func getToken(completion: #escaping (Result<String,Error>)->Void){
getPool.getLatestAuthToken { (token, error) in
if let error = error {
completion(.failure(error))
}
if let token = token {
completion(.success(token))
}
}
}
getToken { (result) in
print(("This is the token — \(String(describing: try? result.get() as String))"))
}
}
If you place this in your viewDidLoad you'll be able to access the user token. In this example it's just printed out, but I think from here you'll be able to use it for what you need.

FCM: Message sent but not received

tried to solve this myself. spent like an hour or so still no result.
Got me old code of a previous project regarding FCM. however the code was and still working on it's app. though i managed to transfer the code to my new project. but it won't work here.
Now i know APN's are weird and complicated. but it's more of a memorized situation for me.
Things i have done:
- Uploaded my personal .p12 to my firebase project
- Enabled "Push Notifications" in app capabilities
- Imported and used UserNotifications framework on appdelegate.swift
Here's how my AppDelegate look like:
import UIKit
import Firebase
import FirebaseFirestore
import StoreKit
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {
var window: UIWindow?
override init() {
super.init()
FirebaseApp.configure()
// not really needed unless you really need it FIRDatabase.database().persistenceEnabled = true
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
Auth.auth().signInAnonymously() { (authResult, error) in
// ...
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
}
Messaging.messaging().delegate = self
UIApplication.shared.statusBarStyle = .default
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = mainStoryboard.instantiateViewController(withIdentifier: "gateway") as! gatewayViewController
window!.rootViewController = viewController
return true
}
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
let dataDict:[String: String] = ["token": fcmToken]
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
// TODO: If necessary send token to application server.
// Note: This callback is fired at each app startup and whenever a new token is generated.
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
// Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
// if let messageID = userInfo[gcmMessageIDKey] {
// print("Message ID: \(messageID)")
// }
// Print full message.
print(userInfo)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
// Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
//if let messageID = userInfo[gcmMessageIDKey] {
// print("Message ID: \(messageID)")
//}//
// Print full message.
print(userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
Okay so with this code you get the devices Firebase registration token, i copied my code and used it on Cloud Functions to send a test message, here's how my CF looks like:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.helloWorld = functions.https.onRequest((request, response) => {
var registrationToken = 'f8fWx_sANVM:APA91bEd46drxiBvHLZd5YKVClQr91oubzJKOyXE1LNgxOsi3ihUw31yEJL6prHKm-A83B1N1sr2GOff3P9tUsRNhCpG7_VMRlDUDfthIcwkDUgzKPV5NZtlo6pcpxsvD9ZgYlPqibNp';
var payload = {
notification: {
title: "just published new Word",
body: "Hii",
}
};
// registration token.
admin.messaging().sendToDevice(registrationToken, payload)
.then(function(response) {
// See the MessagingDevicesResponse reference documentation for
// the contents of response.
return console.log("Successfully sent message:", response);
})
.catch(function(error) {
console.log("Error sending message:", error);
});
});
Okay so far after hitting the helloWorld url, my console gets this:
Successfully sent message: { results: [ { messageId: '0:1537714204565821%b3b8835bb3b8835b' } ],
canonicalRegistrationTokenCount: 0,
failureCount: 0,
successCount: 1,
multicastId: 8154206809408282000 }
Function execution took 60002 ms, finished with status: 'timeout'
Last time on my previous project it took 20ms at it's very best. I still can't figure this out. Your help is greatly appreciated
You're using a HTTP(S) triggered Cloud Function, which means your code must send a response. Since your code doesn't do that, the function runs for 60s and then gets terminated by the Cloud Functions environment. This means you pay for more time than you actually need, so you'll want to fix it.
For example:
// registration token.
admin.messaging().sendToDevice(registrationToken, payload)
.then(function(response) {
// See the MessagingDevicesResponse reference documentation for
// the contents of response.
//return console.log("Successfully sent message:", response);
res.status(200).send(response);
})
.catch(function(error) {
console.log("Error sending message:", error);
});
This will get rid of the message in the Cloud Functions logs, and ensures you only pay for the time you actually need.

Swift 3 / Firebase 4: Notifications from Firebase show alerts, but notifications from my server only show up in console

In a Swift 3 app, implementing Firebase 4 for notifications and a WKWebView for displaying an Angular web app I've come to a point where I can receive app notifications from Firebase and get an alert in the Notification Centre.
But when attempting to get app notifications from my server I only end up having them displayed in the console when running the app, but the phone does not display notifications at all.
I'm suspecting an issue with the way the messages from my server are formatted, below is the console output from a successful alert from Firebase:
[AnyHashable("google.c.a.e"): 1, AnyHashable("google.c.a.ts"): 1504280715, AnyHashable("google.c.a.udt"): 0, AnyHashable("gcm.n.e"): 1, AnyHashable("aps"): {
alert = "This one works";
}, AnyHashable("google.c.a.c_id"): 7239776096663233136,
AnyHashable("gcm.message_id"): 0:1504280715823529%dc6002bbdc6002bb]
And here is a sample from my own app server, which does not display as a phone notification:
[AnyHashable("from"): 1001513747966, AnyHashable("body"): Urgent action
is needed to prevent your account from being disabled!,
AnyHashable("title"): Urgent action needed!]
Message ID: 0:1504280715823529%dc6002bbdc6002bb
I'm testing this on ios10 devices, only.
My AppDelegate.swift file looks like this:
import UIKit
import CoreData
import Fabric
import Crashlytics
import Firebase
import FirebaseMessaging
import UserNotifications
import FirebaseInstanceID
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let gcmMessageIDKey = "gcm.message_id"
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
// [START set_messaging_delegate]
Messaging.messaging().delegate = self as MessagingDelegate
// [END set_messaging_delegate]
// Register for remote notifications. This shows a permission dialog on first run, to
// show the dialog at a more appropriate time move this registration accordingly.
// [START register_for_notifications]
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
Messaging.messaging().shouldEstablishDirectChannel = true
application.registerForRemoteNotifications()
// [END register_for_notifications]
Fabric.with([Crashlytics.self])
return true
}
// [START receive_message]
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
// [END receive_message]
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Unable to register for remote notifications: \(error.localizedDescription)")
}
// This function is added here only for debugging purposes, and can be removed if swizzling is enabled.
// If swizzling is disabled then this function must be implemented so that the APNs token can be paired to
// the InstanceID token.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("APNs token retrieved: \(deviceToken)")
// With swizzling disabled you must set the APNs token here.
Messaging.messaging()
.setAPNSToken(deviceToken, type: MessagingAPNSTokenType.unknown)
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
Messaging.messaging().shouldEstablishDirectChannel = true
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
//connectToFirebaseMessaging()
UIApplication.shared.applicationIconBadgeNumber = 0
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
self.saveContext()
}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "ViDent")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
// [START ios_10_message_handling]
#available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
// Receive displayed notifications for iOS 10 devices.
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
// With swizzling disabled you must let Messaging know about the message, for Analytics
Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
// Change this to your preferred presentation option
completionHandler([])
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
completionHandler()
}
}
// [END ios_10_message_handling]
extension AppDelegate : MessagingDelegate {
// [START refresh_token]
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
}
// [END refresh_token]
// [START ios_10_data_message]
// Receive data messages on iOS 10+ directly from FCM (bypassing APNs) when the app is in the foreground.
// To enable direct data messages, you can set Messaging.messaging().shouldEstablishDirectChannel to true.
func messaging(_ messaging: Messaging, didReceive remoteMessage: MessagingRemoteMessage) {
print("Received data message: \(remoteMessage.appData)")
}
// [END ios_10_data_message]
And it is the print("Received data message: (remoteMessage.appData) at the very end of the AppDelegate.swift file which triggers and prints the message to the console, so I just need to work out how to pass these messages on so an alert is triggered on the device.

Receiving Firebase Notifications in Foreground but not Background

I've tried everything to get the alerts to pop up while in the background. I receive the data when app is open or while launching. Because I receive the notifications I'm assuming it's in my AppDelegate code or perhaps something wrong in my .plist??? I've followed a few of the standard tutorials on firebase notifications, all this code is from those tutorials.
import UIKit
import CoreData
import Firebase
import UserNotifications
import FirebaseInstanceID
import FirebaseMessaging
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
// For iOS 10 data message (sent via FCM
Messaging.messaging().delegate = self
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
// Override point for customization after application launch.
NotificationCenter.default.addObserver(self, selector: #selector(self.tokenRefreshNotification(_:)),
name: NSNotification.Name.InstanceIDTokenRefresh, object: nil)
return true
}
func application(application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String) {
print("refreshed token")
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
// Messaging.messaging().disconnect()
// print("Disconnected from FCM.")
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
connectToFcm()
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
self.saveContext()
}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "firetail")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
#objc private func tokenRefreshNotification(_ notification: Notification) {
if let refreshedToken = InstanceID.instanceID().token() {
print("InstanceID token: \(refreshedToken)")
}
// Connect to FCM since connection may have failed when attempted before having a token.
connectToFcm()
}
func connectToFcm() {
// Won't connect since there is no token
guard InstanceID.instanceID().token() != nil else {
return
}
// Disconnect previous FCM connection if it exists.
Messaging.messaging().disconnect()
Messaging.messaging().connect { (error) in
if error != nil {
print("Unable to connect with FCM. \(error?.localizedDescription ?? "")")
} else {
print("Connected to FCM.")
}
}
}
public func application(received remoteMessage: MessagingRemoteMessage) {
print(remoteMessage.appData)
}
}
A few things to note:
The simulator cannot receive push notifications.
You must have push notifications and background modes (remote notifications & background fetch) enabled in your project's capabilities.
Try adding these lines of code to your app delegate:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
// Will not be called until you open your application from the remote notification (returns to foreground)
// Note: *with swizzling disabled you must let Messaging know about the message
// Messaging.messaging().appDidReceiveMessage(userInfo)`
// Print message ID.
if let messageId = userInfo["gcm.message_id"] {
print("Message Id: \(messageId)")
}
// Print full message.
print(userInfo)
Messaging.messaging().appDidReceiveMessage(userInfo)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// Will not be called until you open your application from the remote notification (returns to foreground)
// Note: *with swizzling disabled you must let Messaging know about the message
// Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message id
if let messageId = userInfo["gcm.message_id"] {
print("Message Id: \(messageId)")
}
// Print full message.
print(userInfo)
Messaging.messaging().appDidReceiveMessage(userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
I also noticed you map your APNS token to Messaging (in didRegisterForRemoteNotificationsWithDeviceToken) using :
Messaging.messaging().apnsToken = deviceToken
I would try your luck replacing it with the following:
Messaging.messaging().setAPNSToken(deviceToken, type: MessagingAPNSTokenType.sandbox)
Messaging.messaging().setAPNSToken(deviceToken, type: MessagingAPNSTokenType.prod)
Good luck!
Be sure to send your messages through Firebase with the high priority setting instead of the standard/default priority.