Search code examples
iosswiftfirebasefirebase-realtime-database

Dealing with memory usage due to high element count in Firebase query


I'm writing code that is appending elements from firebase to an array to perform a simple search using a textfield.

The code for the method is below:

 var filteredEvents = [Event]()
var eventsArray = [Event]()
fileprivate func fetchEvents(){
    print("Fetching events....")
    //create a reference to the location in the database that you want to pull from and observe the value there
    let ref = Database.database().reference().child("events")
    // this will retur a snapshot with all the data at that location in the database and cast the results as a dictionary for later use
    ref.observe(.value, with: { (snapshot) in
        guard let dictionaries = snapshot.value as? [String: Any] else{
            return
        }
        //does the job of sorting dictionary elements by key and value
        //displaying the key and each corresponding value
        dictionaries.forEach({ (key,value) in
           // print(key, value)
            //creating an eventDictionary to store the results of previous call
            guard let eventDictionary = value as? [String: Any] else{
                return
            }
            //will cast each of the values as an Event based off my included struct
            //Make sure to create a model it is the only way to have the data in the format you want for easy access
             let events = Event(currentEventKey: key, dictionary:eventDictionary)
            // appends that to the dictionary to create the dictionary of events
            self.eventsArray.append(events)
        })
        // will sort the array elements based off the name
        self.eventsArray.sort(by: { (event1, event2) -> Bool in
            return event1.currentEventName.compare(event2.currentEventName) == .orderedAscending
        })
        // will again reload the data
        self.collectionView?.reloadData()

    }) { (err) in
        print("Failed to fetch events for search")
    }
}

I personally did not think of the chance where I may have a great number of events. I won't be able to just append all 1000+ events to a dictionary. That will ratchet my memory up. Is there anyway I could have the query respond to the text field. Also could anyone maybe help me with the line for the query that would perform this action but not destroy my memory?

"events" : {
"CCDS" : {
  "attend:count" : 1,
  "event:date" : {
    "end:date" : "08/09/2017",
    "end:time" : "7:00 PM",
    "start:date" : "08/09/2017",
    "start:time" : "5:00 PM"
  },
  "event:description" : "Happy hour is more joyful in the summer thanks to Center City District Sips, which offers discounted drinks and appetizers every Wednesday evening.  Catch up with old friends and make a few new ones as Center City’s best bars and restaurants host the summer’s happiest hour every Wednesday from 5-7 p.m.  Enjoy $5 cocktails, $4 wine, $3 beers and half-price appetizers at dozens and dozens of bars and restaurants.",
  "event:imageURL" :someURL",
  "event:location" : {
    "event:city" : "Philadelphia",
    "event:state" : "PA",
    "event:street:address" : "660 Chestnut St",
    "event:zip" : 19106
  },
  "event:name" : "Center City District Sips"
},
"MIA" : {
  "attend:count" : 1,
  "event:date" : {
    "end:date" : "09/03/2017",
    "end:time" : "7:00 PM",
    "start:date" : "09/02/2017",
    "start:time" : "12:00 PM"
  },
  "event:description" : "Budweiser Made in America Festival is an annual music festival held in Philadelphia and formerly simultaneously held in Los Angeles.Sponsored by Anheuser–Busch and produced by Live Nation, the event features several stages that continuously host live music from a wide range of genres including hip hop, rock, pop, R&B, and EDM.",
  "event:imageURL" : "someURL",
  "event:location" : {
    "event:city" : "Philadelphia",
    "event:state" : "PA",
    "event:street:address" : "Ben Franklin Parkway",
    "event:zip" : 19130
  },
  "event:name" : "Made In America"
}
  },

For example I want to pull all information about an event that I have searched for using the query. So if I begin to type in Made in America it will pull all relevant information from the events tab about that event

This is what I currently have

