App goes stuck when click on firebase push notification in Killed state - ios

I was handling push notification data and then after call API based in push notification custom data. This will work fine when app is in Active and background state.
But when app is not running and then click on notification, I was able to get custom data from custom date But, API is not called and app getting stuck.
I checked in iOS 10 and 11, but not working
Handling push is like this.
AppDelegate
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
redirectToScreen(notificaiton: userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
//Notify through Notification center
func redirectToScreen(notificaiton: [AnyHashable: Any]) {
let dictPayload = notificaiton as NSDictionary
print(dictPayload)
if let type = dictPayload.value(forKey: "type") as? String {
var dict = ["type" : type]
NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "handlePush"), object: dict)
}
}
HomeViewController
//Notification Observer goes here and call API
let spinner = showLoader(view: self.view) // App goes stuck here and loaded process continuously, response is not getting
Alamofire.request(kURl, method: .post, parameters: param, encoding: URLEncoding.httpBody, headers: nil).authenticate(user: R.string.keys.basicAuthUsername(), password: R.string.keys.basicAuthPassword()).responseSwiftyJSON(completionHandler: {
spinner.dismissLoader()
})

Swift 4.0
As per #TarasChernyshenko statement, I put Post Notification code in DispatchQueue.main.async { } block and now it works fine.
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "handlePush"), object: dict)
}

As mentioned by #TarasChernyshenko,
When you get callback from Notification Observer via didRecieveNotification(_:), app remains in background. Any UI updates such as :
let spinner = showLoader(view: self.view)
Must keep in Main thread queue as given below :
DispatchQueue.main.async {
let spinner = showLoader(view: self.view)
//other ui stuffs...
}

Related

Why can't I read push message data in Swift5?

I have a problem that I cannot read the push message data at this time. I get a message normally.
I can receive both when the app is in Foreground or Background when I press the Home button.
But I can't see the message data in the log.
AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {
...
func application(_ application: UIApplication, didReceiveRemoteNotification data: [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
guard
let aps = data[AnyHashable("notification")] as? NSDictionary,
let alert = aps["alert"] as? NSDictionary,
let body = alert["body"] as? String,
let title = alert["title"] as? String
else {
// handle any error here
return
}
Log.Info("Title: \(title) \nBody:\(body)")
Messaging.messaging().appDidReceiveMessage(data)
// Print full message.
Log.Info(data)
}
...
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
Log.Info("fcmToken \(fcmToken)")
}
func messaging(_ messaging: Messaging, didReceive remoteMessage: MessagingRemoteMessage) {
Log.Info("remort \(remoteMessage.appData)")
}
Data I Send
{
notification : {
"title" : "test title.",
"body" : "test context."
},
data : {
"image" : "http://11.111.111.111:100000000/_img/sample_01.jpg",
"page_url" : "http://11.111.111.111:100000000/Point?address=",
"type" : "point"
}
}
Logs cannot be viewed in any function. What am I missing? Please let me know what's wrong.
EDIT
I modified the wrong data and changed the location of the log. But I don't have any logs on me.
AppDelegate.swift
func application(_ application: UIApplication, didReceiveRemoteNotification data: [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
// Print full message.
Log.Info(data)
guard
let aps = data[AnyHashable("notification")] as? NSDictionary,
let body = aps["body"] as? String,
let title = aps["title"] as? String
else {
// handle any error here
return
}
Log.Info("Title: \(title) \nBody:\(body)")
Messaging.messaging().appDidReceiveMessage(data)
}
send test message in firebase
I have two messaging functions in addition to application functions of the application. Are these message functions not needed by me? These functions have never shown me a log.
The method that you are using was deprecated in iOS 10.
https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623117-application
The Notifications framework replaced this method with the updated one from iOS 10 onwards:
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void)
More details of the Notifications framework are available here
This solved this problem using a different function.
I solved it using a different function, but I wish there was another good solution.
I wonder what the fundamental problem was. This is just another alternative.
#available(iOS 10, *)
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
let data = response.notification.request.content.userInfo
guard
let aps = data[AnyHashable("aps")] as? NSDictionary,
let alert = aps["alert"] as? NSDictionary,
let body = alert["body"] as? String
else {
Log.Error("it's not good data")
return
}
Log.Info(body)
completionHandler()
}

How to update UI when app is in foreground?

