Search code examples
iosswiftfirebasefirebase-realtime-databasefirebase-storage

querying firebase efficiently


I want to search my user base from each users name value. From what I've seen online people often return all users then filter them in a table view but that doesn't seem practical nor viable. My thought was to query data and return an exponentially smaller array of values but I am having trouble using the query methods provided.

How do I query a specific aspect of my database?

How do I structure my code so that it's viable; not loading in EVERY user, something like 10 max at a time.

Any suggestions, resources, and links are greatly appreciated.

EDIT:

I did some researching and it looks like Firebase comes with some built in querying methods... So far this is what I'm attempting to test it out with the code below to print out users starting with I, but I can't get it to print any users in the console

 ref.queryOrderedByKey().queryStarting(atValue: "I").queryEnding(atValue: "I\u{f8ff}")
        .observe(.childAdded, with: { snapshot in
            print(snapshot.key)
        })

Solution

  • There are a number of solutions and often times loading ALL of the user data is too much data.

    Here's a typical users node

    users
      uid_0
        name: "Jean Luc"
      uid_1
        name: "Will"
      uid_2
        name: "Geordi"
    

    One option is to iterate through each user node, one at a time, to retrieve the user name. This avoids an enormous data set entirely. We'll use the .childAdded event to load each and store in an array

        let usersRef = self.ref.child("users")
        var userNamesArray = [String]()
        
        usersRef.observe(.childAdded, with: { snapshot in
            let userDict = snapshot.value as! [String: Any]
            let name = userDict["name"] as! String
            userNamesArray.append(name)
        })
    

    A second option is to store the user name in an entirely different node, which significantly reduces the 'clutter' as the rest of the data remains in the main users node

    user_names
       uid_0: "Jean Luc"
       uid_1: "Will"
       uid_2: "Geordi"
    

    As you can see with this structure, even with thousands of names it's just text with a very small footprint.

    Another option is to load X number of users at a time using .startingAt and .endingAt and iterate over the returned users to get each name. In this case we want all users starting with A and ending with M... Sorry Worf.

        let usersRef = self.ref.child("users")
        var userNamesArray = [String]()
        
        let nameQuery = usersRef.queryOrdered(byChild: "name")
                                .queryStarting(atValue: "A")
                                .queryEnding(atValue: "M\u{f8ff}")
        nameQuery.observe(.value, with: { snapshot in
            for child in snapshot.children {
                let snap = child as! DataSnapshot
                let userDict = snap.value as! [String: Any]
                let name = userDict["name"] as! String
                userNamesArray.append(name)
            }
        })
    

    The last example started with users names starting with A and ended with user names ending with M + a very high unicode character, which makes it inclusive for all names starting with M

    The \uf8ff character used in the query above is a very high code point in the Unicode range. Because it is after most regular characters in Unicode, the query matches all values that start with queryString.

    Edit: 2024-06

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