0

I'm using Firebase Firestore in my SwiftUI app project. At the start of the app, I want to read multiple documents from various collections.

Let's say I have collection A (1 document), B (2 documents) and C (4 documents). The data in the documents from A and B determine which documents from C I have to read.

Currently, I have a DatabaseHelper class which has a readCollection function for each collection (each of these readCollection functions call readDocument functions to create a listener for each required document). Each readCollection function as well as each readDocument function has a completion handler. At the start of the app, I start with reading collection A. After completing the read, it then starts reading B, and so on:

func listen(completion: @escaping () -> ()) {
  readA() {
    readB() {
      readC() {
        completion()
      }
    }
  }
}

My app is waiting for the listen function to complete before displaying any data.

This approach is 'kind of working', HOWEVER, it does not seem to be the optimal approach. For example, if I read A, then I read both documents from B, the completion handler from B is called twice, which results in two calls for readC(), unless I count the document reads for B and only call the completion after the last read. THEN, the problem is, that only an update to the second document in B triggers the completion handler and reloads documents from C, but not an update to the first document in B.

Surely, there has to be a better logic to load my data from the database at the start of my app, which handles each document update during runtime correctly and without unneccessary double-reads.

Does anyone have a better approach? Thanks

ediheld
  • 213
  • 2
  • 12

1 Answers1

1

So here is a possible solution. You'll have to modify it to your needs but as you provided little to no code for your issue it has to do.

  1. Get your data from A
  2. When that data arrives use it to determine what data to get from B. Use a completion handler for that. Other solutions could be and are not limited to (Dispatcher etc.).
  3. Determine what data to get from C with the data provided from B. Same procedure as in Nr.2 to wait for it.

This is just a basic representation of a potential procedure of your problem. It is oversimplified but should help you understand:

class DatabaseHelper: ObservableObject {
    
    @Published var myDataA: Array<String> = []
    @Published var myDataB: Array<String> = []
    @Published var myDataC: Array<String> = []
    
    init() {
        self.getData(dataBaseACollectionName: "dataBase_A")
    }
    
    // Get A Data
    private func getData(dataBaseACollectionName: String) {
        self.dataBaseCollectionGetDocuments(collectionName: dataBaseACollectionName) { isSucceededA in
            if !isSucceededA.isEmpty {
                self.myDataA = isSucceededA
                
                // Get first "documentID from A to determine what in B I have to read
                self.getSpecificDocumentFromDataBase(collectionName: self.myDataA.first ?? "randomCollection", documentID: self.myDataA.first ?? "randomDocument") { isSucceededB in
                    if !isSucceededB.isEmpty {
                        self.myDataB = isSucceededB
                        
                        // Get first "documentID from B to determine what in C I have to read
                        self.getSpecificDocumentFromDataBase(collectionName: self.myDataB.first ?? "randomCollection", documentID: self.myDataB.first ?? "randomDocument") { isSucceededC in
                            if !isSucceededC.isEmpty {
                                self.myDataC = isSucceededC
                                // done
                            }
                        } // C
                        
                    }
                } // B
                
            } // A
        }
    }
    
    // I made just one wrapper function for the DB call as all myData Arrays are from the same type (containing Strings)
    private func dataBaseCollectionGetDocuments(collectionName: String ,completing: @escaping (Array<String>) -> Void) {
        
        if !collectionName.isEmpty {
            var dataArray: Array<String> = []
            let group = DispatchGroup()
            group.enter() // initial enter
            
            Firestore.firestore().collection(collectionName).getDocuments() { (documents, error) in
                if error != nil {
                    print("Error getting amount of recipes in last seen")
                } else {
                    for doc in documents!.documents {
                        group.enter()
                        dataArray.append(doc.documentID) // Just appending ID as it is a String
                        group.leave()
                    }
                }
                group.leave() // Initial leave
            }
            
            group.notify(queue: DispatchQueue.global(qos: .background)) {
                completing(dataArray)
            }
        }
        
    }
    
    // For Specific documents
    private func getSpecificDocumentFromDataBase(collectionName: String, documentID: String ,completing: @escaping (Array<String>) -> Void) {
        
        if !collectionName.isEmpty && !documentID.isEmpty {
            var dataArray: Array<String> = []
            let group = DispatchGroup()
            group.enter() // initial enter
            
            let docRef = Firestore.firestore().collection(collectionName).document(documentID)
            docRef.getDocument() { (document, error) in
                group.enter()
                if error != nil {
                    print("Error getting amount of recipes in last seen")
                } else {
                    dataArray.append(document!.documentID) // Just appending ID as it is a String
                    group.leave()
                }
                group.leave() // Initial leave
            }
            
            group.notify(queue: DispatchQueue.global(qos: .background)) {
                completing(dataArray)
            }
        }
        
    }
    
}
Simon
  • 1,047
  • 5
  • 19
  • 1. I'm using snapshotListeners ("sL") instead of reading data once (I probably didn't emphasize this) 2. If I understand your approach correctly, it still doesn't provide a solution to the following problem: I have to read data from both documents in B, so I add a sL to each of the documents. Only after reading all required documents in B (in this case: two), the program continues to read data from C (readC). NOW (here is the problem), data from one document in B gets changed, which triggers its sL. Does this data update to only one document in B result in a new call of readC (as desired)? – ediheld Aug 24 '20 at 08:55