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.

newswiftcoder
  • 111
  • 1
  • 11
  • What issue are you having? – DrewG23 Jan 15 '20 at 01:51
  • Why aren't you using Firestore? Just curious. – trndjc Jan 15 '20 at 01:57
  • my code gives me an empty array @DrewG23 – newswiftcoder Jan 15 '20 at 02:27
  • I am not familiar with firestore @bsod – newswiftcoder Jan 15 '20 at 02:27
  • If you're new to Swift and/or programming, jump on Firestore. Firebase RTDB is not the future. – trndjc Jan 15 '20 at 02:35
  • 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. – Frank van Puffelen Jan 15 '20 at 02: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: https://stackoverflow.com/questions/37918256/firebase-with-swift-3-counting-the-number-of-children/37925384#37925384 – Frank van Puffelen Jan 15 '20 at 02:56
  • @FrankvanPuffelen I respectfully disagree, RTDB is not as relevant as it was before Firestore became the default Firebase database. Firestore has richer querying, is faster, and scales better than RTDB. And the syntax and the data model, IMO, are superior. RTDB also pulled the plug on native geoquery and Firebasers here have said that it will eventually come to Firestore. Firestore is clearly the future, IMO. And—having worked with AppSync and DynamoDB—I think it will eventually overtake them too. – trndjc Jan 15 '20 at 03:10
  • @FrankvanPuffelen that makes sense. However, including `group.notify(queue: .main) {` within `dictionaries.forEach({ (key, value) in` would prematurely end the loop, no? – newswiftcoder Jan 15 '20 at 03:27
  • 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: https://stackoverflow.com/a/35906703/209103 – Frank van Puffelen Jan 15 '20 at 03:51
  • @FrankvanPuffelen do you guys sell Firebase hats by chance? I love the work you guys do! – newswiftcoder Jan 22 '20 at 21:50
  • 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). – Frank van Puffelen Jan 23 '20 at 01:06
  • @FrankvanPuffelen you guys ROCK! – newswiftcoder Jan 23 '20 at 08:33

1 Answers1

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.
Jay
  • 34,438
  • 18
  • 52
  • 81
  • 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. – newswiftcoder Jan 15 '20 at 23:35
  • 1
    Thanks for the great answer Jay! I'm glad someone who actually knows dispatch groups could help here. :) – Frank van Puffelen Jan 16 '20 at 00:37
  • 1
    @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. – Jay Jan 16 '20 at 01:05
  • 1
    @newswiftcoder I updated the answer with a DispatchGroup example. If my answer helps, be sure to accept it so it can help others. – Jay Jan 17 '20 at 17:45
  • 1
    @FrankvanPuffelen Dispatch groups are slightly weird science for sure. lol. Updated the answer with working code. – Jay Jan 17 '20 at 17:46