Search code examples
iosswiftfirebasefirebase-realtime-databasepagination

How to apply multiple filter in Firebase query in Swift?


I am trying developing an application like Taxi booking and storing data on Firebase.

But, I am facing problem while querying data for RideDetail(History) from Firebase.

I want to fetch ride_detail for specific "customer_id" in pagination form.

My Firebase DataStructure:

{
  "ride_details": {
    "NuEoP2WNPwigsbY1FQy9M150131918189233": {
      "customer_id": "tstebwLlf4OCRdWhNKO9XCO08xY2",
      "destination_address": "New Ranip\nNew Ranip\nAhmedabad\nGujarat 380081\nIndia",
      "destination_lang": 72.55924470000001,
      "destination_latg": 23.0930152,
      "discount": "10%",
      "driver_id": "cIyZQIJ7tsdvF1a9KpRrKucF2o62",
      "drop_time": "2017-07-29 09:12:21 +0000",
      "fare": "13.16 Rs.",
      "payment_time": 150149034812771,
      "pickup_time": "2017-07-29 09:10:38 +0000",
      "priceperkm": "10.00 Rs.",
      "ride_confirm_time": "2017-07-29 09:06:21 +0000",
      "source_address": "Vastrapur\nVastrapur\nAhmedabad\nGujarat\nIndia",
      "source_lang": 72.5293244,
      "source_latg": 23.0350073,
      "tax": "10%"
    },
    "RH0oZ0Ypbkur3wJM3HMvM150147833457957": {
      "customer_id": "aYQFbwLlf4OCRdWhNKO9XCO08xY2",
      "destination_address": "Sarovar Park Plaza Hotels and Resorts Private Limted\nNo 1\nSector 10\nCBD Belapur\nWadala West\nWadala\nMumbai\nMaharashtra 400614\nIndia",
      "destination_lang": 72.8561644,
      "destination_latg": 19.0176147,
      "discount": 0,
      "driver_id": "cIyZQIJ7tsdvF1a9KpRrKucF2o62",
      "drop_time": "",
      "fare": 0,
      "payment_time": 150149034812772,
      "pickup_time": "",
      "priceperkm": 0,
      "ride_confirm_time": "2017-07-31 05:18:54 +0000",
      "source_address": "Smokin Joe's Fresh Pizza\nShop No. 2\n3\nGround Floor\nAbhiman II\nWadala West\nThane West\nMumbai\nMaharashtra 400602\nIndia",
      "source_lang": 72.8561644,
      "source_latg": 19.0176147,
      "tax": 0
    }
  }
}

Here "payment_time" is timestamp when payment done.

And the response I want is like:

{
    "RH0oZ0Ypbkur3wJM3HMvM150147833457957": {
      "customer_id": "aYQFbwLlf4OCRdWhNKO9XCO08xY2",
      "destination_address": "Sarovar Park Plaza Hotels and Resorts Private Limted\nNo 1\nSector 10\nCBD Belapur\nWadala West\nWadala\nMumbai\nMaharashtra 400614\nIndia",
      "destination_lang": 72.8561644,
      "destination_latg": 19.0176147,
      "discount": 0,
      "driver_id": "cIyZQIJ7tsdvF1a9KpRrKucF2o62",
      "drop_time": "",
      "fare": 0,
      "payment_type": 150149034812772,
      "pickup_time": "",
      "priceperkm": 0,
      "ride_confirm_time": "2017-07-31 05:18:54 +0000",
      "source_address": "Smokin Joe's Fresh Pizza\nShop No. 2\n3\nGround Floor\nAbhiman II\nWadala West\nThane West\nMumbai\nMaharashtra 400602\nIndia",
      "source_lang": 72.8561644,
      "source_latg": 19.0176147,
      "tax": 0
    },
    "1trcf0Ypbkur3wJM3HMvM150147833457957": {
      "customer_id": "aYQFbwLlf4OCRdWhNKO9XCO08xY2",
      "destination_address": "Sarovar Park Plaza Hotels and Resorts Private Limted\nNo 1\nSector 10\nCBD Belapur\nWadala West\nWadala\nMumbai\nMaharashtra 400614\nIndia",
      "destination_lang": 72.8561644,
      "destination_latg": 19.0176147,
      "discount": 0,
      "driver_id": "cIyZQIJ7tsdvF1a9KpRrKucF2o62",
      "drop_time": "",
      "fare": 0,
      "payment_type": 150149034812778,
      "pickup_time": "",
      "priceperkm": 0,
      "ride_confirm_time": "2017-07-31 05:18:54 +0000",
      "source_address": "Smokin Joe's Fresh Pizza\nShop No. 2\n3\nGround Floor\nAbhiman II\nWadala West\nThane West\nMumbai\nMaharashtra 400602\nIndia",
      "source_lang": 72.8561644,
      "source_latg": 19.0176147,
      "tax": 0
    } 
}

