Home > Software engineering >  Firebase realtime database: load & monitor children (Swift) - best practice?
Firebase realtime database: load & monitor children (Swift) - best practice?

Time:03-01

This seems like it ought to have a very simple answer.

I am building an app using SwiftUI and firebase realtime database. The database will have a node called items with a large number of children - on the order of 1,000. I'd like the app to load the contents of that node children when it launches, then listen to firebase for future additions of children. Imagine the following:

struct Item { … }   // some struct
var items: [Item] = []
let itemsRef = Database.database().reference().child("items")

According to firebase, this call ought to load in all the children one at a time, and then add new ones as they are added to the items node in firebase:

itemsRef.observe(.childAdded) { snapshot in
   // add a single child based on snapshot
   items.append(Item(fromDict: snapshot.value as! [String:String])
}

That gets the job done, but seems hugely inefficient compared to using the getData() method they supply, which hands us a dictionary containing all the children:

itemsRef.getData() { error, snapshot in
   // set all children at once base on snapshot
   items = Item.itemArray(fromMetaDict: snapshot.value as! [String:[String:String]])
}

It would seem best to use getData() initially, then observe(.childAdded) to monitor additions. But then how do we prevent the observe completion block from running 1,000 times when it fires up? The firebase docs say that that's what will happen:

This event is triggered once for each existing child and then again every time a new child is added to the specified path.

Thanks in advance!

PS I didn't think it necessary to include definitions for the functions Item.init(fromDict:) or Item.itemArray(fromMetaDict:) — hopefully it's clear what they are meant to do.

CodePudding user response:

There is (or: should be) no difference between listening for .childAdded on a path versus listening for .value or calling `getData() on that same path. The distinction is purely client-side and the wire traffic is (or: should be) the same.

It is in fact quite common to listen for .child* events to manipulate some data structure/UI, and then also listen for the .value event to commit those changes, as .value is guaranteed to fire after all corresponding .child*.


A common trick to do the first big batch of data in .value or getData(), and then use .child* for granular updates is to have a boolean flag to indicate whether you got the initial data already, and set if to false initially.

In the .child* handlers, only process the data if the flag is true.

itemsRef.observe(.childAdded) { snapshot in
   if isInitialDataProcessed {
       items.append(Item(fromDict: snapshot.value as! [String:String])
    }
}

And then in your .value/getData handler, process the data and then set the flag to true.

  • Related