简体   繁体   中英

Swift: Firestore sub collections, custom objects and listeners

I have a model pattern as depicted in the below picture.

模型

In the UI I am trying to get all the Countries and related data. I have created respective structures and my idea was to use the custom object approach shown in the link Custom_Objects . Now the problem I have is that subcollections won't come in the querysnapshot ( I am getting only the fields) and hence I cannot do the direct object mapping (as I have to query and retrieve the subcollections to make the object complete). for example: Country structure has States as one of the properties and without states Country object cannot be created and States further has provinces. Right now I am doing recursive looping to construct the whole structure which I am not feeling quite good.

So my question would be, what is the best way to handle this kind of data (provided there is no room for normalization and we can't avoid subcollections)?

Also if I want to get notified on any changes to State, Province or Town, do I need to separately add listeners to each collection or adding to the root is enough?

Here is the current code snap

    db.collection("countries").getDocuments { (QuerySnapshot, error) in
    if let error = error {
        print("\(error.localizedDescription)")
    }else{
        var docCount = QuerySnapshot!.documents.count
        for document in QuerySnapshot!.documents {
            self.fetchStatesForDoc(document: document, completion: { (nodes) in
                var data = document.data()
                data["states"] = nodes
                let country = Country(dictionary: data)
                self.countryList.append(country!)
                print(self.sectionList)
                docCount = docCount - 1
                if docCount == 0{
                    DispatchQueue.main.async {
                        self.countryCollection.reloadData()
                    }
                }
            })
        }
    }
}
}
func fetchStatesForDoc(document: DocumentSnapshot, completion:@escaping ([State])-> Void){

    var states = [State]()
    document.reference.collection("states").getDocuments(completion: { (QuerySnapshot, error) in
        if let error = error {
            print("\(error.localizedDescription)")
        }else{
            var docCount = QuerySnapshot!.documents.count
            for document in QuerySnapshot!.documents {
                //print("\(document.documentID) => \(document.data())")
                var data = document.data()
                self.fetchProvincesForDoc(document: document, completion: { (provinces) in
                    data["Provinces"] = provinces
                    let state = State(dictionary: data)
                    states.append(state!)
                    docCount = docCount - 1
                    if docCount == 0{
                        completion(state)
                    }
                })
            }
        }
    })
}
func fetchProvincesForDoc(document: DocumentSnapshot, completion:@escaping ([Province])-> Void){

    var provinces = [Province]()
    document.reference.collection("provinces").getDocuments(completion: { (QuerySnapshot, error) in
        if let error = error {
            print("\(error.localizedDescription)")
        }else{
            var docCount = QuerySnapshot!.documents.count
            for document in QuerySnapshot!.documents {
                //print("\(document.documentID) => \(document.data())")
                var data = document.data()
                self.fetchTownsForDoc(document: document, completion: { (towns) in
                    data["towns"] = provinces
                    let province = Province(dictionary: data)
                    provinces.append(province!)
                    docCount = docCount - 1
                    if docCount == 0{
                        completion(province)
                    }
                })
            }
        }
    })
}
func fetchTownssForDoc(document: DocumentSnapshot, completion:@escaping ([Towns])-> Void) {

    var towns = [Towns]()
    document.reference.collection("towns").getDocuments(completion: { (QuerySnapshot, error) in
        if let error = error {
            print("\(error.localizedDescription)")
        }else{
            for document in QuerySnapshot!.documents {
                //print("\(document.documentID) => \(document.data())")
            }
            towns = QuerySnapshot!.documents.compactMap({Towns(dictionary: $0.data())})
            completion(towns)
        }
    })
}

Now the problem I have is that subcollections wont come in the querysnapshot ( I am getting only the fields)

That's right, this is how Cloud Firestore queries works. The queries are named shallow, which means they only get items from the collection that the query is run against. There is no way to get documents from a top-level collection and a subcollections in a single query. Firestore doesn't support queries across different collections in one go. A single query may only use properties of documents in a single collection. That's why ypu cannot see the subcollection in the querysnapshot object so you can do the direct object mapping.

what is the best way to handle these kind of data (provided there is no room for normalization and we cant avoid subcollections)?

In this case you should query the database twice, once to get the objects within the collection and second to get all the objects within the subcollection.

There is also another practice which is called denormalization and is a common practice when it comes to Firebase. This technique implies also querying the database twice. If you are new to NoQSL databases, I recommend you see this video, Denormalization is normal with the Firebase Database for a better understanding. It is for Firebase realtime database but same rules apply to Cloud Firestore.

Also, when you are duplicating data, there is one thing that need to keep in mind. In the same way you are adding data, you need to maintain it. With other words, if you want to update/detele an item, you need to do it in every place that it exists.

So in this case, you can denormalize your data by creating a top-level collection in which you should add all the objects that exist in your subcollections. It's up to you to decide which practice is better for you.

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