0
votes

I'm just getting started with Firebase Database and am having some troubles wrapping my head around efficiently querying data. My current database structure is similar to the Firebase documentation for recommended data structures, where we see this example:

// An index to track Ada's memberships
{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      // Index Ada's groups in her profile
      "groups": {
         // the value here doesn't matter, just that the key exists
         "techpioneers": true,
         "womentechmakers": true
      }
    },
    ...
  },
  "groups": {
    "techpioneers": {
      "name": "Historical Tech Pioneers",
      "members": {
        "alovelace": true,
        "ghopper": true,
        "eclarke": true
      }
    },
    ...
  }
}

The problem I'm having is that I'm not sure how to efficiently retrieve all of the group names for a specific user. I can retrieve all of the group IDs for a user easily:

usersRef.child("alovelace").child("groups").observeSingleEvent(of: .value, with: { (snapshot) in 
// code to store group IDs 
})

But now the only way I can think to get all the group names is to make a for loop through the IDs, then make a .observeSingleEvent call for each one to get the name. But what if I have a very large set of groups for the user? Ideally I'd prefer not to make so many database calls and just do it in one call. Is that possible with this data structure?

2
Firebase will retrieve the items over a single open connection, so the overhead of the connection is small. Retrieving reasonable number of items in such a way (also called a client-side join) is not as slow as you may think, because the requests are pipelined over the existing connection. See stackoverflow.com/questions/35931526/…Frank van Puffelen

2 Answers

0
votes

This is what I suggest you do. This populates the groups array with all the data inside the groups tree that the user belongs to.

import UIKit
import Firebase

class TableViewController: UITableViewController {

    var groups = Array<Group>()

    override func viewDidLoad() {
        super.viewDidLoad()

        Group.get(userUID: "alovelace") { (groups) in

            for uid in groups {

                Group.get(groupUID: uid, completion: { (group) in

                    if let group = group {

                        print(group)

                        self.groups.append(group)

                    } else {

                        print("There is no group with id: \(uid)")
                    }

                    self.tableView.reloadData()
                })
            }
        }
    }
}

struct Group {

    var uid: String

    var name: String

    var members: Array<String>

    init?(uid:String, dict:Dictionary<String,Any>){

        guard

            let name = dict["name"] as? String,

            let users = dict["members"] as? Dictionary<String,Bool>

        else {

            return nil
        }

        self.uid = uid

        self.name = name

        self.members = Array<String>()

        for (id, _) in users {

            self.members.append(id)
        }
    }

    // Returns a Group, when given a group id.
    static func get(groupUID:String, completion: @escaping (Group?) -> ()) {

        let ref = FIRDatabase.database().reference().child("groups").child(groupUID)

        ref.observeSingleEvent(of: .value, with: { (snapshot) in

            if let value = snapshot.value as? Dictionary<String,Any> {

                if let group = Group(uid: groupUID, dict: value) {

                    completion(group)

                    return

                } else {

                    print("Incomplete Group Data")
                }
            }

            completion(nil)
        })
    }

    // Returns the group ids that a user belongs to
    static func get(userUID:String, completion: @escaping (Array<String>) -> ()) {

        let ref = FIRDatabase.database().reference().child("users").child(userUID).child("groups")

        ref.observeSingleEvent(of: .value, with: { (snapshot) in

            if let value = snapshot.value as? Dictionary<String,Any> {

                completion(value.keys.sorted())

                return
            }

            completion([])
        })
    }
}
0
votes

You can do this:

usersRef.child("alovelace").child("groups").observeSingleEvent(of: .value, with: { (snapshot) in 
    let result = snapshot.children.allObjects as? [FIRDataSnapshot]
    for child in result {
        let groupName = child.key
        print(groupName)
    }
})