What does the snapshot/observer code do in firebase? - swift

When retrieving data from Firebase, we typically use the code below (or some reiteration of it)
...observeSingleEvent(of: .value, with: { snapshot in
var tempPosts = [PostModel]()
for child in snapshot.chidren{
}
return tempPosts
})
but I don't exactly get what this code is doing? Can someone explain it on a high level? I've tried printing data on mulitple spots but the only data I'm getting is: Snap post or [App.PostModel]

This code is used for observing data change in your database. You don't need to send requests from time to time for getting the latest data.
When the data changes, it will trigger the closure you given so that you can do things. For more reference, you could read this doc.
You could try this to covert snapshot into dictionary:
for child in snapshot.children {
let dataS = child as! DataSnapshot
let dict = dataS.value as? [String : AnyObject]
// handle the data
}

The code in your question uses .observeSingleEvent. What that means is that it's requesting data from Firebase immediately one time and will not observe any future changes or fire any other events.
The data is returned in in the closure as a 'snapshot' and is a 'picture' of what that data looks like at a point in time. (snapshot...picture? Pretty snappy huh)
Firebase data is only valid within the closure; any code following the closure will execute before Firebase has time to retrieve data from the server, so ensure you work with Firebase data inside that closure.
The for loop iterates over the child nodes within the snaphot one at a time. For example, the snapshot could contain child snapshots of each user in a /users node. You can then get the users data from each child snapshot.
The return statement should never be used within a asynchronous closure as you cannot return data (in that fashion) from a closure, so that line should be removed. You could however leverage an completion handler like this
func getUser(with userID: String, completion: #escaping ((_ user: UserClass) -> Void)) {
//get the user info from the snapshot, create a user object and pass it back
// via the completion
completion(user)
}
to work with the data outside the closure.

Related

How to store a Firebase ListenerRegistration object in UserDefaults?

I'm writing a wrapper for storing, retrieving, and removing a snapshot listener from UserDefaults. I'm having a few issues however. I'm attempting to store it by doing the following:
Encode the ListenerRegistration listener into Data
Update my activeListeners dictionary ([String:Data]) property with the new key + data
Store the updated activeListeners dictionary in UserDefaults
func storeListener(listener: ListenerRegistration, for objectId: String, atPath path: AppAPI.Path) {
let key = formatKeyForListener(objectId: objectId, path: path)
do {
let encodedListenerData = try NSKeyedArchiver.archivedData(withRootObject: listener, requiringSecureCoding: false)
activeListeners.updateValue(encodedListenerData, forKey: key)
UserDefaults.standard.set(activeListeners, forKey: Strings.listenersKey)
UserDefaults.standard.synchronize()
} catch {
print("Error encoding listener with key: \(key)\nError: \(error.localizedDescription).")
}
}
Unfortunately, I'm seeing the following error: Error: The data couldn’t be written because it isn’t in the correct format...
Firestore listeners do not persist between app launches. When the app process is killed by any means, all of the listeners are gone and do not come back when the app launches again. If you want the listeners to come back at next launch, you will need to write code to establish those listeners again, and decide what to do with any documents they produce. If that's what you want, then you should focus your efforts on figuring out how to represent those listeners in your storage system (maybe just a collection or document path will do), and reconstitute them at the time of launch.

Do I need multiple Firebase database references?

Lets say I have some code like this
let ref = Database.database().reference()
let refTwo = Database.database().reference()
func getPosts() {
ref.child("posts").queryOrderedByKey().observeSingleEvent(of: .value, with: { snap in
// get some posts
})
}
func getOtherStuff() {
refTwo.child("child").queryOrderedByKey().observeSingleEvent(of: .value, with: { snap in
// get some other data
})
refTwo.removeAllObservers()
}
And I call getPosts() and getOtherStuff() in viewDidLoad() do I need to use two different references or can I just use one ref for all of my queries?
I know if you have the same ref using .observe in two different locations the data is only returned once. So you wouldn't want to re-use that ref? However, here I am just using .observeSingleEvent so I'm not sure. Additionally, would it matter if they were on the same child?
Firebase database references are just lightweight references to locations in the database. Nothing happens until you either attach a listener or write to them.
There is no need to use separate listeners in the scenario you shared. I would remove the call to removeAllObservers: since you're calling observeSingleEvent, the observers are automatically removed after the first time they fire.
in Firebase 4.4 you need just use
var ref = Database.database().reference()
also you can see "Read and Write Data" in left list for basic structs.
example:
self.ref.child("users").child(user!.uid).setValue(["mentionName": ""])
self.ref.child("users").child(user!.uid).child("email").setValue(self.emailField.text)
reference: https://firebase.google.com/docs/database/ios/start

Getting an array from Firebase Database