When my app is active and I receive a silent notif from the server to update my tableview with the new data, I'm calling the following function in which I'm making a request to the server to bring latest data and then reload that specific row.
func updateCell(path: Int, messageId: String) {
let indexPath = IndexPath(item: path, section: 0)
if let visibleIndexPaths = mainTableView.indexPathsForVisibleRows?.index(of: indexPath as IndexPath) {
if visibleIndexPaths != NSNotFound {
if let id = self.userData?.id {
let conversationID = String(describing: id)
ServerConnection.getSpecificMessage(conversationId: conversationID, messageId: messageId) { (dataMessage) in
if let message = dataMessage {
self.chat[path] = message
self.mainTableView.beginUpdates()
self.mainTableView.reloadRows(at: [indexPath], with: .fade)
self.mainTableView.endUpdates()
}
}
}
}
}
}
My problem is when my app is in the foreground the flow doesn't work anymore because of the API request which can't be done in the foreground / background .
Console log shows :
load failed with error Error Domain=NSPOSIXErrorDomain Code=53 "Software caused connection abort"
I've tried to modify my function with
let state = UIApplication.shared.applicationState
if state == .background || state == .inactive {
NotificationCenter.default.addObserver(self, selector: #selector(self.reloadData(_:)), name: NSNotification.Name("BackgroundReload"), object: nil)
}
and posted this "BackgroundRelod" notification in AppDelegate
func applicationWillEnterForeground(_ application: UIApplication)
but this will always trigger my function even though I didn't receive any silent notification to update the UI.
You should not depend on background mode in updates , you need to only modify a var say needsUpdate whenever a silent notification comes in background here
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
Then
NotificationCenter.default.addObserver(self, selector: #selector(update), name: UIApplication.willEnterForegroundNotification, object: nil)
#objc func ppp(_ no:NSNotification) {
if needsUpdate {
// pull here
}
}

How to perform an action on a ViewController when pushing a notification and retrieve its data

I would like to know how to change the value of a UITextField on a ViewController whenever a notification arrives and the user taps on it. The notification contains the String that I will be putting on that UITextField.
This is how my app looks
I can currently retrieve the notification data on AppDelegate and decide which tab must be selected when the user taps on the notification. This is how I do it:
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)
let fragmento = response.notification.request.content.userInfo["fragmento"] as? String //Keeps the notification String "Fragmento" on a local variable
if fragmento == "anuncios"{ // Condition to select tab
if let tabbarController = self.window!.rootViewController as? UITabBarController {
tabbarController.selectedViewController = tabbarController.viewControllers?[1]
}
} else if fragmento == "registro"{
if let tabbarController = self.window!.rootViewController as? UITabBarController {
tabbarController.selectedViewController = tabbarController.viewControllers?[0]
}
}
completionHandler()
}
What I would like to do know is to pass the data from the notification to that specific Tab bar ViewController and change the value of the UITextField based on that data and then perform an action when that TextField changes its value.
I hope I explained myself well, otherwise please ask me whatever questions you have. Thank you so much
The NotificationCenter is potentially your simplest solution. Define a custom string to use as a common name for a a NotificationCenter notification that will be used to pass the information from the AppDelegate to whoever is listening. You can attach the string as the notification object.
When you instantiate that label, either via a custom class or from your view controller, add your notification listener to the NotificationCenter and upon receiving the notification retrieve the object attached to the notification, double check its a string then if so, use it to update your label.
For example, in AppDelegate.swift:
static let conferenciaNotification = NSNotification.Name(rawValue: "conferenciaNotification")
...
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let updatedTextFromReceivedPushNotification = "Hello world"
NotificationCenter.default.post(name: AppDelegate.conferenciaNotification, object: updatedTextFromReceivedPushNotification)
}
In your view controller with the label:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: AppDelegate.conferenciaNotification, object: nil, queue: OperationQueue.main) { (conferenciaNotification) in
if let conferenciaText = conferenciaNotification.object as? String {
myTextLabel.text = conferenciaText
}
}
}
Please note you probably should keep a reference to the NSObjectProtocol returned from the NotificationCenter when you add the observer, so you can remove it when your view controller is deinit().

Digits to firebase migration iOS Phone number input

