swiftfirebasegoogle-cloud-firestorepromisekit

Firebase Firestore fetching data with an ID reference then fetching the reference


After searching for a few hours for the answer to this question, I have found 1 post that was similar here: However I tried to replicate but I believe the difference in language syntax made it very hard to translate.

Within my application, users are allowed to make posts, the structure for the post in Firsestore looks like this:

enter image description here

The creator is a userId of a user that also lives in the database.

I am aware of how to fetch things from Firestore when my structs conform to Codable and they map 1 to 1 but I have not experienced having to fetch nested data after an initial fetch.

Question By querying my backend for posts, how can I also create the user object that lives inside?

Here is the post object I was expecting to create:

import FirebaseFirestoreSwift

public struct Post: Codable {
    /// The school id
    @DocumentID var id: String?
    /// The name of the school
    public let content: String
    /// The user who made the post
    public var creator: AppUser?
}

I want to create appUser from the creator field that is returned. Should I build the content and then have some sort of promise.then to fetch the user? Or can i do both at the same time?

Here is what I think I should be doing

    public func fetch(for schoolId: String) -> Promise<[Post]> {
    return Promise { resolver in
        fireStore
            .collection("schools").document(schoolId)
            .collection("posts").getDocuments { (querySnapshot, err) in
                guard let documents = querySnapshot?.documents else {
                  resolver.reject(Errors.firebaseError)
                  return
                }
                
                let posts = documents.compactMap { queryDocumentSnapshot -> Post? in
                  return try? queryDocumentSnapshot.data(as: Post.self)
                }
                
                let postsWithUser: [Post] = posts.map { post in
                    //Fetch User and return an updated struct
                }
                resolver.fulfill(postsWithUser)
            }
    }
}

Solution

  • I solved it! Basically, we want to let the first fetch complete. Then we iterate through each post.id and call FetchUser() i which is a function i built that returns Promise<User>

    func fetchTopLevelPost(for schoolId: String) -> Promise<[Post]> {
        return Promise { resolver in
        fireStore
            .collection("schools").document(schoolId)
            .collection("posts").getDocuments { (querySnapshot, err) in
                guard let documents = querySnapshot?.documents else {
                  resolver.reject(Errors.firebaseError)
                  return
                }
    
                let posts = documents.compactMap { queryDocumentSnapshot -> Post? in
                  return try? queryDocumentSnapshot.data(as: Post.self)
                }
                resolver.fulfill(posts)
            }
        }
    }
    
    func fetchPostUser(for posts: [Post]) -> Promise<[Post]> {
        let allPromise = posts.map{ FetchUser().fetch(for: $0.creatorId) }
        return Promise { resolver in
            when(fulfilled: allPromise).done { users in
                let completePost = zip(users, posts).map(post.init(completeData:))
                resolver.fulfill(completePost)
            }
            .catch { error in
                resolver.reject(Errors.firebaseError)
            }
        }
    }
    

    Here is the callsite:

    public func fetch(for schoolId: String) -> Promise<[Post]> {
        return fetchTopLevelPost(for: schoolId)
            .then { self.fetchPostUser(for: $0) }
    }