I've a [String] Array under the "urls" key. How can i get it out? Here is my structure:
You can read it by using an observer to the urls reference and initializing an array with its value.
ref = Database.database().reference()
ref.child("sectionList").child("name of").child("urls")observe(.value, with: { (snapshot:FIRDataSnapshot) in
var urls : [String] = snapshot.children
}
"This function takes two parameters: an instance of FIRDataEventType and a closure.
The event type specifies what event you want to listen for. The code listens for a .value event type, which in turn listens for all types of changes to the data in your Firebase database—add, removed, and changed.
When the change occurs, the database updates the app with the most recent data.
The app is notified of the change via the closure, which is passed an instance of FIRDataSnapshot. The snapshot, as its name suggests, represents the data at that specific moment in time. To access the data in the snapshot, you use the value property."
Source:(https://www.raywenderlich.com/139322/firebase-tutorial-getting-started-2)
I got it like this
let ref = Database.database().reference(withPath: "sectionList")
ref.child("name of").child("urls").observeSingleEvent(of: .value, with: { (dataSnapshot:DataSnapshot) in
for object in dataSnapshot.children.allObjects as! [DataSnapshot] {
for obj in object.value as! NSArray {
print("value = \(obj)")
}
}
})
Basicly, I highly recomend you to take a look at this tutorial, especially the part about retrieving Data. To devide your project in standard MVC model will be very useful. I hope my answer will help you ;)

In what order does Swift evaluate functions?

I have a function that has a function within it in terms of a call to a firebase database.
The internal function sets the value of a variable from the wrapper function, but my output does not register this. When debugging it looks like this happens in reverse order. I'm new to swift and firebase so i'm just trying to get my head round this.
This is my function and output.
func checkIfUsernameExists(inUsername: String) -> String{
var aString = "false"
let ref = FIRDatabase.database().reference()
ref.child("-KohvyrIikykRsOP0XCx").observeSingleEvent(of: .value , with: {(snapshot) in
if snapshot.hasChild(self.username.text!){
print ("*** username already exists")
aString = "true"
}
})
print ("*** value of aString is: ", aString)
return aString
}
output is:
*** value of aString is: false
*** username already exists
Edit:
I phrased my question poorly i think.
What i meant to ask was how can i get the call back from firebase before processing the information its collected. I've bounced round SO and lots of blogs all pointing to Async, GCD and Completion handlers. None of which seemed to work or were easy enough for noob to get their head round.
Needless to say i've found my answer here.
Firebase Swift 3 Completion handler Bool
This is what i used:
func checkIfUsernameExists(userid: String, completionHandler: #escaping ((_ exist : Bool) -> Void)) {
let ref = FIRDatabase.database().reference()
ref.child("-KohvyrIikykRsOP0XCx").observeSingleEvent(of: .value , with: {(snapshot) in
if snapshot.hasChild(self.username.text!){
self.usernameCheck = "true"
completionHandler(true)
}
else {
self.usernameCheck = "false"
completionHandler(true)
}
})
}
It seems like that you are trying to get a value from firebase database. As you may know. data in your firebase database is stored remotely. This means that you need a relatively large amount of time to et your data, when compared with getting data from the disk.
Since it takes so long, the designers of the firebase API thought "Instead of making the UI wait and become unresponsive, we should fetch the data asynchronously. This way, the UI won't freeze."
In your code, this block of code:
if snapshot.hasChild(self.username.text!){
print ("*** username already exists")
aString = "true"
}
will be executed after it has fetched the data, which is some time later. Since the operation is done asynchronously, the rest of the function will continue to be executed, namely this part:
print ("*** value of aString is: ", aString)
return aString
And at this stage the closure hasn't been finished executing yet, aString is false.
This might sound counter intuitive, but think about it this way, you are calling a method here - observeSingleEvent. The code in the closure is meant to be a parameter. You are just telling the method to "Run this when you're done fetching the data". The data is fetched asynchronously, so the code after that executes first.

Firebase: observing childAdded returns existing / old records?

I have a query (written in swift):
FIRDatabase.database().reference(withPath: "\(ORDERS_PATH)/\(lId)")
.child("orders")
.observe(.childAdded, with: { firebaseSnapshot in
let orderObject = firebaseSnapshot.value as! [String: AnyObject]
let order = AppState.Order(
title: orderObject["name"] as! String,
subtitle: orderObject["item_variation_name"] as! String,
createdAt: Date(timeIntervalSince1970: TimeInterval(orderObject["created_at"] as! Int / 1000)),
name: "name",
status: AppState.Order.Status.Pending
)
f(order)
})
My database looks like this:
I want it to just listen all NEW incoming orders. However, every time it initially loads it fetched a bunch of existing orders with it, which isn't what I want.
I do have a created_at (an int that represents that time e.g. 1478637444000) on each of the orders, so if there's a solution that can utilize that that works too.
Is there something wrong with my query?
Observers always fire once and read in "all the data".
A .value observer reads in everything in the node at once, where as a .childAdded observer (from the docs)
This event is triggered once for each existing child and then again
every time a new child is added to the specified path.
If you think about your question, what you are actually looking for is a sub-set of all of your data. This is achieved via a query.
Your query will be to observe all .childAdded events that occur after a certain timestamp. So that's what you need to do!
Craft an observe query leveraging queryStartingAtValue(the timestamp to start at) which will return all children added after that timestamp.
Swift3 Solution:
Just write multiple observers.
You can retrieve your previous data through the following code:
queryRef?.observeSingleEvent(of: .value, with: { (snapshot) in
//Your code
})
observeSingleEvent of type .value just calls once and retrieves all previous data
and then observe the new data through the following code.
queryRef?.queryLimited(toLast: 1).observe(.childAdded, with: { (snapshot) in
//Your Code
})
queryLimited(toLast: 1) with bserving of type .childAdded is being called every time that a new data is available