0

I'm currently dealing with this problem. I have Firestore database. My goal is to fill friends array with User entities after being fetched. I call fetchFriends, which fetches currently logged user, that has friends array in it (each item is ID of friend). friends array is then looped and each ID of friend is fetched and new entity User is made. I want to map this friends array to friends Published variable. What I did there does not work and I'm not able to come up with some solution.

Firestore DB

User
- name: String
- friends: [String]

User model

struct User: Identifiable, Codable {   
    @DocumentID var id: String?
    var name: String?
    var email: String
    var photoURL: URL?
    
    var friends: [String]?
}

User ViewModel

@Published var friends = [User?]()

func fetchFriends(uid: String) {
    
    let userRef = db.collection("users").document(uid)
    
    userRef.addSnapshotListener { documentSnapshot, error in
        
        do {
            guard let user = try documentSnapshot?.data(as: User.self) else {
                return
            }
            
            self.friends = user.friends!.compactMap({ friendUid in
                
                self.fetchUserAndReturn(uid: friendUid) { friend in
                    return friend
                }
            })
        }
        catch {
            print(error)
        }
    }
}

func fetchUserAndReturn(uid: String, callback:@escaping (User)->User) {
    let friendRef = db.collection("users").document(uid)
    
    friendRef.getDocument { document, error in
        callback(try! document?.data(as: User.self) as! User)
    }
}
6
  • Where exactly in this chain of events is it breaking? Put print statements everywhere and confirm that the document is actually downloaded, that the User object is actually initialized, that the array is actually looped, etc. Somewhere along the way, it will print incorrectly and let us know where that is. Commented May 25, 2021 at 16:42
  • @MJ-12 Great suggestion. I would also add a breakpoint and step through your code line by line, examining the vars along the way. Once you find one that isn't what you expect, update that question with what line it was and what the expected value was supposed to be. Commented May 25, 2021 at 17:02
  • Isn't the primary issue here that self.friends is expecting to get populated by repeating calls to fetchUserAndReturn which is also asynchronous (or, specifically, the getDocument function within it is) and won't return a User to the compactMap like expected? Commented May 25, 2021 at 17:04
  • on line self.fetchUserAndReturn I now get this error message: "Cannot convert value of type '()' to closure result type 'User??'" Commented May 25, 2021 at 17:29
  • @jnpdx this could be it. Do you have any idea how to make it work? I tried to append to the self.friends array each time i got a result back, but it worked wierdly in view (because of appending to the array I had to clear the self.friends each time new change was made in database and because of that, every reload of view cleared all the content first and then filled it with subviews - it didn’t look well) Commented May 25, 2021 at 17:44

1 Answer 1

2

One option is to use DispatchGroups to group up the reading of the users but really, the code in the question is not that far off.

There really is no need for compactMap as user id's are unique and so are documentId's within the same collection so there shouldn't be an issue where there are duplicate userUid's as friends.

Using the user object in the question, here's how to populate the friends array

func fetchFriends(uid: String) {
    let userRef = db.collection("users").document(uid)
    userRef.addSnapshotListener { documentSnapshot, error in
        guard let user = try! documentSnapshot?.data(as: User.self) else { return }

        user.friends!.forEach( { friendUid in
            self.fetchUserAndReturn(uid: friendUid, completion: { returnedFriend in
                self.friendsArray.append(returnedFriend)
            })
        })
    }
}

func fetchUserAndReturn(uid: String, completion: @escaping ( MyUser ) -> Void ) {
    let userDocument = self.db.collection("users").document(uid)
    userDocument.getDocument(completion: { document, error in
        guard let user = try! document?.data(as: User.self) else { return }
        completion(user)
    })
}

Note that I removed all the error checking for brevity so be sure to include checking for Firebase errors as well as nil objects.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.