4

I want my method to wait until Firebase request finished

uploadSingUpInfo returns before the Firebase request finishes, and this is a problem for me - some method returns nil.

static func uploadSingUpInfo(fullName:String,email:String,password:String)->String{
    rootRef = FIRDatabase.database().reference()
    var returnVlue="not valid"

    FIRAuth.auth()?.createUserWithEmail(email, password: password) { (user, error) in
        if (error != nil){
            returnVlue=(error?.userInfo["error_name"]) as! String
        }
        else{

            let newUser = [
                "username": fullName
            ]
            rootRef.childByAppendingPath("User")
                .childByAppendingPath((user?.uid)!).setValue(newUser)
            NSUserDefaults.standardUserDefaults().setBool(true, forKey: "isLogin")
            NSUserDefaults.standardUserDefaults().setObject(email, forKey: "email")
            NSUserDefaults.standardUserDefaults().setObject(user?.uid, forKey: "user_ID")
            print(NSUserDefaults.standardUserDefaults().objectForKey("user_ID"))
            returnVlue="valid"

        }
    }

    return returnVlue

}
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Sam
  • 243
  • 3
  • 12
  • 1
    Don't use Firebase as functions that return values - it goes against it's asynchronous nature. You should plan your code to allow Firebase to perform it's task and then within the closure (block) go to the next step. For example, in your code remove the returns and within the createUserBlock, as the last line instead of return, call you next function to update your UI. – Jay Aug 28 '16 at 11:39

3 Answers3

5

Don't use Firebase as functions that return values - it goes against it's asynchronous nature.

Plan code structure that allows Firebase to perform it's task and then within the closure (block) go to the next step.

For example: In your code, change the function to not return anything and within the createUserBlock, as the last line instead of return, call the next function to update your UI.

static func uploadSingUpInfo(fullName:String,email:String,password:String) {

    rootRef = FIRDatabase.database().reference()

    FIRAuth.auth()?.createUserWithEmail(email, password: password) { (user, error) in
        if (error != nil){
            showUserAnError(error)
        } else {

            let newUser = [
                "username": fullName
            ]
            rootRef.childByAppendingPath("User")
                .childByAppendingPath((user?.uid)!).setValue(newUser)
            NSUserDefaults.standardUserDefaults().setBool(true, forKey: "isLogin")
            NSUserDefaults.standardUserDefaults().setObject(email, forKey: "email")
            NSUserDefaults.standardUserDefaults().setObject(user?.uid, forKey: "user_ID")
            print(NSUserDefaults.standardUserDefaults().objectForKey("user_ID"))
            continueLoginProcess()  //reload the ui or whatever step is next
        }
      }
    }
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Jay
  • 34,438
  • 18
  • 52
  • 81
4

In your code, the method returns before the firebase request completes so the value of returnVlue will not be set.

Normal Functions don't wait for async functions to finish running ;- because that's kind of the point of an async function. isn't it?

So if you want your uploadSingUpInfo to wait until createUserWithEmail has finished, then you have to make createUserWithEmail an async function as well by using completionHandlers:

func uploadSingUpInfo(fullName: String, email: String, password: String, completion: (result: String) -> Void) {
    FIRAuth.auth()?.createUserWithEmail(email, password: password) {
     (user, error) in
       if (error != nil){
           returnVlue=(error?.userInfo["error_name"]) as! String
       }
       else{

           let newUser = [
               "username": fullName
           ]
           rootRef.childByAppendingPath("User")
            .childByAppendingPath((user?.uid)!).setValue(newUser)
           NSUserDefaults.standardUserDefaults().setBool(true, forKey: "isLogin")
           NSUserDefaults.standardUserDefaults().setObject(email, forKey: "email")
           NSUserDefaults.standardUserDefaults().setObject(user?.uid, forKey: "user_ID")
           print(NSUserDefaults.standardUserDefaults().objectForKey("user_ID"))
           returnVlue="valid"

       }

       completion(returnVlue);
    }
}

And this is how you would call this method :

uploadSingUpInfo(<arguments>) {
    (result: String) in             //this is what you pass to completion handler.
     print("got back: \(result)")
}

See this article for a better idea.

ShahiM
  • 3,179
  • 1
  • 33
  • 58
  • did you mean a dictionary? Well, you have to ensure that you call completion only after all the code that is required to generate the return value has been executed. – ShahiM Aug 29 '16 at 08:44
1

Instead of returning a value from that function. You can call another function inside the firebase method at the "very end" of the method. This will ensure that whatever function you need to do next, is executed at the end when Firebase is done processing your request

FIRAuth.auth()?.createUserWithEmail(email, password: password) { (user, error) in
        if (error != nil){
            returnVlue=(error?.userInfo["error_name"]) as! String
        }
        else{

            let newUser = [
                "username": fullName
            ]
            rootRef.childByAppendingPath("User")
                .childByAppendingPath((user?.uid)!).setValue(newUser)
            NSUserDefaults.standardUserDefaults().setBool(true, forKey: "isLogin")
            NSUserDefaults.standardUserDefaults().setObject(email, forKey: "email")
            NSUserDefaults.standardUserDefaults().setObject(user?.uid, forKey: "user_ID")
            print(NSUserDefaults.standardUserDefaults().objectForKey("user_ID"))
            returnVlue="valid"

        }
callAnotherFunctionWhenFinished()
    }
Tommy
  • 979
  • 8
  • 15