简体   繁体   中英

How to read multiple custom objects from Firestore in Swift

Using example from documentation (for Swift). Here is recommended way of reading multiple documents:

db.collection("cities").whereField("capital", isEqualTo: true)
    .getDocuments() { (querySnapshot, err) in
        if let err = err {
            print("Error getting documents: \(err)")
        } else {
            for document in querySnapshot!.documents {
                print("\(document.documentID) => \(document.data())")
            }
        }
}

And here is recommended way of reading custom object:

let docRef = db.collection("cities").document("BJ")

docRef.getDocument { (document, error) in
    let result = Result {
        try document.flatMap {
            try $0.data(as: City.self)
        }
    }
    switch result {
    case .success(let city):
        if let city = city {
            print("City: \(city)")
        } else {
            print("Document does not exist")
        }
    case .failure(let error):
        print("Error decoding city: \(error)")
    }
}

There is no example for reading multiple custom objects. This was my try based on the previous two examples:

db.collection("cities").whereField("capital", isEqualTo: true)
    .getDocuments() { (querySnapshot, err) in
        if let err = err {
            print("Error getting documents: \(err)")
        } else {
            if let snapshotDocuments = querySnapshot?.documents {
                for document in snapshotDocuments {        
                    let result = Result {
                        try document.flatMap { // error: Value of type 'QueryDocumentSnapshot' has no member 'flatMap'
                            try $0.data(as: City.self)
                        }
                    }
                    switch result {
                    case .success(let city):
                        if let city = city {
                            print("City: \(city)")
                        } else {
                            print("Document does not exist")
                        }
                    case .failure(let error):
                        print("Error decoding city: \(error)")
                    }
                }
            }
        }
    }
}

Based on the error (I've placed it's content in comment in code above) I'm assuming that classes of document objects are different while querying for single object or multiple, even though their usual use is the same. How should I change the code for loading multiple custom objects so that it would work?

In the first example, getDocument returns a DocumentSnapshot which is a subset of DocumentSnapshot? which is optional so it can be used with a Results object.flatmap.

In the second example, getDocuments doesn't return a DocumentSnapshot, it returns a QueryDocumentSnapshot:

QueryDocumentSnapshots guarantee that their contents are always nonnull

So that means it is not an optional and cannot be used with.flatmap as it cannot be unwrapped.

The Firebase Documentation shows QueryDocumentSnapshot is a subclass of DocumentSnapshot, DocumentSnapshot is optional, QueryDocumentSnapshot is not.

In this case, .flatmap is being used to unwrap a DocumentSnapshot (an optional) - whats happening is that the object within Result would be double wrapped optional without it. So.flatmap unwraps it once and then if let city = city { unwraps it again so it ends up being just the city object.

Here's what I would do... Replace that code with something like this

func getCities() {
    let docRef = db.collection("cities")
    docRef.getDocuments { (querySnapshot, error) in
        if let snapshotDocuments = querySnapshot?.documents {
            for document in snapshotDocuments {
                do {
                    if let city = try document.data(as: City.self) {
                        print(city.name)
                    }
                } catch let error as NSError {
                    print("error: \(error.localizedDescription)")
                }
            }
        }
    }
}

EDIT

I am going to borrow this from github because it makes the use of the Results (as shown in the documentation) much more clear.

There are thus three cases to handle, which Swift lets us describe
nicely with built-in sum types:

      Result
        /\
   Error  Optional<City>
               /\
            Nil  City //<- we get the actual city with `if let city = city {`

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM