Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI - Firebase Authentication - Environmental Object Doesn't Update on Sign In

I followed this tutorial to add Firebase user authentication to my SwiftUI project. When the user signs in or signs up, the sign in view is supposed to dissapear, and text displaying "Login Successful" should appear instead. However, this does not happen. It seems like the login / sign up process works, but the views don't change, and I suspect it may a problem with the EnvironmentalObject. Does anybody have some ideas about why this is happening? Thank you!

Here is some of my code -

AppDelegate.swift

...
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(SessionStore()))
        self.window = window
        window.makeKeyAndVisible()
    }
}
...

ContentView.swift

import SwiftUI

struct ContentView : View {

  @EnvironmentObject var session: SessionStore

  func getUser () {
      session.listen()
  }

  var body: some View {
      VStack {
          if (session.session == nil) {
              SignInView()
          } else {
              Text("Login successful!")
          }
    }.onAppear {
        self.getUser()
    }
  }
} 

SignInView.swift

import SwiftUI

struct SignInView : View {

    @State var email: String = ""
    @State var password: String = ""
    @State var loading = false
    @State var error = false

    @State var signInWorked = ""

    @EnvironmentObject var session: SessionStore

    func signIn () {
        loading = true
        error = false
        session.signIn(email: email, password: password) { (result, error) in
            self.loading = false
            if error != nil {
                self.error = true
            } else {
                self.email = ""
                self.password = ""
                self.signInWorked = "Signed In!"
            }
        }
    }

    func signUp () {
        loading = true
        error = false
        session.signUp(email: email, password: password) { (result, error) in
            self.loading = false
            if error != nil {
                self.error = true
            } else {
                self.email = ""
                self.password = ""
                self.signInWorked = "Signed In!"
            }
        }
    }

    var body: some View {
        VStack {
            TextField("Email Adress", text: $email)
            SecureField("Password", text: $password)
            if (error) {
                Text("ahhh crap")
            }
            Button(action: signIn) {
                Text("Sign in")
            }
            Button(action: signUp) {
               Text("Sign Up")
            }
            Text(signInWorked)
        }
    }
}

SessionStore.swift

import SwiftUI
import Firebase
import Combine

class SessionStore : ObservableObject {
var didChange = PassthroughSubject<SessionStore, Never>()
var session: User? { didSet { self.didChange.send(self) }}
var handle: AuthStateDidChangeListenerHandle?

func listen () {
    // monitor authentication changes using firebase
    handle = Auth.auth().addStateDidChangeListener { (auth, user) in
        if let user = user {
            // if we have a user, create a new user model
            print("Got user: \(user)")
            self.session = User(
                uid: user.uid,
                displayName: user.displayName,
                email: user.email)
        } else {
            // if we don't have a user, set our session to nil
            self.session = nil
        }
    }
}

func signUp(
    email: String,
    password: String,
    handler: @escaping AuthDataResultCallback
    ) {
    Auth.auth().createUser(withEmail: email, password: password, completion: handler)
}

func signIn(
    email: String,
    password: String,
    handler: @escaping AuthDataResultCallback
    ) {
    Auth.auth().signIn(withEmail: email, password: password, completion: handler)
}

func signOut () -> Bool {
    do {
        try Auth.auth().signOut()
        self.session = nil
        return true
    } catch {
        return false
    }
}

func unbind () {
    if let handle = handle {
        Auth.auth().removeStateDidChangeListener(handle)
    }
}
}

User.swift

import Foundation

class User {
var uid: String
var email: String?
var displayName: String?

init(uid: String, displayName: String?, email: String?) {
    self.uid = uid
    self.email = email
    self.displayName = displayName
}
}
like image 959
George B Avatar asked Aug 26 '19 01:08

George B


1 Answers

You need to add the property wrapper @Published to your var session in SessionStore ensure that all views using it are refreshed when it changes. So change

var session: User? { didSet { self.didChange.send(self) }}

to

@Publishedvar session: User? { didSet { self.didChange.send(self) }}

See here for more info: https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views

like image 118
Benjamin Lavin Avatar answered Oct 19 '22 19:10

Benjamin Lavin