Swift 5, How to Execute Code After Fetching All Childadded from Firebase

Swift 5, how to execute code after fetching all childadded from Firebase

One option is to leverage that Firebase .value functions are called after .childAdded functions.

What this means is that .childAdded will iterate over all childNodes and then after the last childNode is read, any .value functions will be called.

Suppose we want to iterate over all users in a users node, print their name and after the last user name is printed, output a message that all users were read in.

Starting with a simple structure

users
uid_0
name: "Jim"
uid_1
name: "Spock"
uid_2
name: "Bones"

and then the code that reads the users in, one at a time, prints their name and then outputs to console when all names have been read

var initialRead = true

func readTheUsers() {
let usersRef = self.ref.child("users")

usersRef.observe(.childAdded, with: { snapshot in
let userName = snapshot.childSnapshot(forPath: "name").value as? String ?? "no name"
print(userName)

if self.initialRead == false {
print("a new user was added")
}
})

usersRef.observeSingleEvent(of: .value, with: { snapshot in
print("--inital load has completed and the last user was read--")
self.initialRead = false
})
}

and the output

Jim
Spock
Bones
--inital load has completed and the last user was read--

Note this will leave an observer on the users node so if a new user is added it will print their name as well.

Note Note: self.ref points to my root firebase reference.

Firebase observe with type .childAdded retrieves all my information every time. Please assist

A few ways to do this:

  • Use ref.queryLimitedToLast(1) when you start. See Firebase, only get new children.
  • keep track of the most recent key you've got in your local storage and then query with queryOrderByKey().queryStartingAtValue(latestKey)
  • Add a timestamp to the items and then queryOrderByChild("timestamp").queryStartingAt(now) for items with timestamp >= now. See how to discard initial data in a Firebase DB

Fetch data from firebase using childAdded in firebase

I am shocked no one was able to call out the issue in your original .childAdded function.

let post = snapShot.value as? String is the problem.

Firebase Database is a key-value pairing so you can't just cast the whole result into a String. You fixed this problem by casting it into a dictionary when doing .value. Do the same thing in the .childAdded function.

I had a similar answer explaining dictionary handling in Firebase.

How to retrieve data from Firebase Real-Time Databse?

Let me see if I can get you going the right direction. There are a couple parts to this question so I am drilling down to the Firebase part. If the tableView is not showing after that, that's a tableView delegate issue but it looks like the code you have is ok.

First issue to address is the Firebase structure - well, not the stucture but the keys. From the docs

If you create your own keys, they must be UTF-8 encoded, can be a
maximum of 768 bytes, and cannot contain ., $, #, [, ], /, or ASCII
control characters 0-31 or 127. You cannot use ASCII control
characters in the values themselves, either.

So you can see your keys have illegal characters in them. It's generally best practice to allow Firebase to create the keys with .childByAutoId().

Looking at the ProductList class, it's really fine but you're probably going to also want to keep track the Firebase key. So for example a user selects it, you'll know which item it is. So I just added a key property

class ProductList {
var key: String
var name: String
var price: String
var weight: String
var imageUrl: String

init(withSnapshot: DataSnapshot) {
self.key = withSnapshot.key
self.name = withSnapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
self.price = withSnapshot.childSnapshot(forPath: "price").value as? String ?? "No Price"
self.weight = withSnapshot.childSnapshot(forPath: "weight").value as? String ?? "No Weight"
self.imageUrl = withSnapshot.childSnapshot(forPath: "imageURL").value as? String ?? "No ImageUrl"
}
}

As mentioned in a comment, the child node names are case sensitive so I also updated the code to read the imageURL node (to match your structure) instead of imageUrl.

Then a class array to store the Products (as shown in your code)

var feeds = [ProductList]()

Then the code to actually read the nodes and populate the feeds array.

func readProducts() {
let productsRef = self.ref.child("Products") //self.ref points to my firebase
productsRef.observe(.childAdded, with: { snapshot in
let aProduct = ProductList(withSnapshot: snapshot)
self.feeds.append(aProduct)
})
}

The thing to note here is that we are reading using observe.childAdded. The .childAdded iterates over each child node, one at a time initially, and the .observe leaves an observer on that node that will call the code in the closure any time a new child is added.

