0

I am designing an app where when the user first opens it AND they are currently logged in, I want it to take a snapshot of their data and then instantiate a view controller based on the criteria.

In my example, if the user is a "driver" meaning the value in firebase is "true" then I want them to go to the "Driver" view controller. If the user is NOT a "driver" then I want them to go to the "User" view controller (TabBarVC). If the user is nil or not logged in, then they go to the login VC.

I have tested the code without the condition. If the user is logged in (no snapshot) then it goes to TabBarVC, if they are logged out, it goes to Login VC.

When running the app with the condition, the app crashes and says "Application windows are expected to have a root view controller at the end of application launch" which to me means it is not reading my code when using the condition from the FireBase snapshot.

Please let me know what I am doing wrong, I thought that "rootVC" would work because its a variable in the form of "var rootVC : UIViewController?"

Here is switcher.swift

import Foundation
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase

class Switcher {
    static func updateRootVC() {

        var rootVC : UIViewController?

        let currentUser = Auth.auth().currentUser?.uid

        if Auth.auth().currentUser != nil{
            DataService.instance.REF_USERS.child(currentUser!).observeSingleEvent(of: .value, with: { (snapshot) in
                if let userSnapshot = snapshot.children.allObjects as? [DataSnapshot] {
                    for user in userSnapshot {
                            if user.childSnapshot(forPath: "driver_profile/is_userdriver").value as? Bool == false {
                                rootVC = UIStoryboard(name: "Driver", bundle: nil).instantiateViewController(withIdentifier: "DriverHomeVC") as! DriverHomeVC
                            } else {
                                rootVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "TabBarVC")
                                return
                        }
                    }
                }
            })
         // rootVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "TabBarVC")
         // rootVC = UIStoryboard(name: "Driver", bundle: nil).instantiateViewController(withIdentifier: "DriverHomeVC") as! DriverHomeVC

        }else{
            rootVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginVC") as! LoginVC

        }

        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        appDelegate.window?.rootViewController = rootVC

    }
}

Here is part of AppDelegate.swift incase you need to see how this is called:

import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
import FirebaseInstanceID


@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    override init() {

        FirebaseApp.configure()
        Database.database().isPersistenceEnabled = true
    }

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {



        Switcher.updateRootVC()

        return true
    }
Greg
  • 67
  • 7
  • Take a look at [Firebase Async](https://stackoverflow.com/questions/38550634/swift-firebase-return-in-asynchron-task/38551937#38551937) and this older but still relevant answer [ViewController loads early](https://stackoverflow.com/questions/36667897/view-controller-loads-before-data-is-pulled-from-firebase-login-swift/36677094#36677094) – Jay Oct 28 '18 at 13:00
  • @Jay I will check this out. – Greg Oct 29 '18 at 02:17

1 Answers1

1

I haven't used FireBase, but the method you're using, observeSingleEvent(of:with:) is almost certainly an async event. It returns immediately, before the database read is complete. You pass in a closure (a block of code) and the call runs that code once the fetch is complete.

With async calls, the code you pass in will never run by the time the function call returns and you go on to the next step in your app.

Thus you can't do what you're trying to do. Your rootVC variable is going to be nil when you get to the appDelegate code after the if/else block.

You need to find a different way to do what you are doing.

I'd suggest creating a root view controller that's the same type in all cases, and giving it a container view that gets loaded with a child view of the appropriate type once the database fetch is complete.

EDIT:

Create a view controller. Call it ContainerViewController. Install it as the root view controller of the window at launch. Give that view controller a container view. Set up that view controller's viewDidLoad() to call the firebase call, and in the completion handler of the firebase call, decide what type of view controller to instantiate, and install that view controller in the container view.

Community
  • 1
  • 1
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • could you elaborate on how to do that? I have code from a container VC but I am not using a slide out “hamburger” menu. – Greg Oct 27 '18 at 20:04
  • @Greg This answer is spot on. I would suggest another solution and let me clarify. The statement *return true* in the *app:didFinishLaunching* function executes *before* the root controller is established. In other words, it's called before *appDelegate.window?.rootViewController = rootVC* runs. Another option is to return true from within the Firebase closure *after* the rootController is loaded. It'll have to be recoded a bit but it could follow this statement *appDelegate.window?.rootViewController = rootVC*. – Jay Oct 28 '18 at 12:55
  • @Jay Jay, thank you for the suggestions. I did actually do return true under both of the closure statements but it only took the first one. What I mean, In my code you will see I am looking for a false value, and therefore to instantiate the view controller based on false. When I went into firebase and made that value true it still executed the first value because that’s were return was. I will rework the code with the returns. I assume you mean I should have a return under both of the if/else. – Greg Oct 29 '18 at 02:14