1

I want to fill an array by fetching values from one firebase node and use those values to fetch information from a different firebase node. How do I do that?

This is what my firebase database looks like:

{
  "MainTree" : {
    "subTree1" : {
      "JiRtkpIFLVFNgmNBpMj" : {
        "PiRterKFLVFNgmFFFtu" : "PiRterKFLVFNgmFFFtu"
        "TfRterKFLVFNgmFGFre" : "TfRterKFLVFNgmFGFre",
        "X4RterKFLVFNgmaDFca" : "X4RterKFLVFNgmaDFca"
        }
      },
    "subTree2" : {
        "PiRterKFLVFNgmFFFtu" : {
        "username" : "user1",
        "uid" : "PiRterKFLVFNgmFFFtu"
        },
        "TfRterKFLVFNgmFGFre" : {
        "username" : "user2",
        "uid" : "TfRterKFLVFNgmFGFre"
        },
        "X4RterKFLVFNgmaDFca" : {
        "username" : "user3",
        "uid" : "X4RterKFLVFNgmaDFca"
        }
    }
    }
}

My Function

func fetchAllInformation(uid: String, completion: @escaping ([UserData]) -> (), withCancel cancel: ((Error) -> ())?) {

    let ref = Database.database().reference().child("MainTree").child("subTree1").child(uid)
    ref.observeSingleEvent(of: .value, with: { (snapshot) in

        if snapshot.exists(){
            guard let dictionaries = snapshot.value as? [String: Any] else {
                completion([])
                return
            }
            var Values = [UserData]()
            let group = DispatchGroup()
            dictionaries.forEach({ (key, value) in
                group.enter()
                let ref = Database.database().reference().child("MainTree").child("subTree2").child(key)
                ref.observeSingleEvent(of: .value, with: { (snapshot) in
                    guard let userDictionary2 = snapshot.value as? [String: Any] else { return }
                    let user = UserData(dictionary: userDictionary2)
                    Values.append(user)
                }) { (err) in
                    print("Failed to fetch all user data from database:", (err))
                    cancel?(err)
                }
            })
            group.notify(queue: .main) {
                print("loop done")
                completion(Values)
            }
        }
    }) { (err) in
        print("Failed to fetch all data from database:", (err))
        cancel?(err)
    }
}

My Calling Function:

fetchAllInformation(uid: "JiRtkpIFLVFNgmNBpMj", completion: { (userdata) in
                print("fetched all userdata! : ",userdata)

            }) { (err) in
                print("data fetch failed")
            }

My Data Structure

struct UserData {

let uid: String
let username: String

init(dictionary: [String: Any]) {
    self.uid = dictionary["id"] as? String ?? ""
    self.username = dictionary["username"] as? String ?? ""
    }
}

It might be a misunderstanding with asynchronous code. Right now the problem is that the array is turning up empty.

