Firebase Phone Authentication broken after pod update - ios

In my setPhoneNumberVerificationID() function, I am hitting this error:
'Error Domain=FIRAuthErrorDomain Code=17053 "Remote notification and background fetching need to be set up for the app. If app delegate swizzling is disabled, the APNs device token received by UIApplicationDelegate needs to be forwarded to FIRAuth's APNSToken property." UserInfo={NSLocalizedDescription=Remote notification and background fetching need to be set up for the app. If app delegate swizzling is disabled, the APNs device token received by UIApplicationDelegate needs to be forwarded to FIRAuth's APNSToken property., error_name=ERROR_MISSING_APP_TOKEN}'
My didReceiveRemoteNotification function looks like:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if Auth.auth().canHandleNotification(userInfo) {
completionHandler(.noData)
return
}
guard let aps = userInfo["aps"] as? [String: AnyObject] else {
completionHandler(.failed)
return
}
print("got something, aka the \(aps)")
}
This is the setPhoneNumberVerificationID function:
func setPhoneNumberVerificationID(phoneNumber: String) {
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber) { (verificationID, error) in
if let error = error {
print(error) //hitting this error here
return
}
guard verificationID != nil else { return }
print("error is: \(error) and verificationID is \(verificationID)")
//do stuff with verificationID
}
}
As you can see below, my remote notification and background fetching is on.
https://i.stack.imgur.com/BPjxq.png
I had to very slightly change the .verifyPhoneNumber() arguments, because it used to take a 'uiDelegate: nil' parameter as well...how do I fix this???

Related

Simulator FirebaseAuth works! But not a physical device. Error 'Token Mismatch'

Im trying to implement the Phone-Auth for iOS app.
I get error from error.localizedDescription: Token mismatch.
What does this mean? "Token mismatch" says nothing to me...
Also, I get this error: "This fake notification should be forwarded to Firebase Auth."
Code:
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { (verificationID, error) in
if let error = error {
print(error.localizedDescription)
return
}
// Sign in using the verificationID and the code sent to the user
// ...
AppDelegate:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let firebaseAuth = Auth.auth()
firebaseAuth.setAPNSToken(deviceToken, type: AuthAPNSTokenType.prod)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let firebaseAuth = Auth.auth()
if (firebaseAuth.canHandleNotification(userInfo)){
print(userInfo)
return
}
// Print full message.
print(userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
I solved the problem, After enabling phone Authentication, we must download GoogleService-Info.plist again to override the old one

AppDelegate Never Gets Its didReceiveRemoteNotification Called For CKQuerySubscription

I'm trying to let the iOS app listen to CKQuerySubscription changes. Data is transmitted by a remote iOS app. I already have a macOS application, which does receive data sent by the remote iOS app. The iOS app I have trouble with already has a subscription. Yet, its AppDelegate never receives a call in the didReceiveRemoteNotification method.
import UIKit
import UserNotifications
import CloudKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
/* notifications */
let center = UNUserNotificationCenter.current()
center.delegate = self
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
switch settings.authorizationStatus {
case .authorized:
print("You already have permission")
DispatchQueue.main.async() {
application.registerForRemoteNotifications()
}
case .denied:
print("setting has been disabled")
case .notDetermined:
print("Let me ask")
UNUserNotificationCenter.current().requestAuthorization(options: []) { (granted, error) in
if error == nil {
if granted {
print("you are granted permission")
DispatchQueue.main.async() {
application.registerForRemoteNotifications()
}
}
}
}
}
}
return true
}
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register notifications_ error:", error)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("Receiving data...") // never called...
}
}
I have some capabilities on as shown below. I don't know if the app needs push notifications. For now, it's turned on.
So why doesn't my iOS app get the remote notification call? I'm using the app with an actual device, not a simulator. Thanks.
EDIT: Creating a subscription to a record change
class HomeViewController: UIViewController {
override func viewDidLoad() {
registerSubscription()
}
func registerSubscription() {
let cloudContainer = CKContainer(identifier: "iCloud.com.xxx.XXXXX")
let privateDB = cloudContainer.privateCloudDatabase
let predicate = NSPredicate(format: "TRUEPREDICATE")
let subscription = CKQuerySubscription(recordType: "PrivateRecords", predicate: predicate, options: .firesOnRecordCreation)
let notification = CKNotificationInfo()
subscription.notificationInfo = notification
privateDB.save(subscription, completionHandler: ({returnRecord, error in
if let err = error {
print("Subscription has failed: \(err.localizedDescription)")
} else {
print("Subscription set up successfully")
print("Subscription ID: \(subscription.subscriptionID)")
}
}))
}
}
There are a few more things you can check.
First, make sure you implement didReceiveRemoteNotification in your app delegate:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
let dict = userInfo as! [String: NSObject]
let notification = CKNotification(fromRemoteNotificationDictionary: dict)
if let sub = notification.subscriptionID{
print("iOS Notification Received: \(sub)")
}
}
There are also a few other things you can check:
Try deleting your CKQuerySubscription in the CloudKit dashboard, then run your iOS code again that registers it. Does the subscription show up in the dashboard?
Does the CloudKit log show that a notification was sent? It lists all notifications that were pushed to a device.
If you are using silent push notifications, try enabling Background fetch in the Background Modes capability (right above Remote notifications).
If you do all that and it still doesn't work, can you share your CKQuerySubscription code?
-- Update --
Try setting some additional attributes on your CKNotificationInfo object. There are some obscure bugs with notifications that can usually be circumvented by setting a couple properties like this:
notification.shouldSendContentAvailable = true
notification.alertBody = "" //(Yes, a blank value. It affects the priority of the notification delivery)
You can also try setting your predicate to: NSPredicate(value: true)
Also, what does your privateDB.save method return? Does it say it succeeds or fails?