I have been using digits for login via phone number. I have to migrate to firebase but there are a few things I am confused about:
My flow was:
1) User clicked on a custom button login via phone number which had action
#IBAction func loginViaDigits(_ sender: AnyObject) {
let digits = Digits.sharedInstance()
let configuration = DGTAuthenticationConfiguration(accountFields: .defaultOptionMask)
configuration?.phoneNumber = "+44"
digits.authenticate(with: self.navigationController, configuration: configuration!) { session, error in
if error == nil {
let digits = Digits.sharedInstance()
let oauthSigning = DGTOAuthSigning(authConfig:digits.authConfig, authSession:digits.session())
let authHeaders = oauthSigning?.oAuthEchoHeadersToVerifyCredentials()
self.startActivityIndicator()
NetworkApiCall(apiRequest: SignInApiRequest(digits_auth: authHeaders as! [NSObject : AnyObject])).run() { (result: SignInApiResponse?, error: NSError?) in
if error != nil {
self.stopActivityIndicator()
UIUtils.showInfoAlert("Some error occurred!", controller: self)
return;
}
guard let response = result else {
self.stopActivityIndicator()
UIUtils.showInfoAlert("Some error occurred!", controller: self)
return;
}
...
}
}
}
2) Basically, user clicked on login via phone number button and digits showed their popup for getting phone number and then they asked for verification code and when they were done, I would get the oauth params in my callback method and I passed them on to my server.
My question is:
1) Do I need to build both phone number input and verification code input screens myself or firebase is providing them like digits did?
2) If someone has actually migrated this kind of flow already, some pointers would be very helpful.
As suggested by Lazy King, I am trying to use FirebaseAuthUI, but my AppDelegate seems to be missing some function:
My AppDelegate changes for Firebase:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// Pass device token to auth
Auth.auth().setAPNSToken(deviceToken, type: AuthAPNSTokenType.prod)
}
func application(_ application: UIApplication,
didReceiveRemoteNotification notification: [AnyHashable : Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if Auth.auth().canHandleNotification(notification) {
completionHandler(.noData)
return
}
// This notification is not auth related, developer should handle it.
}
But I still keep getting this error:
Error Domain=FIRAuthErrorDomain Code=17054 "If app delegate swizzling is disabled, remote notifications received by UIApplicationDelegate need to be forwarded to FIRAuth's canHandleNotificaton: method."
You can use FirebaseAuth UI or design a UI for your own. For one of my project I used FirebaseAuth UI. Here is step by step:
Add FireBase Auth and Auth UI
pod 'Firebase/Auth', '~> 4.0.0' and
pod 'FirebaseUI/Phone', '~> 4.0'
In Appdelegate file register for push notification, this is mandatory. Google user push notification for first verification.
Add this line on didRegisterForRemoteNotificationsWithDeviceToken:
Auth.auth().setAPNSToken(deviceToken, type:AuthAPNSTokenType.prod)//.sandbox for development
You also need set up Google notification on you Firebase console
on Phone log in button function
var uiAuth = FUIAuth.defaultAuthUI() ;
uiAuth?.delegate = self;
var phoneVC = FUIPhoneAuth(authUI: uiAuth!)
uiAuth.providers = [phoneVC];
phoneVC.signIn(withPresenting: self)
implement delegate function
in Appdelegate receive notification function add code
if Auth.auth().canHandleNotification(userInfo) {
completionHandler(UIBackgroundFetchResult.noData)
return
}
If you use iOS 10 or later then
#available(iOS 10.0, *)
internal func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if Auth.auth().canHandleNotification(userInfo) {
completionHandler()
return
}
completionHandler()
}
Hope it will work.
please register notification before Firebase.configure().
it works

Posting NSNotification when receiving Push crash

When I receive a push notification, I postNotificaionName via NSNotificationCenter (from the AppDelegate method below):
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
playSoundWithUserInfo(userInfo)
NSNotificationCenter.defaultCenter().postNotificationName(kNotifPushReceived, object: nil)
}
I am making one of my view-controllers an observer for this notification:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(retrieveUsersAndSetData(_:)), name: kNotifPushReceived, object: nil
}
func retrieveUsersAndSetData(completed : (() -> Void)?) {
Friendship.retrieveFriendshipsForUser(backendless.userService.currentUser, includeGroups: false) { (friendships, fault) -> Void in
guard let friendships = friendships else { return }
self.friendships = friendships
self.tableView.reloadData()
}
}
Most of the time I receive a push notification, I receive a crash in the appDelegate where postNotificationName() is called:
The crash reads:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x10070)
Now most answers I've read that seem similar to this problem suggest that an object who was made an observer was never properly released, however I do not believe that is the case here because this is currently the only view-controller in the app, and I am implementing the following from the view-controller who is the observer of the notification:
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
All in all, I cannot figure out why this crash occurs most of the time when I receive the push notification in the app delegate and then post my NSNotification.
The handler method 'fetchCompletionHandler' expects by you to call the completionHandler at the end of your processing:
completionHandler(UIBackgroundFetchResult.NewData)
Please read the documentation of this method, there is more to consider then just that.

Resources