Firebase AuthUI - Find if existing user or new user - ios

I'm making use of firebase AuthUI to signup/login users. Depending on if the user is new or an existng user I need to redirect them to different view controllers. Does firebase provide a function to check that?
Here is my current code that, irrespective of new or registered user, segues to 'signUpSegue'
#IBAction func phoneNumberLoginAction(_ sender: AnyObject) {
FUIAuth.defaultAuthUI()?.delegate = self as FUIAuthDelegate
let phoneProvider = FUIPhoneAuth.init(authUI: FUIAuth.defaultAuthUI()!)
FUIAuth.defaultAuthUI()?.providers = [phoneProvider]
// 1
print("clicked butto")
guard let authUI = FUIAuth.defaultAuthUI()
else { return }
// 2
authUI.delegate = self as FUIAuthDelegate
// 3
let authViewController = authUI.authViewController()
present(authViewController, animated: true)
}
#available(iOS 10.0, *)
extension WelcomeViewController: WelcomePageViewControllerDelegate, FUIAuthDelegate {
func authUI(_ authUI: FUIAuth, didSignInWith user: User?, error: Error?) {
print("handle user signup / login")
performSegue(withIdentifier: "signUpSegue", sender: nil)
}
func welcomePageViewController(_ welcomePageViewController: WelcomePageViewController,
didUpdatePageCount count: Int) {
pageControl.numberOfPages = count
}
func welcomePageViewController(_ welcomePageViewController: WelcomePageViewController,
didUpdatePageIndex index: Int) {
pageControl.currentPage = index
}
}

It is not documented, but there is a callback function didSignInWithAuthDataResult which returns an AuthDataResult which provides a way to tell if a user is new or existing via additionalUserInfo.newUser.
https://github.com/firebase/FirebaseUI-iOS/blob/a69aa9536a0f5312bdd2d408a761c7dd21698015/FirebaseAuthUI/FUIAuth.h#L54

Are you saving the user to the database under a "Users" node, with their Auth ID? If you are, just do a query with the returned UID to see if they exist like this...
func checkIfUserExistsAlready(_ uid: String, complete: #escaping ((_ doesExist: Bool) -> Void)) {
//userRef if a Database Reference to "Users" node
userRef.child(uid).observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
complete(true)
} else {
complete(false)
}
}
}
You can call this and check in the FUIAuthDelegate function
func authUI(_ authUI: FUIAuth, didSignInWith user: User?, error: Error?) {
//Call does exists function here with user.uid
//Have if statement here and perform segues accordingly
performSegue(withIdentifier: "signUpSegue", sender: nil)
}

a) Try log in the Firebase whit the the phone number.
b) If the Firebase return error, check the error?.localizedDescription
c) If the error is an inexistent user, your user is new.
d) If the error is a wrong password, try a different password.

Related

How to convert Google iOS Sign-In single page sample AppDelegate.h protocol -> to a segue to LoginPage ViewController AppDelegate.swift protocol?

