Unable to access variable inside closure swift - swift

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.

Related

Retrieve child entry from Firebase Realtime Database without []. - Swift 5

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].

Firebase Checking If Username is Taken [duplicate]

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.

I need help getting values out of a snapshot from firebase

My Data in Firebase looks like this:
I am retrieving the data with
var ref: DatabaseReference
ref = Database.database().reference()
ref.child("peopleReminders").child("-LBO0TMbOM0cwd5TMMiP").observe(.value) { snapshot in
for value in snapshot.children {
print(value)
}
}
This prints the following:
Snap (0) r04
Snap (1) r02
Snap (2) r01
This should be an array. What I need to do is get the values R04, R02, and R01 out of it.
You don't need a loop or enumeration to parse it into an array as Firebase supports both Array and Dictionary. You can directly cast the result into an array. Below piece of code will work:
ref.child("peopleReminders").child("-LBO0TMbOM0cwd5TMMiP").observeSingleEvent(of: DataEventType.value) { (snapshot) in
if snapshot.exists() {
let result: [String] = snapshot.value as? [String] ?? []
print(result)
}
}
Note: Always check snapshot existence it will be false if data doesn't exist for the requested reference. Use observeSingleEvent when you need to fetch the data for once if you use observe the call back will be call many times whenever there is a change in requested node so that delete, insert, update etc.
I replaced for value in snapshot.children with the following code.
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot {
print(rest.value!)
}
The new output is now:
R04
R02
R01

Multiple async request in nested for loops

I'm trying to get data from my database and after obtaining a piece of data, using that piece of data to find a new piece of data.
At the end, I can piece these together and return the derived data. I'm not sure this is the best way to approach this, but this is where im at as of now.
My problem is that each call to the database (Firebase) is async and therefore I need to somehow wait for the async to finish, before going on.
I've looked at dispatch group and heres what I have so far:
let taskGroup = DispatchGroup()
for buildingKey in building.allKeys
{
var aprt = NSDictionary()
taskGroup.enter()
// ASYNC REQUEST
getAbodesWithUID(UID: buildingKey as! String, callback: { (success, abodes) in
aprt = abodes
taskGroup.leave()
})
taskGroup.enter()
for key in aprt.allKeys
{
// ASYNC REQUEST
getTenantsWithAprt(UID: key as! String, callback: { (success, user) in
for userKey in user.allKeys
{
let dict = NSMutableDictionary()
dict.setValue(((building[buildingKey] as? NSDictionary)?["Address"] as? NSDictionary)?.allKeys[0] as? String, forKey: "Building")
dict.setValue((user[userKey] as? NSDictionary)?["Aprt"], forKey: "Number")
dict.setValue((user[userKey] as? NSDictionary)?["Name"], forKey: "Name")
dict.setValue(userKey, forKey: "UID")
dict.setValue((user[userKey] as? NSDictionary)?["PhoneNumber"], forKey: "Phone")
apartments.append(dict)
}
taskGroup.leave()
})
}
}
taskGroup.notify(queue: DispatchQueue.main, execute: {
print("DONE")
callback(true, apartments)
})
I can't seem to get it to callback properly
First, you should be iterating over aprt.allKeys inside of the callback for getAbodesWithUID, other wise, when the for loop executes aprt will be an empty dictionary.
Secondly, the taskGroup.enter() call above that for loop should be inside of the for loop, because it needs to be called once for every key. It should be placed where the // ASYNC REQUEST comment currently is.
This is precisely what "promises" are for is for. They are available in Swift via a 3rd party add-in. A popular way to do this is to push all your reads/gets into an array. Then you promise.all(yourArray) which returns the array of results/values that you then iterate over to get at each one.
From this other answer:
You can look into when which may provide what you need and is covered
here.
Use the loop to put your promises into an array and then do something
like this:
when(fulfilled: promiseArray).then { results in
// Do something
}.catch { error in
// Handle error
}

How can my code know when Firebase has finished retrieving data?

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.
.
.
.