Then at some later time, we want to see what's in the array

func printProducts() {
for product in self.feeds {
print(product.key, product.name)
}
}

and then the output when printProducts is called

dfkjsv4qc22aWE Apples
se04Fws444 Flower

I removed the illegal characters from the structure for this answer but again .childByAutoId() is your friend. It's always valid, always unique and separates the key from the data it contains.

Last thought on this is: when do I reload my tableView. There are a couple of options but one is to read the data once with .observeSingleEvent by .value and then iterate over the child nodes in code, populating the array (kind of what you have in your question) and then reload the tableView.

The second option is to leverage that .value events are always fired LAST, after other events such as .childAdded. So you could use childAdded to populate the array but also add a .value event that will fire after all the child nodes are added, to then reload your tableView. There are a number of examples of that process here on SO. Oh, here's one now

Run Code after all children are added

Calling then function after fetching data from firebase in ionic 3

The Firebase on() method can fire multiple times: once when it initially loads the data, and again whenever the data changes. Since a promise (the thing you call then() on) can only resolve once, on() can't return a promise.

There are two options here:

  1. You want to only load the data once.

    If this is the case, you should use Firebase's once() method, which does return a promise.

    this.oAngularFireDatabase.database.ref('Users').orderByKey()
    .once('value').then(snapshot => {
    if (snapshot.hasChildren()) {
    snapshot.forEach(innerSnap => {
    if (innerSnap.hasChild(user.uid)) {
    //User role key
    this.loggedInUserUserRoleKey = innerSnap.key;
    //User id
    this.loggedInUserId = user.uid;

    //User name
    this.loggedInUserName = innerSnap.child(user.uid).child("user_name").val();

    if (innerSnap.child(user.uid).hasChild("user_image")) {
    //User Image
    this.loggedInUserImage = innerSnap.child(user.uid).child("user_image").val();
    }
    return false;
    }
    })
    }
    }).then(value => {
    // TODO: perform subsequent action on boolean value
    })
  2. You want to listen for changes on the data too.

    If this is the case, you should put the subsequent action you want to take into the on() callback:

    this.oAngularFireDatabase.database.ref('Users').orderByKey()
    .on('value', snapshot => {
    if (snapshot.hasChildren()) {
    snapshot.forEach(innerSnap => {
    if (innerSnap.hasChild(user.uid)) {
    //User role key
    this.loggedInUserUserRoleKey = innerSnap.key;
    //User id
    this.loggedInUserId = user.uid;

    //User name
    this.loggedInUserName = innerSnap.child(user.uid).child("user_name").val();

    if (innerSnap.child(user.uid).hasChild("user_image")) {
    //User Image
    this.loggedInUserImage = innerSnap.child(user.uid).child("user_image").val();
    }
    }
    })
    // TODO: perform subsequent action on data
    }
    })

Note that both of these operations look pretty expensive for what they're trying to accomplish: scanning a JSON tree for a specific value is an anti-pattern in Firebase, and typically means you should modify/augment your JSON to allow a direct lookup or query.

For example, I suspect you now have a structure like /Users/$randomkey/$uid: { ..user data... }. For better performance, consider storing the user data directly under their UID: /Users/$uid: { ..user data... }. This removes the need for a query, and allows you to directly load the data for a user from this.oAngularFireDatabase.database.ref('Users').child(user.uid).

Trouble retrieving data from Firebase using .Value/.Child added in swift

Im silly-- had some extraneous code after the block which was throwing things off. DOH! For anyone experiencing issues, first place to check! also .ChildAdded paired with observeEventType did the trick. (takes all previous data already in DB + adds new data as it comes in).

self.databaseRef.child("App_Queue/\(Category1)").queryLimitedToLast(15).observeEventType(.ChildAdded, withBlock: { (snapshot) in
...

Firebase doesn't call back when using '.childAdded'

If you are observing messages from all users at the same time I'd recommend restructuring your Firebase data a bit. Make messages a top-level object, and then you can just observe that whole section. I don't know what your messages look like, but I assume if you do this you will need to add a reference to the user inside the message so that you can get the new message added and still see which user wrote it.



Related Topics



Leave a reply



Submit