I want first 10 records for specific "customer_id" that I pass in query orderedBy "payment_time". Also I want to do pagination for the same. i.e. in second query call, it must return 11-20 records and so on.


Solution

  • The question and comments have some different criteria but let me address it at a high level;

    The first answer is: Firebase cannot be queried for the value of one child and then ordered by another.

    The simple query function expresses that:

    let query = ridesRef.queryOrdered(byChild: "cust_id").queryEqual(toValue: "cust id 4")
    

    To accomplish that task, query for the child data you want, in this case all customer id 4 nodes, and then order in code. Here's an example

    class RideClass {
        var key = ""
        var cust_id = ""
        var pay_time = ""
        
        init(key: String, cust_id: String, pay_time: String) {
            self.key = key
            self.cust_id = cust_id
            self.pay_time = pay_time
        }
    }
    
    var rideArray = [RideClass]()
    
    func populateRideArray() {
        let usersRef = self.ref.child("ride_details")
        let query = usersRef.queryOrdered(byChild: "cust_id").queryEqual(toValue: "cust id 4") 
        query.observeSingleEvent(of: .value, with: { snapshot in 
            for child in snapshot.children {
                let snap = child as! DataSnapshot
                let dict = snap.value as! [String: Any]
                let key = snap.key
                let custId = dict["cust_id"] as! String
                let payTime = dict["pay_time"] as! String
                let ride = RideClass(key: key, cust_id: custId, pay_time: payTime)
                self.rideArray.append(ride)
            }
            
            for ride in self.rideArray {  //unsorted example
                print(ride.pay_time)
            }
            
            self.rideArray.sort { $0.pay_time < $1.pay_time } //sort
            
            for ride in self.rideArray {  //sorted example
                print(ride.pay_time)
            }
        })
    }
    

    In this example, we create a RideClass which stores some info about the ride, and then an array of rides which could be used as a tableView dataSource.

    Then query for all rides that are for cust id 4. We have a loop to show what was retreived unsorted and then this little gem

    self.rideArray.sort { $0.pay_time < $1.pay_time }
    

    which sorts the ride array in place by pay_time, which answers the question.

    Suppose though, there are 100,000 ride child nodes. Loading in all of that data and sorting in code could be challenging memory wise. What do you do?

    We leverage a compound value; along with the child nodes of cust_id and pay_time, we also include id_time. Here's a possible structure:

      "ride_details" : {
        "ride_0" : {
          "cust_id" : "cust id 4",
          "id_time" : "cust id 4_172200",
          "pay_time" : "172200"
        },
        "ride_1" : {
          "cust_id" : "cust id 2",
          "id_time" : "cust id 2_165500",
          "pay_time" : "165500"
        },
        "ride_2" : {
          "cust_id" : "cust id 1",
          "id_time" : "cust id 1_182300",
          "pay_time" : "182300"
        },
        "ride_3" : {
          "cust_id" : "cust id 3",
          "id_time" : "cust id 3_131800",
          "pay_time" : "131800"
        },
        "ride_4" : {
          "cust_id" : "cust id 4",
          "id_time" : "cust id 4_132200",
          "pay_time" : "132200"
        }
      },
    

    and then some code to read in the cust id 4 nodes in the correct order

        let ridesRef = self.ref.child("ride_details")
        let query = ridesRef.queryOrdered(byChild: "id_time")
                            .queryStarting(atValue: "cust id 4_")
                            .queryEnding(atValue: "cust id 4_\\uf8ff")
        query.observeSingleEvent(of: .value, with: { snapshot in
            for child in snapshot.children {
                let snap = child as! DataSnapshot
                let dict = snap.value as! [String: Any]
                let key = snap.key
                let custId = dict["cust_id"] as! String
                let payTime = dict["pay_time"] as! String
                let ride = RideClass(key: key, cust_id: custId, pay_time: payTime)
                self.rideArray.append(ride)
            }
            
            for ride in self.rideArray {  //unsorted example
                print(ride.pay_time)
            }
        })
    

    Two things to note:

    The snapshot must be iterated over to maintain the child sequence

    "\uf8ff" is a character at a very high code level in Unicode - because of that it encompasses all of the preceeding characters.

    Edit: 2024-06

    You may need to use "\u{F8FF}" instead of "\\uf8ff"