Google's Sign-In sample on GitHub SignInSampleForPod.xcworkspace creates a single page sign-in using the AppDelegate.h SignInViewController.m etc protocol.
However many apps, such as mine, prefer to segue to a Login Page only when a user makes a choice requiring verification. I just want the basic Google Profile info and authentication token.
I have the Google iOS ClientID configured enough so a segue to my LoginPage.swift shows the Google Sign-In button via AppDelegate.swift:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
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
}
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
if error != nil || user == nil {
// Show the app's signed-out state.
} else {
// Show the app's signed-in state.
}
}
return true
}
And LoginPage.swift:
class LoginViewController: UIViewController {
let signInConfig = GIDConfiguration.init(clientID: "foo-bar85.apps.googleusercontent.com")
override func viewDidLoad() {
super.viewDidLoad()
GIDSignIn.sharedInstance.signIn(with: signInConfig, presenting: self) { user, error in
guard error == nil else { return }
guard let user = user else { return }
let emailAddress = user.profile?.email
let fullName = user.profile?.name
let givenName = user.profile?.givenName
let familyName = user.profile?.familyName
let profilePicUrl = user.profile?.imageURL(withDimension: 320)
}
So my question is what is the AppDelegate.swift Google Sign-In code for the fields shown below to display the basic profile info:
// Show the app's signed-out state.
} else {
// Show the app's signed-in state.
I may not able to understand your problem clearly.
But I am trying to answer based on my understanding.
You can create a class (GoogleLoginManager) for all google login related stuff and create a button in UI then call this method (signIn) from button action.
#IBAction func googleButtonAction(_ sender: Any) {
GoogleLoginManager.shared.signIn(controller: self) { (profile) in
print("GoogleLogin profile : \(String(describing: profile.name)), \(String(describing: profile.email))")
} onFailure: { (error) in
print("GoogleLogin error : \(String(describing: error.localizedDescription))")
}
}
import Foundation
import GoogleSignIn
class GoogleLoginManager: SocialLogin {
fileprivate var onSuccess : success?
fileprivate var onFailure : failure?
static let shared = GoogleLoginManager()
private override init() { }
func signIn(controller: UIViewController, onSuccess : #escaping success, onFailure : #escaping failure) {
self.onSuccess = onSuccess
self.onFailure = onFailure
GIDSignIn.sharedInstance().clientID = GOOGLE_CLIENT_ID
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().presentingViewController = controller
GIDSignIn.sharedInstance().signIn()
// Automatically sign in the user.
// GIDSignIn.sharedInstance()?.restorePreviousSignIn()
}
func signOut() {
GIDSignIn.sharedInstance().signOut()
}
}
extension GoogleLoginManager : GIDSignInDelegate {
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!,
withError error: Error!) {
if let error = error {
if (error as NSError).code == GIDSignInErrorCode.hasNoAuthInKeychain.rawValue {
print("The user has not signed in before or they have since signed out.")
}
else if (error as NSError).code == GIDSignInErrorCode.canceled.rawValue {
print("user canceled the sign in request")
}
else {
print("\(error.localizedDescription)")
}
self.onFailure?(error)
return
}
var profile = SocialProfileModel.init(user: user)
profile.loginSuccess = true
self.onSuccess?(profile)
}
func sign(_ signIn: GIDSignIn!, didDisconnectWith user: GIDGoogleUser!,
withError error: Error!) {
// Perform any operations when the user disconnects from app here.
print("GIDSignIn : didDisconnectWith")
}
}
I just had to modify my above AppDelegate.swift slightly - adding a standard UIbutton linked to the following action - gets the profile info:
#IBAction func LogInButtonTouched(_ sender: UIButton) {
GIDSignIn.sharedInstance.signIn(with: signInConfig, presenting: self) { user, error in
guard error == nil else { return }
guard let user = user else { return }
let emailAddress = user.profile?.email
let fullName = user.profile?.name
let givenName = user.profile?.givenName
let familyName = user.profile?.familyName
let profilePicUrl = user.profile?.imageURL(withDimension: 320)
print("GoogleLogin profile : \(String(describing: user.profile?.name)), \(String(describing: user.profile?.email))")
}
}

Stripe iOS didCreatePaymentResult never gets called

