Handling Firebase Auth error in SwiftUI Combine app

I have an app where users can sign up and login using Firebase. However, I can not seem to alert the user of any errors in the view.

First we have a UserStore which is a ObservableObject and is initialised as an EnvironmentObject when setting up the view in the SceneDelegate.

let appView = AppView().environmentObject(userStore)

  if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: appView)
        self.window = window
        window.makeKeyAndVisible()

}

Then we sign up or login to the View like so.

In View

self.userStore.logIn(email: self.email, password: self.password)
self.isLoggingIn = true

if self.userStore.failedToLogin {
   self.isLoggingIn = false
   self.alertTitle = "There seems to be a problem"
   self.alertBody = self.userStore.errorMessage
   self.showingAlert = true
}

Then the actual method should update the UserStore property values which then update the view and display an alert, however, this is not the case.

SignIn

// Session Properties
@Published var isLoggedIn = false {didSet{didChange.send() }}
@Published var isNewUser = false {didSet{didChange.send() }}
@Published var failedToCreateAccount = false {didSet{didChange.send() }}
@Published var failedToLogin = false {didSet{didChange.send() }}
@Published var errorMessage = "" {didSet{didChange.send() }}

init () {
  handle = Auth.auth().addStateDidChangeListener { (auth, user) in
    if let user = user {
      self.session = user
      self.isLoggedIn = true
      //Change isNewUser is user document exists?
   } else {
      self.session = nil
      self.isLoggedIn = false
    }
  }
}

func logIn(email: String, password: String) {

    Auth.auth().signIn(withEmail: email, password: password) { [weak self] user, error in

        print("Signed In")

        if(error != nil) {
            print("Failed to login")

            self!.failedToLogin = true
            self!.errorMessage = ("\(String(describing: error))")
            print(error!)
            return
        } else if error == nil {
            print("Success Logging In")

        }
    }
}

The AppView determines which view is loaded depending if the user is logged in.

AppView

        if !userStore.isLoggedIn {
            LoginView().transition(.opacity)
        }

        if userStore.isLoggedIn  {
            ContentView().transition(.opacity)
        }

Atm error messages are not shown; the login view is also shown shortly before the main view.

How can I correctly display error messages in the view ?

1 answer

  • answered 2020-02-13 19:51 Peter Friese

    The Firebase APIs are asynchronous, simply because they access a remote system, across the internet, which takes a little time. The same applies for accessing the local disk, by the way. This blog post explains this in more detail.

    Consequently, userStore.login is an asynchronous process. I.e. the call to if self.userStore.failedToLogin is executed before userStore.login returns. Thus, userStore.failedToLogin is still false, and the code in the conditional statement will never be executed.

    There are two ways around this:

    1. Implement a trailing closure on userStore.logIn, and move the code which displays the error into the closure
    2. Make userStore.failedToLogin a publisher and subscribe the visibility of your alert to it