0

I'm currently building a simple app with a SignIn/SignUp View in order to better understand how to integrate SwiftUI with Firebase.

I've run into a bit of an issue when I try to update User Data. I noticed that after Signing Out as one User and then Signing In as a different User, there is a split second delay before the User information is updated on the FirstView after the Sign In button is tapped. I'm trying to figure out how to avoid this delay from happening.

As a side, I'm also having trouble figuring out how to smoothly navigate from my LoginView() to my FirstView(). I have my FirstView() inside of a Navigation Stack, but the transition between the views is very abrupt and devoid of the NavigationLink animation. How can I correct this issue?

Much appreciated!

Here is the relevant code...

@main
struct AWSupportLoggerApp: App {
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    @StateObject var viewModel = AppViewModel()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(viewModel)
        }
    }
    
    
    class AppDelegate:NSObject,UIApplicationDelegate{
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
            FirebaseApp.configure()
            return true
        }
    }
    
    
}

struct ContentView: View {
    @EnvironmentObject var viewModel: AppViewModel
    
    var body: some View {
        
        NavigationView {
            if viewModel.signedIn {
                 FirstView()
            } else {
                //.onAppear method is used for keyboard management (See Misc Functions...)
                SignInView()
                    .onAppear(perform: UIApplication.shared.addTapGestureRecognizer)
                    .navigationBarHidden(true)
            }
        }
        .onAppear {
            viewModel.listen()
        }
    }
}

class AppViewModel: ObservableObject {
    private var db = Firestore.firestore()
    
    @Published var userInfo: User?
    @Published var signedIn: Bool = false
    
    var handle: AuthStateDidChangeListenerHandle?
    let authRef = Auth.auth()
    
    var authHandle : AuthStateDidChangeListenerHandle?
    var rootInfoCollection : CollectionReference!
    var userIdRef = ""

    
    
    func fetchUserData(){
        db.collection("Users").document("\(userIdRef)").getDocument { document, error in
            // Check for error
            if error == nil {
                // Check that this document exists
                if document != nil && document!.exists {
                    
                    self.userInfo = document.map { (documentSnapshot) -> User in
                        let data = documentSnapshot.data()
                        
                        let uid = data?["uid"] as? UUID ?? UUID()
                        let company = data?["company"] as? String ?? ""
                        let name = data?["name"] as? String ?? ""
                        let admin = data?["admin"] as? Bool ?? false
                        let photo = data?["photo"] as? String ?? ""
                        
                        return User(uid: uid, company: company, name: name, admin: admin, photo: photo)
                    }
                }
            }
        }
        
    }
    
    func listen(){
        handle = authRef.addStateDidChangeListener({ auth, user in
            print(user?.email ?? "No User Found")
            
            if let user = auth.currentUser {
                self.userIdRef = user.uid
                self.rootInfoCollection = Firestore.firestore().collection("/Users/")
                
                DispatchQueue.main.async {
                    self.fetchUserData()
                }
                
                self.signedIn = true
                
            } else {
                self.signedIn = false
            }
        })
        
    }
    
    func signIn(email: String, password: String){
        authRef.signIn(withEmail: email, password: password) { result, error in
            guard result != nil, error == nil else {
                return
            }
        }
    }
    
    func signOut(){
        do {
            try authRef.signOut()
        } catch {
            print(error)
        }
    }
    
    func signUp(email: String, password: String, company: String, name: String, admin: Bool, photo: String){
        authRef.createUser(withEmail: email, password: password) { result, error in
            
            guard result != nil, error == nil else {
                return
            }
            
            let db = Firestore.firestore()
            
            //Success
            db.collection("Users").document("\(result!.user.uid)").setData(["company" : "\(company)", "name" : "\(name)", "admin" : admin, "photo" : "\(photo)", "uid":result!.user.uid]) { error in
                if error != nil {
                    print(error!)
                }
            }
            
        }
    }
    
    
    func unbind() {
        if let handle = handle {
            authRef.removeStateDidChangeListener(handle)
        }
    }
    
}

struct FirstView: View {
    @EnvironmentObject private var appViewModel: AppViewModel

    var body: some View {

        VStack{
            Spacer()
            VStack(spacing: 50){
                
                NavigationLink(destination: Text("Test")){
                    awButton(content: "Request Support", backColor: Color(#colorLiteral(red: 0, green: 0.723585546, blue: 0.9907287955, alpha: 1)))
                        .shadow(color: Color.primary.opacity(0.5), radius: 20, x: 0, y: 20)
                        .rotation3DEffect(Angle(degrees:10), axis: (x: 10.0, y: 0, z: 0))
                }

                NavigationLink(destination: Text("Test")){
                    awButton(content: "Request Quote", backColor: Color(#colorLiteral(red: 0.9372549057, green: 0.3490196168, blue: 0.1921568662, alpha: 1)))
                        .shadow(color: Color.primary.opacity(0.5), radius: 20, x: 0, y: 20)
                        .rotation3DEffect(Angle(degrees:10), axis: (x: 10.0, y: 0, z: 0))
                }

                NavigationLink(destination: Text("Test")){
                    awButton(content: "Ticket Status", backColor: Color(#colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)))
                        .shadow(color: Color.primary.opacity(0.5), radius: 20, x: 0, y: 20)
                        .rotation3DEffect(Angle(degrees:10), axis: (x: 10.0, y: 0, z: 0))
                }
            }
            Spacer()
        }
        .navigationBarBackButtonHidden(true)
        .navigationTitle(appViewModel.userInfo?.company ?? "Test")
        .navigationBarItems(leading: Button(action: {
            appViewModel.signOut()
            
        }) {
            HStack {
                Text("Sign Out")
            }
        },trailing: HStack{
            Image(systemName: "bell")
                .font(.system(size: 30))
//            selectedImageArray.first!
//                .resizable()
//                .scaledToFit()
//                .clipShape(Circle())
//                .frame(width: 50, height: 50)
            Text(appViewModel.userInfo?.name ?? "Tester")
                .font(.system(size: 20))
        })
    }
}
RapsIn4
  • 91
  • 5
  • "I'm trying to figure out how to avoid this delay from happening." I haven't fully read your code, but I expect that the delay comes from network traffic (either the call to sign-in, or the loading of data from Firestore). If that is the case, there is no way to get rid of the delay - but you can of course show a loading indicator until the network calls have completed. – Frank van Puffelen Sep 05 '21 at 19:11
  • Your second issue should be split off into a separate question so that it can be addressed independently. – jnpdx Sep 05 '21 at 19:15

1 Answers1

1

Your navigation state depends on signedIn. In your auth listener, you do this:


DispatchQueue.main.async {
  self.fetchUserData()
}
                
self.signedIn = true

This will set signedIn to true, which will change your navigation state and then at an indeterminate time in the future, fetchUserData will finish and update the user data (once the network call has completed).

To avoid this delay (or, really, to just avoid seeing the information not update on the screen -- the delay is an inevitability of the network call), don't set signedIn until fetchUserData completes. So, remove the line inside the listener and instead, set it after your self.userInfo = line.

jnpdx
  • 45,847
  • 6
  • 64
  • 94