I have a Firebase database that is modeled as such:
: users
: some-random-id-1
- username:"user1"
- email:"email1#gmail.com"
: some-random-id-2
- username:"user2"
- email:"email2#gmail.com"
I am trying to iterate through all the users in the dictionary of data and append the username into a list in the file to be used for other purposes. I created a string array (userslist) variable and in viewdidload(), I wrote the following code below:
ref = Database.database().reference()
ref?.observe(.value, with: { (snapshot) in
let dataDict = snapshot.value as! NSDictionary
let x = dataDict["users"] as! NSDictionary
print(x)
print("--------")
for user in x{
let y = user.value as? [String: String]
let z = y!["username"]
print(z)
self.userslist.append(z!)
print(self.userslist)
print("NEXT")
}
})
print(self.userslist)
Inside the brackets of the snapshot, when I print self.userslist, I can see that each element is getting added, but when I print it a final time outside of the brackets, it shows it as an empty array. I think that the elements are only appended in the scope of those brackets so I cant access the filled array anywhere else. How do I get around this so I can use the data I appended?
you are using print(self.userslist) outside the observer and Firebase run in Async Mode
So, if you make use of breakpoints you will notice that
print(self.userslist) is Called before the control reach onside the Database handler ,
data is getting fetched you need to load your views inside that handler using Dispatch main queue
ref?.observe(.value, with: { (snapshot) in
let dataDict = snapshot.value as! NSDictionary
let x = dataDict["users"] as! NSDictionary
print(x)
print("--------")
for user in x{
let y = user.value as? [String: String]
let z = y!["username"]
print(z)
self.userslist.append(z!)
print(self.userslist)
print("NEXT")
}
/// Here is your data
print(self.userslist)
})
/// Called before Handler execution
print(self.userslist)
Related
So I'm running into a problem where I can't seem to save the contents of a local array outside of a for loop. This code iterates through notifsTop (which is a dictionary) and saves the values into another array. However, outside of the for loop, the contents of tempnotifsarray are empty, which doesn't make sense to me since I appended them to an array that is outside of the loop block. I have been struggling with this for some time and can't figure out what is going on. Any help would be appreciated, thanks!
func createArray() -> [notificationLabel] {
let newUserInfo = Auth.auth().currentUser
let uid = newUserInfo?.uid
self.ref = Database.database().reference()
let practionerRef = self.ref.child("users").child(uid!)
var tempnotifsArray: [notificationLabel] = []
practionerRef.observeSingleEvent(of: .value, with: {(snapshot) in
let value = snapshot.value as? NSDictionary
if let notifsTop = value?["Notifications"] as? NSDictionary { //top of the notifications hierarchy
for (_, myValue) in notifsTop {
// Iterate in here
//self.notifications.append(myValue)
let notification = notificationLabel(label: myValue as! String)
tempnotifsArray.append(notification)
//if I print here the array is full with the values I want
}
}
})
print(tempnotifsArray) //comes out as []
return tempnotifsArray
}
I have a observeSingleEvent in my viewDidLoad function and I want to change it into a observe with .childAdded so that it will listen constantly and add the objects whenever they get added to the database.
Here's the code that reads from firebase:
let parentRef = Database.database().reference().child("Recipes")
parentRef.observeSingleEvent(of: .value, with: { snapshot in
// PROCESSES VALUES RECEIVED FROM SERVER
if ( snapshot.value is NSNull ) {
// DATA WAS NOT FOUND
print("– – – Data was not found – – –")
} else {
// DATA WAS FOUND
for user_child in (snapshot.children) {
let user_snap = user_child as! DataSnapshot
let dict = user_snap.value as! [String: String?]
// DEFINE VARIABLES FOR LABELS
let recipeName = dict["Name"] as? String
let recipeDescription = dict["Description"] as? String
food.append(Element(name: recipeName!, description: recipeDescription!))
self.tableView.reloadData()
}
}
})
Another problem that I have now (besides that it only loads once) is that whenever I go to another view in the app and then come back it reads everything again so I get duplicates of everything in my tableView, will that still happen with the observe?
Edit:
Here is what my database looks like:
Recipes
-Kv7FAqgLtDrRoyGd-99
Description: "food description"
Name: "food name"
-KvBuzMUnIQXn8gpG2WL
Description: "food description2"
Name: "food name2"
-KvH6yYeJaThK7oP8xBj
Description: "food description3"
Name: "food name3"
Change observeSingleEvent to observe.
Empty food array whenever observing new values.
Reload your food array outside your for-loop so that you only reload whenever you have loaded all of your items into your array.
let parentRef = Database.database().reference().child("Recipes")
// 1. Change to observe
parentRef.observe(.value, with: { snapshot in
// PROCESSES VALUES RECEIVED FROM SERVER
if ( snapshot.value is NSNull ) {
// DATA WAS NOT FOUND
print("– – – Data was not found – – –")
} else {
// 2. Empty food array
self.food = []
// DATA WAS FOUND
for user_child in (snapshot.children) {
let user_snap = user_child as! DataSnapshot
let dict = user_snap.value as! [String: String?]
// DEFINE VARIABLES FOR LABELS
let recipeName = dict["Name"] as? String
let recipeDescription = dict["Description"] as? String
food.append(Element(name: recipeName!, description: recipeDescription!))
}
// 3. reload tableview outside loop
self.tableView.reloadData()
}
})
By emptying your food array whenever observing new values you ensure to only show the values that are present in your database and thus never having repetitive elements.
I'm trying to pass data that I've retrieved from my firebase database into a field of my singleton. The data is received via closure, and in that closure I'm passing some data into my singleton's properties. If I print the data structure inside the closure (after everything's been assigned) I get the output I'm expecting, but if I print it at the end of the initializer after all the data should've been passed in, it's empty.
import Foundation
import Firebase
class EmployeeList {
static let sharedInstance = EmployeeList()
var employeeDictionary: [String: [EmployeeData]]
var ref: DatabaseReference!
private init() {
employeeDictionary = [String: [EmployeeData]]()
ref = Database.database().reference()
ref.child("employeeList").observeSingleEvent(of: .value, with: { snapshot in
if let dictionary = snapshot.value as? [String: [String: AnyObject]] {
for subsection in dictionary {
var subsectionEmployees: [EmployeeData] = []
for item in subsection.value {
self.ref.child("employeeList/\(subsection.key)/\(item.key)").observeSingleEvent(of: .value, with: { employeeSnapshot in
let employeeObject = EmployeeData(snapshot: employeeSnapshot)
subsectionEmployees.append(employeeObject)
self.employeeDictionary[subsection.key] = subsectionEmployees
//print(self.employeeDictionary) This print statement prints out the expected data every time another employee is appended
})
}
}
}
//print(self.employeeDictionary) This print statement prints an empty data structure
})
}
}
get data from Firebase as Below
var messagedata = [String:AnyObject]()
let databaseReff = Database.database().reference().child("message")
databaseReff.queryOrdered(byChild: "fromId").queryEqual(toValue: self.recieverId).observe(.value, with: { snapshot in
if snapshot.exists(){
self.messagedata = snapshot.value! as! [String : AnyObject]
self.getAllMessagesSent(snapshot: self.messagedata)
} else
self.getAllMessagesSent(snapshot: self.messagedata) //Function Created
}
})
pass the data fetched from Clousre to a dictionary and pass that dict to a function and do anything you want to do or use escaping blocks
func getAllMessagesSent(snapshot: [String:AnyObject]) {
//data is here
}
I am trying to fetch data from Firebase and add it as a object in a Swift array but whenever I run the application the array prints out empty. When I put a print statement inside the observe each purse prints out like I want it to but the purse never gets appended to the array.
var purses = [Purse]()
override func viewDidLoad() {
fetchPurse(completion: {print(self.purses.count)})
}
func fetchPurse(completion: #escaping () -> ()){
let ref = FIRDatabase.database().reference(fromURL: "https://test-database-ba3a2.firebaseio.com/")
let user = (FIRAuth.auth()?.currentUser?.uid)!
let userRef = ref.child("users").child(user).child("devices")
userRef.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let purse = Purse()
purse.setValuesForKeys(dictionary)
self.purses.append(purse)
completion()
}
}, withCancel: nil)
}
This sounds like a misunderstanding of how async methods work.
If you use code like this:
//at this point purses is empty..
fetchPurse()
print("purses count = \(purses.count)")
You will always see an empty purses array.
The problem is that the fetchPurse() function uses the Firebase function observe, which is asynchronous. It requests that Firebase run the code you pass as your with: closure when a new entry is added. The observe function returns immediately, and invokes the closure you pass to it as some future time when FireBase adds a new child object.
As a result, your fetchPurse() function also returns before the new purse object is added to your array of purses.
As #DávidPásztor said in his comment, you should refactor fetchPurse to take a completion handler that gets called when the purses array is updated:
func fetchPurse(completion: () -> ){
let ref = FIRDatabase.database().reference(fromURL: "https://test-database-ba3a2.firebaseio.com/")
let user = (FIRAuth.auth()?.currentUser?.uid)!
let userRef = ref.child("users").child(user).child("devices")
userRef.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let purse = Purse()
purse.setValuesForKeys(dictionary)
self.purses.append(purse)
//This is the new line that calls your new completion handler
completion()
}
}, withCancel: nil)
}
And then you'd call it like this:
fetchPurse(completion: {
print("purses count = \(purses.count)")
}
I'm populating my "filteredLocations" array by using this:
let sampleRef = FIRDatabase.database().reference().child("SamplePost").child("post")
sampleRef.observeSingleEvent(of:.value, with: {(snapshot) in
if let result = snapshot.children.allObjects as? [FIRDataSnapshot] {
for child in result{
let dictionary = child.value as? [String: AnyObject]
let lat = dictionary?["lat"] as! Double
let long = dictionary?["long"] as! Double
let structure = MapPoints(Latitude: lat, Longitude: long)
self.filteredLocations.append(structure)
print("This is the amount in here \(self.filteredLocations.count)")
}
}
})
the print statement within the my snapshot returns 2, but when I print filteredLocations.count anywhere else it returns 0. I have the Firebase code at the start of the viewdidload
Your problem is that "sampleRef.observeSingleEvent" is asynchronous. What this means is that it is run in a background thread waiting for data while the app continues executing functions like viewWillAppear etc on the main thread.
By the time you get data back from the server the other print count methods would have already been executed before the array was populated with data.
To get a better understanding of this. Place a UIButton on your controller and bind it to a function that prints the array count. Then start the app and press the button. It should print 2 as by the time you press the button you should have got data back from the server.