0

I have a dynamic array (named "items") of users I follow:

["user5@t.co", " user6@t.co"]

and I'm essentially retrieving these user's posts using:

for i in 0..< items.count{
                
                db.collection("users").document("\(items[i])").collection("posts").addSnapshotListener { (querySnapshot, error) in
                    guard let documents = querySnapshot?.documents else {
                        print("No documents")
                        return
                    }

                    self.posts = documents.map { QueryDocumentSnapshot -> Post in
                        let longitudeVar = data["longitude"] as? String ?? ""
                        let latitudeVar = data["latitude"] as? String ?? ""
                      

                        return Post(id: .init(), longitudeVAR: longitudeVAR, latitudeVAR: latitudeVAR)
                    }
                }
            }

I'm trying to draw information from both users at the same time but the issue I'm having is that this only draws post information (longitudeVar & latitudeVar) for ONE user OR the other- and it seems to randomly pick between user5@t.co and user6@t.co. Any suggestions? Also I apologize if this is a basic question or if my code isn't well written- I'm just trying to get this to work. Thanks!

psettle41
  • 31
  • 5
  • 1
    `addSnapshotListener` works asynchronously. In a loop you need `DispatchGroup`. And please don't use those *stone-age-loops* in Swift if you actually don't need the index – vadian Dec 06 '20 at 08:20
  • 1
    *stone-age-loops* @vadian gave me a good chuckle. – Jay Dec 06 '20 at 14:18
  • The code is vague and we have no idea why your doing what you're doing. It seems all of your users are stored with an email address as the documentId? That's going to be problematic if the user changes their email address as you'll have to change every reference to it in your entire database. Better bet is to store users with the documentId being their user uid and then store their email address as a field within. Is the goal to read the coordinates from two different users? If so, will there ever be more than two users (which using an array would be for - an unknown #)? Can you clarify? – Jay Dec 06 '20 at 14:22
  • Also, as mentioned above, you cannot `return` data in that fashion from a Firebase function because they are asynchronous. However, I don't know that implementing DispatchGroups is the best solution either. Need more clarity in the question to make that determination. – Jay Dec 06 '20 at 14:26
  • @Jay thanks for the comments- I apologize for the lack clarity. Each user has a list of people they follow (in this case its "user5@t.co" and "user6@t.co"). I am using this code to draw data about these users' posts to create a feed. This array is subject to change as the user could follow/unfollow other accounts. To answer your question: yes, the goal is to read the coordinates from two different users. As it exists now, my code will only read the coordinates from one user or the other- not both. does that help at all? thanks again for the response. – psettle41 Dec 06 '20 at 19:40
  • There's a super good answer from @bsod - but as a followup question, for the users this user is following, can you write data to their document? e.g. this user has a list of who they are following; can the users they are following have a list of followed by (users who are following them)? – Jay Dec 07 '20 at 19:46

1 Answers1

1

If you want to loop database fetches and coordinate their returns into a single task then you should use a DispatchGroup. It's a very simple API that will simply record how many calls you make to Firestore and give you a completion handler to execute when that many returns eventually come in (at the end of the last one).

Don't use a snapshot listener here because those are for streams of data—all you want is a one-time document grab. If there are lots of documents to parse and they are relatively big then I would consider using a background queue so the UI doesn't stutter when this is going on.

var posts = [Post]()
let dispatch = DispatchGroup() // instantiate dispatch group outside loop

for item in items {
    dispatch.enter() // enter on each iteration
    
    // make a get-documents request, don't add a continuously-updating snapshot listener
    db.collection("users").document(item).collection("posts").getDocuments { (querySnapshot, error) in
        if let documents = querySnapshot?.documents {
            // fill a local array, don't overwrite the target array with each user
            let userPosts = documents.map { QueryDocumentSnapshot -> Post in
                let longitudeVar = data["longitude"] as? String ?? ""
                let latitudeVar = data["latitude"] as? String ?? ""
                
                return Post(id: .init(), longitudeVAR: longitudeVAR, latitudeVAR: latitudeVAR)
            }
            
            self.posts.append(userPosts) // append local array to target array
        } else if let error = error {
            print(error)
        }
        
        dispatch.leave() // always leave, no matter what happened inside the iteration
    }
}

/* this is the completion handler of the dispatch group
 that is called when all of the enters have equaled all
 of the leaves */
dispatch.notify(queue: .main) {
    self.tableView.reloadData() // or whatever
}
trndjc
  • 11,654
  • 3
  • 38
  • 51
  • thanks for the comment- it makes a lot of sense doing it that way. I gave your code a try and I am still having the same issue when appending the data to the target array; it's only retrieving the data from one item- not both. Any suggestions? thanks again for the help! – psettle41 Dec 06 '20 at 19:34
  • Then you have a data problem. Make sure the data exists in the db and that you’re fetching the right subcollection. – trndjc Dec 06 '20 at 19:43