0

the following is printing "no user with username" but is printing retVal as "false" ( I changed function to a string just for troubleshooting, ideally this should be bool ) I am new to swift and this is absolutely driving me crazy. it is making it to the chunk of code where retVal would get reassigned, but it isn't reassigning it

static func isUserNameUnique(_ username : String) -> String {

    var retVal = "false"

    let db = Firestore.firestore()
    let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
    newQuery.getDocuments { (document, error) in
        if document!.isEmpty {
            retVal = "true"
            print("No user with username")
        }
    }
    print("\(retVal)")
    return retVal
}


func validateFields() -> String? {

    //Check that all fields are filled in
    if premierCodeTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" || userNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
        return "Please fill in all fields."
    }

    //Check unique username
    let cleanedUserName = userNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
        Utilities.isUserNameUnique(cleanedUserName) { res in
        if !res {
            // return "please choose a unique username"
        }
    }

    return nil
}
pkamb
  • 33,281
  • 23
  • 160
  • 191

3 Answers3

1

You need a completion as the request is asynchnous , plus use Bool instead of a String

static func isUserNameUnique(_ username : String,completion:@escaping((Bool) ->())) {

        let db = Firestore.firestore()
        let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
        newQuery.getDocuments { (document, error) in 
            completion(document!.isEmpty)  
        } 
 }

Call

Utilities.isUserNameUnique { res in
    if !res { 
       // name exists
    }
}
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
  • calling if Utilities.isUserNameUnique(cleanedUserName, completion: false) { --- error: Cannot convert value of type 'Bool' to expected argument type '(Bool) -> ()' – rickdariendo Dec 26 '19 at 17:09
  • no call it as in edit – Shehata Gamal Dec 26 '19 at 17:10
  • I want to return statement, I am getting an error Unexpected non-void return value in void function. if document isEmpty I want to continue with the code, if it isn't empty I simply want to return "choose a unique username" – rickdariendo Dec 26 '19 at 17:15
  • if res {return "please choose a unique username"} – rickdariendo Dec 26 '19 at 17:16
  • I appreciate the help, but that isn't working. I don't need an if, I just need the else. and I just need to return text, and I am getting the unexpected non-void return value in void function error. the Firestore code queries the db if there's anyone with the new username. if it finds someone with the username, I just want to return "please pick a new username" – rickdariendo Dec 26 '19 at 17:21
  • if the function can return false, or any static value, and I can say if Utilities.isUsernameUnique(username) == false/static Value {return "pick a new name"} – rickdariendo Dec 26 '19 at 17:23
  • 2
    Please add the core where you are trying to use this method. You are getting confuse with asynchronous vs synchronous. You cannot do `Utilities.isUsernameUnique(username) == something` because you cannot retrieve the value of `isUsernameUnique` synchronously. – Gustavo Conde Dec 26 '19 at 17:26
  • @rickdariendo you can't the request is asynchnous – Shehata Gamal Dec 26 '19 at 17:27
1

You are trying to return a value synchronously while using and asynchronous method.

When you do newQuery.getDocuments execution continues without waiting for completion handler to be called. So after that line is executed, the return is executed, and THEN the completion handler gets called.

If you want to get a value from an asynchronous method, you need to create a method that takes a completion handler like the answer Khan gave you.

static func isUserNameUnique(_ username: String, completionHandler: @escaping (Bool) -> Void) {
    let db = Firestore.firestore()
    let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
    newQuery.getDocuments { (document, error) in
       completionHandler(document!.isEmpty)
    }
}
Gustavo Conde
  • 927
  • 12
  • 20
  • what are the values that get returned, because I can't call if Utilities.isUsernameUnique(username) == false. forgive me for the lack of knowledge – rickdariendo Dec 26 '19 at 17:26
  • As I wrote in the other answer, to get the value of `Utilities.isUsernameUnique(username)` you need to do an asynchronous call `(getDocuments)`. So you can't use it the way you want. You have to use it like `Utilities.isUsernameUnique(username) { result in //user bool value here }` – Gustavo Conde Dec 26 '19 at 17:32
  • I commented before noticing your other response, sorry about that. I can print to console and it seems to work but now I don't understand how I can get anything out of the if !res block. I can't assign a variable and call it outside of the statement, or return the string I want to – rickdariendo Dec 26 '19 at 17:36
  • That's why I told you to post the code where you want to use this method. – Gustavo Conde Dec 26 '19 at 17:48
  • in the edit @gustavo – rickdariendo Dec 26 '19 at 18:00
  • You should also add where you use `validateFields`, because that method will also become asynchronous. – Gustavo Conde Dec 27 '19 at 15:43
0

It's impossible to achieve what you want since newQuery.getDocuments isn't returning value instantly. It will answer you at some point by calling function that you passed to it.

Your code can be described as

func foo() -> String {
    // set retVal to "false"
    var retVal = "false"

    // create query
    let db = Firestore.firestore()
    let newQuery = db.collection("users").whereField("userName", isEqualTo: username)

    // ask query to evaluate
    newQuery.getDocuments { (document, error) in
        // at some point probably after foo ends
        if document!.isEmpty {
            // if document is not empty set retVal to "true" (at this point there is no-one that could look at value of retVal)
            retVal = "true"
            print("No user with username")

        }
    }
    // while query is evaluating in background 
    // print retVal (probably still "false")
    print("\(retVal)")
    // return retVal (probably still "false")
    return retVal
}

Now let's fix your problem.

Solution could be:


class X {
    private var document: <insert correct type here>? {
        didSet {
            // do what you want with document
        }
    }

    func foo() {
        let db = Firestore.firestore()
        let newQuery = db.collection("users").whereField("userName", isEqualTo: username)

        newQuery.getDocuments { 
            [weak self] (document, error) in // [weak self] is important!
            // I have no idea on which thread firebase runs it's callback
            // It's important that only one thread could modify self.document
            // otherwise you will have race condition and a lot of strange behaviours
            DispatchQueue.main.async {
                self?.document = document;
            }
        }
    }

}

If you really need to create func foo() -> String and you don't care that your thread will have to wait (UI will not respond, you will have 0 fps etc) you can do it using NSLock (I won't post code since it's really bad idea in most of the cases).

Pikacz
  • 431
  • 1
  • 5
  • 10