Fetch data from Firebase by joining tables in iOS
Asked Answered
N

2

6

I am trying to fetch data from two different Firebase tables. Here is the structure of table:

Post {
    1{
       pImages{
           i1:true
           i2:true
       }
    }
    2{
       pImages{
           i3:true

       }
    }
}
Images{
       i1{
          iUrl : ....
          pId : 1
         }
       i2{
          iUrl :...
          pId : 1
         }
       i3{
         iUrl:....
          pId : 2
         }
 }

I need to retrieve images corresponding to post with id = 1. The following is my implementation to retrieve images:

 func retrieveImagesForPost(postId: String,completion: (result: AnyObject?, error: NSError?)->()){
        var imgArray:[Image]=[]
        let postsRef = self.ref.child("post")
        let imagesRef = self.ref.child("image")
        let postImagesRef = postsRef.child(postId).child("pImages");
        postImagesRef.observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
            for item in snapshot.children{
                imagesRef.child(item.key).observeSingleEventOfType(.Value, withBlock: { (snap) in
                    let image = Image(snapshot: snap)
                    print(image)
                    imgArray.append(image)
                })
            }
            print(snapshot.key)
            print("called")
            completion(result:imgArray, error:nil)
        })
    }

But, the problem is I am not able to get all images in imgArray to be able to send to completion handler. Below is the output of calling retrieveImagesForPost with post id ==1.

pImages
called
<TestProject.Image: 0x7f9551e82000>
<TestProject.Image: 0x7f955466a150>

The images are retrieved after the completion handler is called. I tried the dispatch groups and the semaphores approach as described in the following post. But the results are still the same. How can I make completion handler to wait for all images to be fetched from Firebase?

Newscast answered 10/7, 2016 at 2:55 Comment(0)
A
5

Keep a counter that you increase as each image is loaded. Once the counter reaches the length of the snapshot.children list, you're done and call your completion handler.

let postImagesRef = postsRef.child(postId).child("pImages");
postImagesRef.observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
    var counter = 0
    for item in snapshot.children{
        imagesRef.child(item.key).observeSingleEventOfType(.Value, withBlock: { (snap) in
            let image = Image(snapshot: snap)
            print(image)
            imgArray.append(image)
            counter = counter + 1
            if (counter == snapshot.childrenCount) {
                completion(result:imgArray, error:nil)
            }
        })
    }
})

You should probably add some error handling in the above, but in general this approach is tried and tested.

Alverson answered 10/7, 2016 at 4:26 Comment(2)
how does your function change i would need to join 3 nodes (relationships) in FirebaseSyringe
if (counter == snapshot.childrenCount) throws an error!?Syringe
E
1

Another answer for this problem is to use GCD's DispatchGroup.

First you need to create a dispatch group with DispatchGroup. In this case, you need to manually tell the group when work is being started with enter() and when it's finished with leave(). Then the dispatch group's notify(queue:execute:) will execute the completion handler on the main queue.

Be careful! The number of enters and leaves must be balanced or the dispatch group's notify will never be called.

let dispatchGroup = DispatchGroup()

let postImagesRef = postsRef.child(postId).child("pImages");
postImagesRef.observeEventType(FIRDataEventType.value, withBlock: { (snapshot) in
    for item in snapshot.children{
        dispatchGroup.enter()
        imagesRef.child(item.key).observeSingleEventOfType(.value, withBlock: { (snap) in
            let image = Image(snapshot: snap)
            print(image)
            imgArray.append(image)
            dispatchGroup.leave()
        })
    }
})

dispatchGroup.notify(queue: DispatchQueue.main, execute: {
    completion(result: imgArray)
})
Everest answered 17/3, 2017 at 18:22 Comment(1)
This is much better than using a counter, as in the accepted answer. Though, for it to work properly, the dispatchGroup must be instantiated inside the first observe block, and notified after the for. Entering and leaving the group is correct.Aquaplane

© 2022 - 2024 — McMap. All rights reserved.