how to setting up AppDelegate for push notification in swift

I am trying to setup a push notification system for my application. I have a server and a developer license to setup the push notification service.
I am currently running my app in Swift4 Xcode 9
here are my questions :
1_ is that possible that I set the title and body of notification massage ??
2_ what is the func of receiving massage ? I'm using didReceiveRemoteNotification but this is called when I touch the notification I need a func which is called before showing notification that I can set my massage on it
3_ I'm generating device token in appDelegate and also in my login page for my server which are different from each other. this is not correct right ?
this is my app delegate :
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
print("lunch",launchOptions?.description,launchOptions?.first)
application.registerForRemoteNotifications()
FirebaseApp.configure()
GMSPlacesClient.provideAPIKey("AIzaSyAXGsvzqyN3ArpWuycvQ5GS5weLtptWt14")
UserDefaults.standard.set(["fa_IR"], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
registerForPushNotifications()
return true
}
func messaging(_ messaging: Messaging, didReceive remoteMessage: MessagingRemoteMessage) {
print("test : ",messaging.apnsToken)
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
print("Recived: \(userInfo)")
print()
// completionHandler(.newData)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
print("userInfo : ",userInfo)
if application.applicationState == .active {
print("active")
//write your code here when app is in foreground
} else {
print("inactive")
//write your code here for other state
}
}
func getNotificationSettings() {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
print("Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
} else {
}
}
func registerForPushNotifications() {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {
(granted, error) in
print("Permission granted: \(granted)")
guard granted else { return }
self.getNotificationSettings()
}
} else {
let settings = UIUserNotificationSettings(types: [.alert, .sound, .badge], categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)
UIApplication.shared.registerForRemoteNotifications()
// self.getNotificationSettings()
}
}
Yes, you can manage the content of notification by sending an appropriate payload in the notification. Sending the payload in the following pattern would show title and body in the notification
{
"aps" : {
"alert" : {
"title" : "Game Request",
"body" : "Bob wants to play poker",
},
"badge" : 5
}
}
Display the notification is handled by the system depending upon the app state. If the app is the foreground state you will get the call in the didReceiveRemoteNotification, otherwise, the system handles the displaying part and get control in the app when the user taps on the notification.
You cannot edit the content of notification from the app side.
According to the document
APNs can issue a new device token for a variety of reasons:
User installs your app on a new device
User restores device from a backup
User reinstalls the operating system
Other system-defined events
So its recommended requesting device token at launch time.
You can send the token in login page rather than requesting a new token in the login.

Swift Warning: Application delegate received call to -application:performFetchWithCompletionHandler: but the completion handler was never called

I am calling this function below in the simulator to simulate background fetch.
Then I get this warning in the log:
Swift Warning: Application delegate received call to -application:performFetchWithCompletionHandler: but the completion handler was never called.
I have seen other Stack Iverflow answers say I just need to add completionhandler(). I've tried this and it says I need to add a parameter and that's where I am lost.
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler:#escaping (UIBackgroundFetchResult) -> Void) {
let db = Firestore.firestore()
guard let uid = Auth.auth().currentUser?.uid else { return }
//check if user online
let docRef = db.collection("Users").document(uid)
docRef.getDocument { (document, error) in
if let document = document {
if document.exists {
guard let dictionary = document.data() else { return }
guard let onlineOfflineStatus = dictionary["Online Offline Status"] as? String else { return }
// if online create value to set offline an alert
if onlineOfflineStatus == "Online" {
print("user is Online and inactive, will set value to trigger notification asking if they would like to go offline")
db.collection("sendGoOffline").document(uid).setData(["OfflineAlert" : 1200], completion: { (error) in
if let error = error {
print("there was an error", error)
}
})
}
}
}
if let error = error {
print("failed to fetch user", error)
}
}
}
The warning is telling you to add this method:
completionHandler(argument)
where the argument is one of the following:
UIBackgroundFetchResult.noData
UIBackgroundFetchResult.newData
UIBackgroundFetchResult.failed
The purpose is to tell the system that you are done.
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler:#escaping (UIBackgroundFetchResult) -> Void) {
// do backgound data fetch
// process it
// finished
completionHandler(UIBackgroundFetchResult.newData)
}
Read more here:
documentation
old but good article
UIBackgroundFetchResult
Remote notification related articles
How to handle remote notification with background mode enabled
Multiple scenarios with Push Notification in iOS
application(_:didReceiveRemoteNotification:fetchCompletionHandler:)

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

Resources