Okay I am reading from a database and when I print the individual variables they print out correctly. However it seems like the data refuses to append to the array. Anyone know why? I can't figure it out at all.
let commuteBuilder = Commutes()
Database.database().reference().child("Users").child(user).child("Trips").observe(DataEventType.childAdded, with: { (snapshot) in
//print(snapshot)
if let dict = snapshot.value as? NSDictionary {
commuteBuilder.distance = dict["Distance"] as! Double
commuteBuilder.title = dict["TripName"] as! String
commuteBuilder.transportType = (dict["Transport"] as? String)!
}
commuteArray.append(commuteBuilder)
})
print("helper")
print(commuteArray.count)
return commuteArray
The data is correctly added to the array, just not at the time that you print the array's contents.
If you change the code like this, you can see this:
let commuteBuilder = Commutes()
Database.database().reference().child("Users").child(user).child("Trips").observe(DataEventType.childAdded, with: { (snapshot) in
if let dict = snapshot.value as? NSDictionary {
commuteBuilder.distance = dict["Distance"] as! Double
commuteBuilder.title = dict["TripName"] as! String
commuteBuilder.transportType = (dict["Transport"] as? String)!
}
commuteArray.append(commuteBuilder)
print("added one, now have \(commuteArray.count)")
})
print("returning \(commuteArray.count)")
return commuteArray
You'll see it print something like this:
returning 0
added one, now have 1
added one, now have 2
etc.
This is likely not the output you expected. But it is working as intended. Firebase loads data from its database asynchronously. Instead of blocking your code, it lets the thread continue (so the user can continue using the app) and instead calls back to the code block you passed to observe when new data is available.
This means that by the time this code returns the array it is still empty, but it later adds items as they come in. This means that you cannot return data from a function in the way you are trying.
I find it easiest to change my way of thinking about code. Instead of "First get the data, then print it", I frame it as "Start getting the data. When data comes back, print it".
In the code above, I did this by moving the code that prints the count into the callback block. Instead of doing this, you can also create your own callback, which is called a completion handler or closure in Swift. You can find examples in this article, this article, this question Callback function syntax in Swift or of course in Apple's documentation.
Related
I am using Firebase's Realtime database in my app. I am fetching data from the database and do some change and after that I am removing the observer which is not working fine.
I have some data in Realtime Database like this:
I am using firebase's observe(.value) function to get this value and after that I am updating an entry and then I am removing the observer. This is my code:
func updatePoints() {
let firebaseId = UserDefaults.standard.value(forKey: "firebaseId") as? String ?? ""
let reference = self.database.child("Points").child(firebaseId)
var handler : UInt = 0
handler = reference.observe(.value, with: { snapshot in
guard let userPoints = snapshot.value as? [String : Any] else {
print("no points data found")
return
}
let pointsLeft = userPoints["points_left"] as? Int ?? 0
reference.child("points_left").setValue(pointsLeft - 1)
reference.removeObserver(withHandle: handler)
})
}
The problem now is, this observer runs twice. For example, if "points_left" : 10, then after this function the points left will have 8 value but it should have 9 instead. It is running twice and I am not understanding why is it doing so as I am using removeObserver. Can someone help me with this?
The reason to the above unexpected behaviour is the setValue function you called to update the points is triggering another .value event in the database. Then it triggers the observer again. Therefore, by the time you remove the observer, it has already triggered twice. This leads to decrease of points by 2 instead of 1.
So if u interchange the last two lines, by the time you call the setValue function observer is removed. So it will not get triggered for the second time.
I'm a little stuck with something small but that is giving me some headaches! I have a Realtime Database and I am able to retrieve the information I need from it. My only problem is that instead of printing for example (ex.: 200) is printing (ex.: [200])!
This is my code:
func readData() {
FirebaseDatabase.Database.database().reference().child("Available_Funds").observeSingleEvent(of: .value, with: { snapshot in
guard let value = snapshot.value as? [String: Any] else {
return
}
let amountWallet = value.values
print(amountWallet)
self.currentBalanceLabel.text = "$" + "\(amountWallet)"
print("\(value)")
})
}
Right now what I get printed with this code is $[200] for example, instead of just $200, which is what I intend to get.
Tried looking online, but no luck with this! Does someone know how to remove these square brackets from printing?
values is an Array -- thus the []. When you say value.values, you're asking for all of the values of the key/value pairs in snapshot.value.
If you intend to get a single value from it, you would use amountWallet[0] to get the first element. Keep in mind that this will crash if amountWallet has 0 elements (arrays are zero indexed).
amountWallet.first will give you an Optional that will be safe to use, but you would need to unwrap it for printing:
let amountWallet = value.values
if let singleAmount = amountWallet.first {
print(singleAmount)
self.currentBalanceLabel.text = "$" + "\(singleAmount)"
}
You're calling it back as an array of strings [String: Any]
You can either change this (remove []) or access the first element in the array: amountWallet[0].
I'm trying to get my firebase database data into a variable to use it in my project
var someArray = [Array]()
let dbRef = Database.database().reference().child("SomeDatabase")
func loadSomeDatabaseData {
dbRef.observeSingleEvent(of: .value) { (snapshot) in
let someDict = snapshot.value as! [String:Any]
let keysOfSomeDict = Array(someDict.keys)
self.someArray.append(contentsOf: keysOfSomeDict)
self.collectionView?.reloadData()
}
}
I've tried calling loadSomeDatabaseData() in my viewDidload, followed by printing someArray, which results in an empty array. I know the keysOfSomeDict array has the correct data that I want, since i tried printing this array directly inside the closure. I would however also like to be able to print and use this data elsewhere in my app.
The Firebase observeSingleEvent method is asynchronous method. it executive in background only because it take time to fetch data from Firebase.
if you print array immediately means you get only empty array.
so print array once you get the data from Firebase. for that you can use escaping closure
Function Declaration:
func loadSomeDatabaseData(resultArray : #escaping([Array])->()) {
dbRef.observeSingleEvent(of: .value) { (snapshot) in
let someDict = snapshot.value as! [String:Any]
let keysOfSomeDict = Array(someDict.keys)
self.someArray.append(contentsOf: keysOfSomeDict)
resultArray(self.someArray)
}
}
Func Call:
self.loadSomeDatabaseData{(firebaseReposne) in
print("FirebaseData" , firebaseReposne) // Hope here you will get your firebase data.
self.collectionView?.reloadData()
}
The observeSingleEvent is asynchronous. So immediately printing someArray after calling loadSomeDatabaseData will result in an empty array. It takes sometime to retrieve the data from Firebase api.
To use this data elsewhere in the app, you could set a flag indicating the data is loaded or send a notification to inform the data is available.
So this is my Firebase Structure:
I'm trying to get all books pictures (bookImage), add them to list and then use this list to fill a table or anythings else. (I'm using swift 3)
struct item {
let picture: String!}
var items = [item]()
func getLatestAddedItems(){
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Items").observe(.childAdded, with: {
FIRDataSnapshot in
let picture = (FIRDataSnapshot.value as? NSDictionary)?["bookImage"] as? String ?? ""
//self.items.insert(item(picture: picture), at: 0)
self.items.append(item(picture: picture))
print(self.items[0].picture)
print(self.items[1].picture) // error here
})}
I'm able to see the first print output but on the second one I'm getting fatal error: Index out of range even I have 3 books on my database.
Since your using .childAdded, it iterates through that closure for each object in the data tree, in this case, each book. When you try to print the second picture, its still in its first iteration. Meaning you only have retrieved the first book so far. That's why you can print the first book item but not the second one. If you moved the print statements outside of the closure, and then did the print statements after the closure iterated over all three books, you wouldn't get the error.
Don't change it to .value unless if every time a new one is subsequently added you want to get the entire list of books all over again. If its a large amount of books, it will be a lot of data to go through each time.
Summary: .childAdded gives you one book at a time, with a new snapshot for each one. .value gives you all the books in one snapshot, then you must iterate over them yourself in the closure. ex.
for snap in snapshot.children {
// now you can do something with each individual item
}
also I just noticed your using the FIRDataSnapshot type in your closure, that should be a variable which represents the snapshot you received, not the type itself. Change "FIRDataSnapshot in" to something like "snapshot in" snapshot is a representation of what information was given to you by the observe closure, in this case, an object with a type of FIRDataSnapshot.
Edit:
Your solution you mentioned below works fine, but I'll add an alternative that is cleaner and easier to use.
add an init method to your Book class that takes a FIRDataSnapshot as the init parameter, then init the object when you query Firebase:
struct Book {
let bookImageString: String
init?(snapshot: FIRDataSnapshot) {
guard let snap = snapshot.value as? [String : AnyObject], let urlString = snap["bookImage"] else { return nil }
bookImageString = imageString
{
{
then when you query firebase you can do this:
for snap in snapshot.children {
if let snap = snap as? FIRDataSnapshot, let book = Book(snapshot: snap) {
self.items.append(book)
{
}
doing it this way cleans up the code a little bit and leaves less chance of error in the code.
Also, since your using .value, make sure to empty the data source array at the beginning of the closer, or else you will get duplicates when new books are added.
items.removeAll()
Finally I'm posting the solution:
func getLatestAddedItems(){
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Items").observe(.value, with: {
snapshot in
//self.items.insert(item(picture: picture), at: 0)
for childSnap in snapshot.children.allObjects {
let snap = childSnap as! FIRDataSnapshot
print(snap.key)
let picture = (snap.value as? NSDictionary)?["bookImage"] as? String ?? ""
print(picture)
}
})
}
I just want to ask about firebase retrieve data. How can i handle firebase retrieve data finished? I don't see any completion handler.
I want to call some function after this firebase data retrieve finished. How can i handle???
DataService.ds.POST_REF.queryOrderedByChild("created_at").observeEventType(.ChildAdded, withBlock: { snapshot in
if let postDict = snapshot.value as? Dictionary<String, AnyObject> {
let postKey = snapshot.key
let post = Post(postKey: postKey, dictionary: postDict)
self.posts.append(post)
}
})
In Firebase, there isn't really a concept of 'finished' (when listening to 'child added'). It is just a stream of data (imagine someone adds a new record before the initial data is 'finished'). You can use the 'value' event to get an entire object, but that won't give you new records as they're added like 'child added' does.
If, you really need to use child added and get notified when it's probably finished, you can set a timer. I don't know swift, but here's the logic.
Set up your 'child added' event.
Set a timer to call some finishedLoading() function in 500ms.
Each time the 'child added' event is triggered, destroy the timer set in step two and create another one (that is, extend it another 500ms).
When new data stops coming in, the timer will stop being extended and finsihedLoading() will be called 500ms later.
500ms is just a made up number, use whatever suits.
Do one request for SingleEventOfType(.Value). This will give you all info initially in one shot, allowing you to then do whatever function you want to complete once you have that data.
You can create a separate query for childAdded and then do anything there you want to do when a new post has been added
Write your entire block of code in a function which has a completion handler like so:
func aMethod(completion: (Bool) -> ()){
DataService.ds.POST_REF.queryOrderedByChild("created_at").observeEventType(.ChildAdded, withBlock: { snapshot in
if let postDict = snapshot.value as? Dictionary<String, AnyObject> {
let postKey = snapshot.key
let post = Post(postKey: postKey, dictionary: postDict)
self.posts.append(post)
}
completion(true)
})
}
Then call it somewhere like so:
aMethod { success in
guard success == true else {
//Do something if some error occured while retreiving data from firebase
return
}
//Do something if everything went well.
.
.
.