13
  • What issue are you having? Commented Jan 15, 2020 at 1:51
  • 1
    @bsod firebaser here The Realtime Database is equally relevant as it's always been. We have people actively working on maintaining and improving it. While Firestore may be a better fit for some scenarios, Realtime Database is a better fit for others. Commented Jan 15, 2020 at 2:50
  • 1
    Data is loaded from Firebase asynchronously. Any code that needs the data, needs to be in the closure of observe or be called from there. That includes your call to group.notify(queue: .main) {, which now fires before the data is loaded and thus when bookmarkedUsers is still empty. You can most easily test with by adding some logging statements, for example as shown here: stackoverflow.com/questions/37918256/… Commented Jan 15, 2020 at 2:56
  • 1
    @newswiftcoder Yes, if you only enter the group once, you will exit as as as you call notify (as far as I understand, as I'm definitely not an expert on dispatch groups). But since you call enter for each item, you should also call leave for each if I read this answer correctly: stackoverflow.com/a/35906703/209103 Commented Jan 15, 2020 at 3:51
  • 1
    Firebase swag is not for sale. But if you keep an eye out for Firebasers at local events, they often bring swag (although no hats yet). Commented Jan 23, 2020 at 1:06

1 Answer 1

2

I think what's you're asking is how to iterate over a series of nodes, getting another nodes child keys from that node, then reading the data from the other node based on those keys.

Let me start with a Firebase structure that should help clarify

MainTree
   subTree1
      some_node
         subTree2_0: true
         subTree2_1: true
         subTree2_2: true
   subTree2
      subTree2_0:
          user_name: "Larry"
      subTree2_1:
          user_name: "Moe"
      subTree2_1:
          user_name: "Curly"

That should match up to the structure in the question.

We're going to iterate over the child nodes located in MainTree/subTree1/some_node to get the nodes we want to read from subTree2. I didn't know what some_node was in respect to the rest of the data so I just called it... some_node.

This first section of code reads the subTree1 node child nodes at once, then iterates over them to get each child key - that child key (e.g. subTree2_0) corresponds to a child node in subTree2

func readMainTree() {
    let mainTreeRef = self.ref.child("MainTree")
    let subTree1Ref = mainTreeRef.child("subTree1")
    let someNodeRef = subTree1Ref.child("some_node")
    someNodeRef.observeSingleEvent(of: .value, with: { snapshot in
        let childNodes = snapshot.children.allObjects as! [DataSnapshot]
        for childSnap in childNodes {
            self.readSubTree2At(node: childSnap.key)
        }
    })
}

Within the for..loop, we get each child key and pass that to the function that reads the child data (user_name) and prints it out.

func readSubTree2At(node: String) {
    let mainTreeRef = self.ref.child("MainTree")
    let subTree2Ref = mainTreeRef.child("subTree2")
    let childRef = subTree2Ref.child(node)
    childRef.observeSingleEvent(of: .value, with: { snapshot in
        let userName = snapshot.childSnapshot(forPath: "user_name").value as? String ?? "No Name"
        print(userName)
    })
}

and the output is:

Larry
Mo
Curly

You could throw a dispatchGroup into the mix if you want.. here's a solution using a DispatchGroup

func readMainTreeWithDispatch() {
    let mainTreeRef = self.ref.child("MainTree")
    let subTree1Ref = mainTreeRef.child("subTree1")
    let someNodeRef = subTree1Ref.child("some_node")
    someNodeRef.observeSingleEvent(of: .value, with: { snapshot in
        let childNodes = snapshot.children.allObjects as! [DataSnapshot]

        let myGroup = DispatchGroup()

        for childSnap in childNodes {
            let mainTreeRef = self.ref.child("MainTree")
            let subTree2Ref = mainTreeRef.child("subTree2")
            let childRef = subTree2Ref.child(childSnap.key)

            myGroup.enter()
            childRef.observeSingleEvent(of: .value, with: { snapshot in
                let userName = snapshot.childSnapshot(forPath: "user_name").value as? String ?? "No Name"
                print(userName)
                myGroup.leave()
            })
        }

        myGroup.notify(queue: .main) {
            print("Finished reading all user names.")
        }
    })
}

and the output

Larry
Mo
Curly
Finished reading all user names.
Sign up to request clarification or add additional context in comments.

5 Comments

Your work is on the spot! One question though: How would you implement a completion listener after all the loops? Let's say you want the end result to be the array: [Larry, Mo, Curly]. My approach feels redundant because it calls completion(Values) every loop.
Thanks for the great answer Jay! I'm glad someone who actually knows dispatch groups could help here. :)
@newswiftcoder If you need it, I will get a dispatch group solution together in the morning. It's started there are the end of my answer.
@newswiftcoder I updated the answer with a DispatchGroup example. If my answer helps, be sure to accept it so it can help others.
@FrankvanPuffelen Dispatch groups are slightly weird science for sure. lol. Updated the answer with working code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.