2

How can I get ids documents from firestore?

Now I get several ids documents from backend and me need display received ids documents in tableview.

In firestore i have this ids:

xNlguCptKllobZ9XD5m1 uKDbeWxn9llz52WbWj37 82s6W3so0RAKPZFzGyl6 EF6jhVgDr52MhOILAAwf FXtsMKOTvlVhJjVCBFj8 JtThFuT4qoK4TWJGtr3n TL1fOBgIlX5C7qcSShGu UkZq3Uul5etclKepRjJF aGzLEsEGjNA9nwc4VudD dZp0qITGVlYUCFw0dS8C n0zizZzw7WTLpXxcZNC6

And for example my backend found only this ids:

JtThFuT4qoK4TWJGtr3n TL1fOBgIlX5C7qcSShGu UkZq3Uul5etclKepRjJF

or

aGzLEsEGjNA9nwc4VudD dZp0qITGVlYUCFw0dS8C n0zizZzw7WTLpXxcZNC6

Me need display only this three ids in tableview. (But in reality backend return me 100+ ids and below you can see frantic sorting these ids)

Backend append this ids in temporary array var tempIds: [String] = []

So how I can get from firestore only those ids and display their in tableview?

I use this code:

fileprivate func query(ids: String) {
    Firestore.firestore().collection(...).document(ids).getDocument{ (document, error) in
        if let doc = document, doc.exists {
            if let newModel = Halls(dictionary: doc.data()!, id: doc.documentID) {
                self.halls.append(newModel)
                self.halls.shuffle()
                self.halls.sort(by: { $0.priority > $1.priority })
                self.tableView.reloadData()
            } else {
                fatalError("Fatal error")
            }
        } else {
            return
        }
    }
}

Me need to process ids from backend in background and after process need to show processed ids in tableview without frantic sorting.

May be need use addSnapshotListened, but I don't understand how.

UPDATED CODE:

for id in idsList {
                            dispatchGroup.enter()
                            Firestore.firestore().collection(...).document(id).getDocument{ (document, error) in
                                if let doc = document, doc.exists {
                                    if let newHallModel = Halls(dictionary: doc.data()!, id: doc.documentID) {
                                        self.tempHalls.append(newHallModel)
                                        dispatchGroup.leave()
                                    } else {
                                        fatalError("Fatal error")
                                    }
                                } else {
                                    print("Document does not exist")
                                    MBProgressHUD.hide(for: self.view, animated: true)
                                    return
                                }
                            }
                        }

                        dispatchGroup.notify(queue: .global(qos: .default), execute: {

                            self.halls = self.tempHalls

                            DispatchQueue.main.async {
                                MBProgressHUD.hide(for: self.view, animated: true)
                                self.tableView.reloadData()
                            }
                        })

enter image description here

Dan McGrath
  • 41,220
  • 11
  • 99
  • 130

4 Answers4

5

Instead of getting documents one-by-one, you could use "IN" query to get 10 docs with 1 request:

userCollection.where('uid', 'in', ["1231","222","2131"]);

// or 
myCollection.where(FieldPath.documentId(), 'in', ["123","456","789"]);

// previously it was 
// myCollection.where(firestore.FieldPath.documentId(), 'in', ["123","456","789"]);

Firestore Docs: "Use the in operator to combine up to 10 equality (==) clauses on the same field with a logical OR. An in query returns documents where the given field matches any of the comparison values"

Stefan Majiros
  • 444
  • 7
  • 11
3

Getting a document by its identifier should be used when you need a single document or documents you cannot query for. Don't be hesitant to denormalize your data to make queries work, that's the point of NoSQL. If I were you, I'd either add a field to these documents so that they can be queried or denormalize this dataset set with a new collection (just for this query). However, if you still choose to fetch multiple documents by identifier, then you need to make n getDocument requests and use a dispatch group to handle their synchronization.

let docIds = ["JtThFuT4qoK4TWJGtr3n", "TL1fOBgIlX5C7qcSShGu", "UkZq3Uul5etclKepRjJF"]
let d = DispatchGroup()
    
for id in docIds {
    d.enter()
    
    Firestore.firestore().collection(...).document(id).getDocument { (document, error) in
        // handle doc
        d.leave() // always leave no matter how you exit this closure
    }
}

d.notify(queue: .main, execute: {
    // here is your completion when the last leave() was called
})

All the dispatch group does is keep a count of the number of times it's entered and left and when they match, it calls its notify(queue:execute:) method (its completion handler).

trndjc
  • 11,654
  • 3
  • 38
  • 51
0

I've faced the same task. And there is no better solution. Fetching documents one by one, so I've written small extension:

extension CollectionReference {
typealias MultiDocumentFetchCompletion = ([String: Result<[String: Any], Error>]) -> Void

class func fetchDocuments(with ids: [String], in collection: CollectionReference, completion:@escaping MultiDocumentFetchCompletion) -> Bool  {
    guard ids.count > 0, ids.count <= 50 else { return false }
    var results = [String: Result<[String: Any], Error>]()
    for documentId in ids {
        collection.document(documentId).getDocument(completion: { (documentSnapshot, error) in
            if let documentData = documentSnapshot?.data() {
                results[documentId] = .success(documentData)
            } else {
                results[documentId] = .failure(NSError(domain: "FIRCollectionReference", code: 0, userInfo: nil))
            }
            if results.count == ids.count {
                completion(results)
            }
        })
    }
    return true
}
}
temrov
  • 9
  • 2
  • 1
    This answer is outdated. It is now possible like this: `Firestore.firestore().collection("yourCollection") .whereField(FieldPath.documentID(), in: ["123", "456"...])` – Tulleb Jan 27 '22 at 22:37
0

Swift5 and Combine:

func getRegisteredUsers(usersId: [String]) -> AnyPublisher<[RegisteredUser], Error> {
    return Future<[RegisteredUser], Error> { promise in
        self.db.collection("registeredUsers")
            .whereField(FieldPath.documentID(), in: usersId)
            .getDocuments { snapshot, error in
                
                do {
                    let regUsers = try snapshot?.documents.compactMap {
                        try $0.data(as: RegisteredUser.self)
                    }
                    
                    promise(.success(regUsers ?? []))
                } catch {
                    promise(.failure(.default(description: error.localizedDescription)))
                }
            
        }
    }
    .eraseToAnyPublisher()
}
oskarko
  • 3,382
  • 1
  • 26
  • 26