The problem seems simple, didCreatePaymentResult never gets called.
BUT, in my old sample project, taken from your iOS example for payment intent, that didCreatePaymentResult gets called every single time I create or select a card, here's the repo of the working project: https://github.com/glennposadas/stripe-example-ios-nodejs
BUT again, my main concern is my current project.
I use v19.2.0 in both of these projects, I even tried the v19.3.0.
I wanted to use Stripe Charge really, but I believe Stripe does not support Apple pay for that. So I have no choice but to use Stripe Payment Intent.
CoreService.swift (conforms to STPCustomerEphemeralKeyProvider)
extension CoreService: STPCustomerEphemeralKeyProvider {
func createCustomerKey(withAPIVersion apiVersion: String, completion: #escaping STPJSONResponseCompletionBlock) {
orderServiceProvider.request(.requestEphemeralKey(stripeAPIVersion: apiVersion)) { (result) in
switch result {
case let .success(response):
guard let json = ((try? JSONSerialization.jsonObject(with: response.data, options: []) as? [String : Any]) as [String : Any]??) else {
completion(nil, NSError(domain: "Error parsing stripe data", code: 300, userInfo: nil))
return
}
completion(json, nil)
default:
UIViewController.current()?.alert(title: "Error stripe", okayButtonTitle: "OK", withBlock: nil)
}
}
}
}
PaymentController.swift
class PaymentViewController: BaseViewController {
// MARK: - Properties
private var paymentContext: STPPaymentContext!
private let paymentConstantValue: Int = 3000
// MARK: - Functions
// MARK: Overrides
override func viewDidLoad() {
super.viewDidLoad()
self.setupStripe()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.hideNavBar(animated: true)
}
#IBAction func creditCardButtonTapped(_ sender: Any) {
self.paymentContext.presentPaymentOptionsViewController()
}
private func setupStripe() {
let config = STPPaymentConfiguration.shared()
config.appleMerchantIdentifier = "merchant.com.gsample.app"
config.companyName = "Scoutd LLC"
config.requiredBillingAddressFields = .none
config.requiredShippingAddressFields = .none
config.additionalPaymentOptions = .applePay
let customerContext = STPCustomerContext(keyProvider: CoreService())
let paymentContext = STPPaymentContext(
customerContext: customerContext,
configuration: config,
theme: STPTheme.default()
)
let userInformation = STPUserInformation()
paymentContext.prefilledInformation = userInformation
paymentContext.paymentAmount = self.paymentConstantValue
paymentContext.paymentCurrency = "usd"
self.paymentContext = paymentContext
self.paymentContext.delegate = self
self.paymentContext.hostViewController = self
}
}
// MARK: - STPPaymentContextDelegate
extension PaymentViewController: STPPaymentContextDelegate {
func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
print("paymentContextDidChange")
}
func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) {
// error alert....
}
func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: #escaping STPPaymentStatusBlock) {
print("didCreatePaymentResult ✅")
}
func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
switch status {
case .success:
// success
case .error:
// error alert....
default:
break
}
}
}
SOLVED! This should help engineers struggling with Stripe implementation in the future.
So in my case, I have two buttons:
Apple Pay
Credit card.
The absolute solution for me is handle the selectedPaymentOption of the paymentContext.
Scenarios:
If the apple pay button is tapped, present apple pay sheet and don't present add/select card UI of Stripe.
If the credit card button is tapped, don't present apple pay sheet and instead present select card.
Related to #2, call requestPayment() if there's a selected option.
Voila! The didCreatePaymentResult now gets invoked!
// MARK: IBActions
#IBAction func applePayButtonTapped(_ sender: Any) {
if self.paymentContext.selectedPaymentOption is STPApplePayPaymentOption {
self.paymentContext.requestPayment()
}
}
#IBAction func creditCardButtonTapped(_ sender: Any) {
if let selectedPaymentOption = self.paymentContext.selectedPaymentOption,
!(selectedPaymentOption is STPApplePayPaymentOption) {
self.paymentContext.requestPayment()
return
}
self.paymentContext.presentPaymentOptionsViewController()
}

I have written some code for my users to log in to my app, but email and password column doesn't appear

With the help of Chris Coding on youtube, I have tried to open a authViewController where my users should be able to sign up. So when I try it out I can reach the authViewController but the email and password column doesn't appear in the screen. So does anyone have an idea where the problem may occur?
#IBAction func logInTapped(_ sender: Any) {
let authUI = FUIAuth.defaultAuthUI()
guard authUI != nil else {
return
}
authUI?.delegate = self
let authViewController = authUI?.authViewController()
present(authViewController!, animated: true, completion: nil)
}
extension ViewController: FUIAuthDelegate{
func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?,error: Error?){
if error != nil {
return
}
performSegue(withIdentifier: "goHome", sender: self)
}
}

Redirect the user to a specific page after signIn

