0

Once the user signs in with Google, I want to take them to the home screen; however, the code does not fully execute this.

This is the code:

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 {
          print("\(error.localizedDescription)")
        }
        return
      }
        let firstName = user.profile.givenName
        let lastName = user.profile.familyName
        let email = user.profile.email
    //Firebase sign in
      guard let authentication = user.authentication else {return}
        let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken)

        Auth.auth().signIn(with: credential) { (authResult, error) in
            if let error = error {
                print("Firebase sign In error")
                print(error)
                return
            } else {
                let db = Firestore.firestore()

                db.collection("users").addDocument(data: ["firstName": firstName!, "lastName": lastName!, "email": email!, "uid": authResult!.user.uid]) { (error) in

                    if error != nil {
                        print("Error: User data not saved")
                    }
            }
            print("User is signed in with Firebase")
            let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
            let homeViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeVC") as! HomeViewController
            self.window?.rootViewController = homeViewController
            self.window?.makeKeyAndVisible()

        }
    }
}

More specifically:

Auth.auth().signIn(with: credential) { (authResult, error) in
            if let error = error {
                print("Firebase sign In error")
                print(error)
                return
            } else {
                let db = Firestore.firestore()

                db.collection("users").addDocument(data: ["firstName": firstName!, "lastName": lastName!, "email": email!, "uid": authResult!.user.uid]) { (error) in

                    if error != nil {
                        print("Error: User data not saved")
                    }
            }
            print("User is signed in with Firebase")
            let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
            let homeViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeVC") as! HomeViewController
            self.window?.rootViewController = homeViewController
            self.window?.makeKeyAndVisible()

        }
    }

the print("User is signed in with Firebase") does take place but it fails to switch the HomeViewController and I'm not sure what it is that I am doing wrong here.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
KoolKid
  • 45
  • 5
  • See my answer but as a followup question, the `.signIn' function is to sign in existing users. With this code flow, every time an existing user signs in, it will create a new document in the users collection - is that what you want? – Jay Jun 13 '20 at 12:47
  • No it isn't. Working on that. – KoolKid Jun 13 '20 at 17:46

2 Answers2

0

My guess is that window is nil and that's why your code isn't executing properly. Try the code below if you are targeting iOS 13. Ideally, you would want to move the Sign-in code inside SceneDelegate and duplicate it in AppDelegate if your app also supports iOS 12, 11 etc.

   Auth.auth().signIn(with: credential) { (authResult, error) in
        if let error = error {
            print("Firebase sign In error")
            print(error)
            return
        } else {
            let db = Firestore.firestore()

            db.collection("users").addDocument(data: ["firstName": firstName!, "lastName": lastName!, "email": email!, "uid": authResult!.user.uid]) { (error) in

                if error != nil {
                    print("Error: User data not saved")
                }
            }
            print("User is signed in with Firebase")
            DispatchQueue.main.async {
                let scene = UIApplication.shared.connectedScenes.first
                if let sceneDelegate = scene?.delegate as? SceneDelegate {  
                    let window = sceneDelegate?.window
                    let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
                    let homeViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeVC") as! HomeViewController
                    window?.rootViewController = homeViewController
                    window?.makeKeyAndVisible()

                }
            }
        }
    }
rs7
  • 1,618
  • 1
  • 8
  • 16
  • So then how do I get it to run only under the condition that the user has finished signing in? – KoolKid Jun 13 '20 at 06:58
  • @KoolKid If this answer is referring to the *Firebase closure*, UI calls within Firebase closures are all [called on the main thread](https://stackoverflow.com/questions/56314265/how-to-display-data-from-firebase-faster/56314312#56314312) so no need for DispatchQueues. – Jay Jun 13 '20 at 12:38
  • @KoolKid If you are targeting iOS13, try the code I just posted. Ideally, you would want to move the Sign-in code inside SceneDelegate and duplicate it in AppDelegate if your app also supports iOS 12, 11 etc. – rs7 Jun 13 '20 at 18:26
  • Can you explain the difference between AppDelegate and SceneDelegate. I am familiar with Swift but not so much with app development so I'm not sure what that would change. – KoolKid Jun 13 '20 at 22:41
  • Here is a good explanation: https://stackoverflow.com/questions/56498099/difference-between-scenedelegate-and-appdelegate Did you get a chance to try the code? – rs7 Jun 14 '20 at 00:16
  • It seems to only be effective in a use case for iPadOS as opposed to iPhone. – KoolKid Jun 16 '20 at 08:15
0

The problem in the code is that Firebase is asynchronous. So this section

} else {
   let db = Firestore.firestore()

   db.collection("users").addDocument(data: ["firstName": firstName!, "lastName": lastName!, "email": email!, "uid": authResult!.user.uid]) { (error) in

      if error != nil {
         print("Error: User data not saved")
      }
   }

   print("User is signed in with Firebase")

This print statement

print("User is signed in with Firebase")

will execute before the code within the closure. The reason is the code is faster than the internet - you have to 'wait' for data to be returned from Firebase before moving forward in your code. To fix, move the statements inside the closure so they execute when Firebase returns

} else {
   let db = Firestore.firestore()

   db.collection("users").addDocument(data: ["firstName": firstName!, "lastName": lastName!, "email": email!, "uid": authResult!.user.uid]) { (error) in

      if error != nil {
         print("Error: User data not saved")
         return
      }

      print("User is signed in with Firebase")
      let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
      let homeViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeVC") as! HomeViewController
      self.window?.rootViewController = homeViewController
      self.window?.makeKeyAndVisible()
    }

Note that with this code flow, every time an existing user signs it, it will create a new document in the users collection. That's probably not the intention.

Jay
  • 34,438
  • 18
  • 52
  • 81
  • Yeah I'm still working on fixing the issue of a new document being created every time they login. So I tried the return statement but it still won't change the view controller to homeViewController. My issue isn't that there is a problem with the sign in, it's that once they are signed in, everything after the print does not take place as intended. I feel like something my "wording" there is wrong. – KoolKid Jun 13 '20 at 17:19
  • @KoolKid When do you *want* to create that data in Firestore? I would think that it would be when the user is first created with [.createUser.](https://firebase.google.com/docs/auth/ios/password-auth#create_a_password-based_account) After that, there would be no need to recreate the document in the `.signIn` function – Jay Jun 14 '20 at 12:24
  • Yes I only want to create the data only once. The .createUser takes place when the user is creating their account with the their email. In this instance, the user is signing with their Google account. I would like to use Auth.auth().createUser() but I am not sure what would go into the parameters for creating the user. Normally Auth.auth().createUser(withEmail: email, password: password) would work, but I'm not sure about Google accounts. – KoolKid Jun 14 '20 at 22:11
  • Two remarks here: (1) have a look at [FirebaseUI](https://firebase.google.com/docs/auth/ios/firebaseui), this will make creating a sign-up/login experience much easier. (2) if you want to run code *once* for newly created users, write a Firebase Cloud Function that triggers when a new user is created. See [Extend Firebase Authentication with Cloud Functions](https://firebase.google.com/docs/auth/extend-with-functions) for details. – Peter Friese Jun 17 '20 at 06:24