2

I'm facing an issus with the Firebase Snapshot. I conntected my Fierbase account succesfully with my Xcode project. I am capable to change data in my Firestore cloud. But I can't read it. Here is my function:


class UserService{
static func CheckIfMoreThanOneUserIsLoggedIn() -> Bool{
    var BoolenToReturn:Bool?
    let AuthUser = Auth.auth().currentUser
    let userId = AuthUser!.uid
    let localDeviceString = Constants.UserDefaultsStorage.string(forKey: userId)
    // LocalDeviceString is generated (20 random letters) when the user is logged in succesfully.
    //This DeviceString would be stored on one hand in the cloud on the other hand on the device.
    //When the user open the Application it checks if the localDeviceString of the cloud is the same as the String stored on the device.
    //I’m able to denie the user access through multiple devices simultaneously (-> Any imporvent is desired)
    let newUserReference = Firestore.firestore().collection("users").document(userId)
    newUserReference.getDocument {
        (querySnapshot, err) in // Code never runs the closure
        guard err != nil else {
            return
        }
        for document in querySnapshot!.data()! {
            if document.key == "RandomString" {
                let docValue:String = document.value as! String
                if docValue == localDeviceString{
                    BoolenToReturn = true
                }
                else{
                    BoolenToReturn = false
                }
            }
        }
    }

    return BoolenToReturn  ?? false
}
}

Edit

Based on @Frank van Puffelen's links I was able to make my own solution. But the program isn't as professional as I want because now it is a function defined in the LaunchViewController. In addition I'm calling the segue to the HomeViewController in the closure of newUserReference.getDocument . What I wan't to achieve: A function that is defined in the UserService Class which returns a bool if the user is allowed to use the app.

Bruno Eigenmann
  • 346
  • 3
  • 16
  • 1
    Data is loaded from Firestore asynchronously. This means that by the time that your `return BoolenToReturn ?? false` runs, the `for document in querySnapshot!.data()! ...` hasn't been called yet. For more on this, see https://stackoverflow.com/questions/52244924/why-i-couldnt-assign-fetched-values-from-firestore-to-an-array-in-swift/52245358#52245358 and many of these https://stackoverflow.com/search?tab=votes&q=%5bgoogle-cloud-firestore%5d%5bswift%5d%20asynchronous%20completion%20handler – Frank van Puffelen Apr 15 '20 at 22:37
  • Thanks for the reference. I'll defenitly check it out. It seem like you know a lot about firebase. Do you have any idea how to improve the algorithm to denie the user access through multiple devices simultaneously? – Bruno Eigenmann Apr 15 '20 at 23:28
  • `newUserReference.getDocument {` <- that is a closure. So when you say you were not able to achieve it with closure, what does that mean as you have closures in the code in the question? What specifically isn't working? Maybe you should ask a different question as the issue @FrankvanPuffelen helped you resolve is unrelated to your followup question about 'awful' looking code. Also, we are not a code writing service but can help with existing code. – Jay Apr 16 '20 at 18:46
  • I guess that was a misunderstanding. I described the situation more accurate in the "edit". Thanks for mention it. – Bruno Eigenmann Apr 16 '20 at 19:56

1 Answers1

1

I think you may want to investigate a completion handler. The idea is to call an asynchronous function and then perform a task when that function has completed. The below code can almost be directly applied as a solution to your question.

So here's an example you can start with. Suppose we have an app with users stored in a users collection

users
   uid_0
      name: "Steve"
   uid_1
      name: "Danno"
   uid_2
      name: "Junior"
   uid_3
      name: "Tani"

and a new user wants to sign up, but the app doesn't allow for duplicate names. So we need to query our users collection to determine if the name is already in use. Here's the code that calls the query function.

self.doesUserNameExist(userName: "Steve" ) { doesExist in
    if doesExist == true {
        print("yep, it exists")
    } else {
        print("Nope - it's not there")
    }
}

print("this code will execute before the above code")

That will print yep, it exists

and here's the actual function that performs the query. Note how we're using an completion handler thats @escaping the closure when it's done.

//determine if a user name exists in Firestore, return true if it does, false if not
func doesUserNameExist(userName: String, completion: @escaping( (Bool) -> Void) ) {
    let usersCollection = self.db.collection("users")
    usersCollection.whereField("name", isEqualTo: userName).getDocuments(completion: { querySnapshot, error in
        if let err = error {
            print(err.localizedDescription)
            return
        }
        
        guard let docs = querySnapshot?.documents else { return }
        
        if docs.count > 0 {
            completion(true)
        } else {
            completion(false)
        }
    })
}

This is the typical way to 'return' data from an asynchronous function as it allows the function to execute, the data to be valid and then pass that data back to the calling function in an asynchronous way.

Note that any code following the calling function, like this

print("this code will execute before the above code")

Will execute before the code in the function closure.

EDIT

This above called is called like this

self.doesUserNameExist(userName: "Jay", completion: { doesItExist in
    print(doesItExist)
})
Jay
  • 34,438
  • 18
  • 52
  • 81