fileprivate func fetchEvents(searchString: String){
    print("Fetching events....")
    //create a reference to the location in the database that you want to pull from and observe the value there
    let ref = Database.database().reference().child("events")
    // this will retur a snapshot with all the data at that location in the database and cast the results as a dictionary for later use
  let query = ref.queryOrdered(byChild: "event:name").queryEqual(toValue: searchString)
    print(query)
    query.observeSingleEvent(of: .value, with: { (snapshot) in
        guard let dictionary = snapshot.value as? [String: Any] else{
            print(snapshot.value)
            return
        }
        print(snapshot.value)
    }) { (err) in
        print("Failed to fetch event data", err)
    }

}

Returns this

(/events { ep = Made In America; i = "event:name"; sp = Made In America; })


Solution

  • It appears the question is

    "How can I query for a value contained in a child node?"
    

    Given a structure similar to the original

    "events" : {
      "CCDS" : {
        "attend:count" : 1,
        "event:imageURL" :"someURL",
        "event:name" : "Center City District Sips"
      "MIA" : {
        "attend:count" : 1,
        "event:imageURL" : "someURL",
        "event:name" : "Made In America"
    

    a Firebase query would return the node you want.

    If the user typed in Made In America and tapped the search button here's the query to return that node in a snapshot.

    let searchString = "Made In America"
    let ref = self.ref.child("events")
    let query = ref.queryOrdered(byChild: "event:name").queryEqualTo(searchString)
    query.observeSingleEvent(of: .value, with: { (snapshot) in
        for child in snapshot.children {
            let snap = child as! DataSnapshot
            let eventDict = snap.value as! [String: Any]
            let attendCount = eventDict["attend:count"] as! String
            let url = eventDict["event:imageURL"} as! String
        }
    })
    

    If you want to do a partial string match, where the user could type in just a few characters like Made the code is similar but you need to let firebase return all of the matches starting with Made...

    The query would look like this

    let startString = "Made"
    let endString = "Made" + "\\uf8ff"
    let query = ref.queryOrdered(byChild: "event:name")
                            .queryStarting(atValue: startString)
                            .queryEnding(atValue: endString")
    

    The "\uf8ff" is a character at a very high code level in Unicode - because of that it encompasses all of the preceeding characters (see edit below)

    However, querying 'on the fly' can create an unresponsive or sluggish UI so it's not recommended.

    An alternative is to create a seperate node that contains a lot less info and contains the elements the user would search for and a reference to the event node.

    So the main node containing all the data looks like this

    "events" : {
      "-uyuh8s8j8jsdas" : {
        "event": "CCDS"
        "attend:count" : 1,
        "event:imageURL" : "someURL",
      "-y88jsijsijjids" : {
        "event": "MIA"
        "attend:count" : 1,
        "event:imageURL" : "someURL",
    

    and a 'smaller' node would look like this

       events_for_searching
           -uyuh8s8j8jsdas
             event:name: "Center City District Sips"
           -y88jsijsijjids
             event:name: "Made In America"
     
    

    With this, you could load all of the nodes from the events_for_searching into an array (then filter the array as the user types) which would make the UI very responsive and when the user selects a name, you can then use the key from that node as a reference to load the data from the events node via an observeSingleEvent function.

    EDIT

    In response to a comment, I wanted to add a bit more detail in code.

    Here's my structure

      "events" : {
        "event_0" : {
          "event:name" : "An Event"
        },
        "event_1" : {
          "event:name" : "Made In America"
        }
      },
    

    and the code to query for event:name: Made In America

    let searchString = "Made In America"
    let ref = self.ref.child("events")
    let query = ref.queryOrdered(byChild: "event:name").queryEqual(toValue: searchString)
    
    query.observeSingleEvent(of: .value, with: { (snapshot) in
        guard let dictionary = snapshot.value as? [String: Any] else{
            print(snapshot.value)
            return
        }
        print(snapshot.value)
    }) { (err) in
        print("Failed to fetch event data", err)
    }
    

    and the output

    Optional({
        "event_1" =     {
            "event:name" = "Made In America";
        };
    })
    

    Edit: 2024-06

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