0

I'm trying to obtain a document from my Firestore Database and filter the returned data by what the user selects. If the user selects ["snapchat", "facebook"] then I'm trying to only get these specific values from the database. It should return ["snapchat": username, "instagram": username, "UID": user UID]. However, when I print out the return value of the function, it only contains the key-value is for "UID". Here is my code:

func getMediaInfo(selected: [String]) -> Dictionary<String, String> {
    var mediaDict: [String: String] = [:]
    let db = Firestore.firestore()
    let docRef = db.collection("users").document(Auth.auth().currentUser!.uid)
    docRef.getDocument { (document, error) in
        if let document = document, document.exists {
            let dataDescription = document.data()
            for media in selected {
                print(media, ":", dataDescription![media]!)
                mediaDict[media] = (dataDescription![media]! as! String)
            }
        } else {
            print("Document does not exist")
            self.dismiss(animated: true, completion: nil)
        }

    }
    mediaDict["UID"] = Auth.auth().currentUser?.uid
    print("Dictionary: ", mediaDict)
    return mediaDict
}

My output is :

Dictionary: ["UID": "ub8bUMYBbeSPXd8UOdJWKCnbQzJ2"]
Twitter : nrjfjfj
Phone Number : nrnrjrn
Snapchat : hello

As you can see the values are retrieved and outputted properly. However, the mediaDict on return only has the key value pair for "UID". It also seems like the function is returning before the entire dictionary is being filled up. I'm not sure why this would happen and I'm not sure how I can fix this.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
nilay neeranjun
  • 145
  • 2
  • 14

1 Answers1

1

Data is loaded from Firestore (and most modern web, and cloud APIs) asynchronously. By the time your return mediaDict runs, the mediaDict[media] = (dataDescription![media]! as! String hasn't been called yet, which is why you're not seeing any of its contents.

Any code that needs the data from the database, needs to be inside the closure/callback, or be called from there. For example:

docRef.getDocument { (document, error) in
    var mediaDict: [String: String] = [:]
    mediaDict["UID"] = Auth.auth().currentUser?.uid
    if let document = document, document.exists {
        let dataDescription = document.data()
        for media in selected {
            print(media, ":", dataDescription![media]!)
            mediaDict[media] = (dataDescription![media]! as! String)
        }
    } else {
        print("Document does not exist")
        self.dismiss(animated: true, completion: nil)
    }
    print("Dictionary: ", mediaDict)
}

You'll note that there is no return statement here, since you can't return something that hasn't loaded yet.


The alternative to having the code inside the closure/callback, is to pass in a completion handler of your own to getMediaInfo and call that once the data is loaded (where we now have print("Dictionary: ", mediaDict)).

This'd looks something like:

func getMediaInfo(selected: [String], completionHandler: (Dictionary<String, String>) -> ()) {
    let db = Firestore.firestore()
    let docRef = db.collection("users").document(Auth.auth().currentUser!.uid)
    docRef.getDocument { (document, error) in
        var mediaDict: [String: String] = [:]
        mediaDict["UID"] = Auth.auth().currentUser?.uid
        if let document = document, document.exists {
            let dataDescription = document.data()
            for media in selected {
                print(media, ":", dataDescription![media]!)
                mediaDict[media] = (dataDescription![media]! as! String)
            }
        } else {
            print("Document does not exist")
            self.dismiss(animated: true, completion: nil)
        }
        completionHandler(mediaDict)
    }
}


getMediaInfo(["one", "two"]) { info in
    print(info)
}

You'll note that this is a very similar pattern to what the Firestore APIs use themselves, you're just returning a more application-specific version of the data.

Also see:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thank you! What would I put in the completion handler argument when calling my function? I'm not too sure how these work – nilay neeranjun Apr 12 '20 at 02:24
  • Added an example of that. As said: it's quite similar to the call to `getDocument` itself, which also has a similar closure/callback/completion handler. So when in doubt, just search for similar examples based on these terms (which is what i just did to create the list of links). – Frank van Puffelen Apr 12 '20 at 02:38