Main issue is checking if user has a child in the Firebase database so that I know if he is signing up or logging in.
Part 1: Part 1 (Child Database (this works) and making that a user default (I'm not sure how to check it that worked)
Part 2: in different .Swift file (Check if the User Default (aka Education Child) exists. I have pretty much nothing, except I know it must go into viewDidAppear
Part 1
#IBAction func SubmitPressed(_ sender: Any) {
let databaseRef = Database.database().reference()
let uid = Auth.auth().currentUser!.uid
databaseRef.child("Education").child(uid).setValue(self.Education.text!)
UserDefaults.standard.set("Education", forKey: "Education")
Part 2
func viewDidAppear(_ animated: String) {
??????
}
No error for part 1, though not sure if it created the user default. For part 2, I have tried a bunch of stuff, but hasn't worked.
Here is the updated code after first answer:
import Foundation
import UIKit
import SwiftKeychainWrapper
import Firebase
import CoreFoundation
import AVFoundation
import FirebaseDatabase
var educationCache : String {
get {
return (UserDefaults.standard.string(forKey: "Education")!)
} set {
UserDefaults.standard.set(newValue, forKey: "Education")
}
}
relavant part of education/personal info enter page
#IBAction func SubmitPressed(_ sender: Any) {
let databaseRef = Database.database().reference()
let uid = Auth.auth().currentUser!.uid
databaseRef.child("Education").child(uid).setValue(self.Education.text!)
// The following line will save it in userDefault itself. And you dont have to call the whole UserDefault Everywhere
educationCache = "Education"
self.performSegue(withIdentifier: "tohome", sender: nil)
}
homepage
import Foundation
import UIKit
import SwiftKeychainWrapper
import Firebase
import CoreFoundation
import AVFoundation
import FirebaseDatabase
class homepage:UITableViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if educationCache.count < 0 {
self.performSegue(withIdentifier: "toFeed", sender: nil)
}
}
override func viewDidLoad() {
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Sign Out", style: .plain, target: self, action: #selector(signOut))
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#objc func signOut (_sender: AnyObject) {
KeychainWrapper.standard.removeObject(forKey: "uid")
do {
try Auth.auth().signOut()
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
dismiss(animated: true, completion: nil)
}
}
You dont want to check the condition is the viewController where you are performing specific actions. Also checking the userDefaults everytime is just putting extra views in heirarchy. Firebase provides .addStateDidChangeListener function to keep a check of it. Add the following code to your delegate. It will automatically switch between the users if there are any or not.
AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
observeUser()
return true
}
func observeUser(){
Auth.auth().addStateDidChangeListener { (auth, user) in
if (user != nil) {
// If the user is not nil. Perform this block
self.showInitController(with identifier: "<add_your_storyboard_reusableID_for_homepage>", animated: false)
// The storyboard Id belongs to the HOMEPAGE/FEED, the view where user goes on signIn
} else {
//If there is no user. This block will perform
self.showInitController(with identifier: "<add_your_storyboard_reusableID_for_loginpage>", animated: false)
// The storyboard Id belongs to the SIGNUP PAGE, the view where user goes on signIn
}
}
}
func showInitController(with identifier: String, animated: Bool = false) {
DispatchQueue.main.async {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: identifier)
var topRootViewController: UIViewController = (UIApplication.shared.keyWindow?.rootViewController)!
while((topRootViewController.presentedViewController) != nil){
topRootViewController = topRootViewController.presentedViewController!
}
topRootViewController.present(vc, animated: animated, completion: nil)
}
}
In your swift file one. do the following and never look back. You can automatically redirect to view if you applied the other functions.
#IBAction func SubmitPressed(_ sender: Any) {
let databaseRef = Database.database().reference()
let uid = Auth.auth().currentUser!.uid
databaseRef.child("Education").child(uid).setValue(self.Education.text!)
self.performSegue(withIdentifier: "tohome", sender: nil)
}
In the second view dont bother to do anything anywhere. Just leave it, if nothing in that view is required again.

How to force new user or who just register have to fill the form first (FirebaseUI in IOS)

I'm using FirebaseUI as registration system, and now user can login by 2 way (facebook and phone number) but now problem is i want new user or first time login must fill the form before go to MainViewController. on the other hand, old user who ever fill the form will go to MainViewController without force to fill the form again.
Now I'm using Xcode 10.2 and FirebaseUI 6.2.1
Now I'm using Xcode 10.2 and FirebaseUI 6.2.1
import UIKit
import Firebase
import FirebaseUI
class UserLoginViewController: UIViewController, FUIAuthDelegate {
override func viewDidAppear(_ animated: Bool){
super.viewDidAppear(animated)
if Auth.auth().currentUser != nil {
//Old user go to segue withIdentifier "GoMain"
self.performSegue(withIdentifier: "GoMain", sender: nil)
//First time login go to segue withIdentifier "FillForm"
}
let authUI = FUIAuth.defaultAuthUI()
guard authUI != nil else {
return
}
authUI?.delegate = self
let providers: [FUIAuthProvider] = [
FUIFacebookAuth(),
FUIPhoneAuth(authUI:FUIAuth.defaultAuthUI()!),
]
authUI?.providers = providers
let authViewController = authUI!.authViewController()
authViewController.isNavigationBarHidden = true
present(authViewController, animated: true, completion: nil)
}
}
extension UserLoginViewController {
func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?, error: Error?) {
if error != nil {
return
}
}
}

Resources