Firebase Database observeSingleEvent not working - swift

I have a simple function in my code which gets the users name. However, when it comes to the line saying observeSingleEvent, it skips the whole closure and exits the function. Any ideas why?
func getUsersName() {
let userId = Auth.auth().currentUser?.uid
// It comes to the line below
ref?.child("users").child(userId!).child("name").observeSingleEvent(of: .value, with: { snapshot in
// It skips all of that
guard let value = snapshot.value as? NSDictionary else {
return
}
let name = value["name"] as? String ?? ""
print("Users name is: \(name)")
})
} // And comes here
I have it just like in the docs from firebase. The idea is that I have a pair: name: "users_name" and I want to get the users name and display it in a table view.

Related

How to read data from firebase and append it to an array

I have a userImages collection and I am trying to read data from it and appending it to my imgUrls array.
This is my code where I read the data from the database and try appending it to my array. Unfortunately, I keep getting an error because the array is apparently empty.
override func viewDidLoad() {
var ref: DatabaseReference!
let userID = Auth.auth().currentUser?.uid //holds the current user uid
ref = Database.database().reference()
var imgUrls = [String]() //array to hold the image urls from the userImages collection
ref.child("userImages").child(userID!).observeSingleEvent(of: .value) { (snapshot) in //read from userImages collection only from the subcollection where the
guard let dict = snapshot.value as? [String: Any] else { return } //document Id equals the current user uid. Create a dictionary from the
//snapshot values
let values = dict.values //holds the values from the dictionary
for value in values { //for loop to go through each value from the dictionary
imgUrls.append((value as? String)!) //and append to the imgUrls array
}
}
testLabel.text = imgUrls[0] //I used this to test, but i get an error saying the array is empty
}
I posted a question before, but it was so convoluted I decided to delete it and repost it simpler.
Any help is much appreciated!
the reason you are not getting anything in your testLabel.text is because:
ref.child("userImages").child(userID!).observeSingleEvent(of: .value) { (snapshot) in //read from userImages collection only from the subcollection where the
...
}
is an asynchronous function called. That is it will be done sometimes in the future.
but your:
testLabel.text = imgUrls[0] //I used this to test, but i get an error saying the array is empty
is outside of this call. So the results in "imgUrls" are not available yet.
Put this line inside the function or wait until it has finished before using the results.
You try to use the array before the observeSingleEvent closure is executed. All observation calls to Firebase are asynchronous. This means that you test code is executed before the closure and the array is still empty.
ref.child("userImages").child(userID!).observeSingleEvent(of: .value) { snapshot in
// this closure is executed later
guard let dict = snapshot.value as? [String: Any] else { return }
let values = dict.values
for value in values {
imgUrls.append((value as? String)!)
}
// your test code should bee here
}
// this is executed before closure and the array is empty
testLabel.text = imgUrls[0]
For this reason you get the error. You need to add your test code into the closure.
However, there is another catch. Asynchronous calls (their closures) are executed on the background thread.
ref.child("userImages").child(userID!).observeSingleEvent(of: .value) { snapshot in
// this is executed on the background thread
}
However, all user interface calls must be executed on the main thread. Also wrap your test code by calling the main thread, otherwise you won't see the result in the user interface.
DispatchQueue.main.async {
// this code is executed on the main thread
// all UI code must be executed on the main thread
}
After editing, your code might look like this:
override func viewDidLoad() {
var ref: DatabaseReference!
let userID = Auth.auth().currentUser?.lid
ref = Database.database().reference()
var imgUrls = [String]()
ref.child("userImages").child(userID!).observeSingleEvent(of: .value) { snapshot in
guard let dict = snapshot.value as? [String: Any] else { return }
let values = dict.values
for value in values {
imgUrls.append((value as? String)!)
}
DispatchQueue.main.async {
testLabel.text = imgUrls[0]
}
}
}

How to store a data from a closure firebase in a local variable?

I'm trying to store the value of the completion block into a dictionary. But I don't know how to access and store it in a local dictionary variable.
var id = String()
var answeredDict = [String:[String]]()
var answeredDictUsers = [String:String]()
override func viewDidLoad() {
super.viewDidLoad()
for user in answeredDict.keys{
let ref = Database.database().reference(fromURL: "URL").child("users/\(user)")
ref.child("name").observeSingleEvent(of: .value) { (snap) in
guard let name = snap.value as? String else { return }
self.answeredDictUsers.updateValue(name, forKey: user)
}
}
print(answeredDictUsers)
}
the print(answeredDictUsers) gives [:] (empty dictionary)
Data is loaded from Firebase asynchronously. By the time your print(answeredDictUsers) runs, the code inside the callback hasn't run yet. And since self.answeredDictUsers.updateValue(name, forKey: user) hasn't run yet, the print sees an empty array. You can easily test this for yourself by placing breakpoints on both those lines and running the code in the debugger.
This means that any code that needs data from the database, must be (called from) inside the callback/completion handler that gets that data from the database.
A very simple example:
for user in answeredDict.keys{
let ref = Database.database().reference(fromURL: "URL").child("users/\(user)")
ref.child("name").observeSingleEvent(of: .value) { (snap) in
guard let name = snap.value as? String else { return }
self.answeredDictUsers.updateValue(name, forKey: user)
print(answeredDictUsers)
}
}
The above will print the dictionary each time it has loaded one of your users' data.
If you only want to print the dictionary once the data for all users has been loaded, you could for example keep a counter:
let count = 0
for user in answeredDict.keys{
let ref = Database.database().reference(fromURL: "URL").child("users/\(user)")
ref.child("name").observeSingleEvent(of: .value) { (snap) in
guard let name = snap.value as? String else { return }
self.answeredDictUsers.updateValue(name, forKey: user)
count = count + 1
if count == dict.count {
print(answeredDictUsers)
}
}
}

I cannot update the value when I fetch the user data from firebase database

I can fetch the user data from the firebase, but I cannot input the user data to a global variable.
Database.database().reference().child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let username = value?["username"] as? String ?? ""
let userDigit = value?["lastDigit"] as? String ?? ""
let userDate = value?["babyDate"] as? String ?? ""
print(username)
print(userDigit)
print(userDate)
self.user.userName = username
self.user.userDigit = userDigit
self.user.userDate = userDate
}) { (error) in
print(error.localizedDescription)
}
print(self.user.userName)
print(self.user.userDigit)
print(self.user.userDate)
From the above code, I can see the username, userDigit, userDate, however, after i input the data into user class that contains 3 string variables, i cannot print the user class value.
To be specific,
print(self.user.userName)
this code returns nil.
How can I input the data?
The value of self.user.userName is nil because you're calling it outside the observer closure.
Printing a value after assigning in a closure doesn't guarantee that it will be available immediately afterwards.
Because the closure of observeSingleEvent gets called after the data is fetched from firebase.
You should modify your logic and try to access the value of user after the data has successfully been fetched from firebase.
below is a sample code that might help you understand the concept:
func getUser(with userID: String, completion: #escaping ((_ user: User) -> Void)) {
Database.database().reference().child("users").child(userID).observeSingleEvent(of: .value) { (snapshot) in
// Get user value
let value = snapshot.value as? [String: Any]
let username = value?["username"] as? String ?? ""
let userDigit = value?["lastDigit"] as? String ?? ""
let userDate = value?["babyDate"] as? String ?? ""
var user = User()
user.userName = username
user.userDigit = userDigit
user.userDate = userDate
DispatchQueue.main.async {
completion(user)
}
}
}
In the above method, we fetch data from firebase and return it inside a closure of our own once the data is available.
and then you call the above function like this:
self.getUser(with: userID!) { (user) in
// the user object inside the closure will be available after fetching data from firebase
// the user object can even be assigned to a global property
self.globalUser = user
print(self.globalUser.userName)
print(self.globalUser.userDigit)
print(self.globalUser.userDate)
}
Hope this helps

Firebase don't send me my value into my variable

I've got a code which normally should return to me a value from Firebase.
My Firebase struct is :
Experience{
UserId{
LDG_DAY: "4"
LDG_NIGHT: "0"
APCH_IFR: "0"
}
}
My code is :
func getUserExp(){
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let Date = self.flightDate.text
ref.child("Experience")/*.child(userID!)*/.observeSingleEvent(of: .value) {(snapshot) in
if snapshot.hasChild(userID!){
let value = snapshot.value as? NSDictionary
let ldg_day = value?["LDG_DAY"] as? String ?? "123"
let ldg_night = value?["LDG_NIGHT"] as? String ?? "0"
let apch_ifr = value?["APCH_IFR"] as? String ?? "0"
self.intLdgDay = Int(ldg_day)!
self.intLdgNight = Int(ldg_night)!
self.intApchIfr = Int(apch_ifr)!
print("string = \(ldg_day) int = \(self.intLdgDay)")
}
}
}
Now the code didn't work as I would like... In fact my code return the basic as? String ?? "123" value but the snapshot.value get the good value from firebase ...
What's wrong ? I use this code for many other part of my app and no problems about it ?
Thanks for your help
I believe you want to ensure the node exists before trying to read the child data.
NOTE:
I see the path to read has the uid commented out so it's unclear if you intended to read a single user (leaving in the uid) or if you actually wanted to load every user at one time (thousands). This answer assumes you are intending to read that specific user node only. See #Callam answer if you intended to read ALL of the users nodes at one time.
The code you have now is using snapshot.hasChild which looks within the node to see if the child, the users uid exists, and it doesn't so the code will always fail.
if snapshot.hasChild(userID!)
I think what you want to do is use snapshot.exists to ensure it's a valid node before reading. Here's the code:
let experienceRef = self.ref.child("Experience")
let usersExpRef = experienceRef.child(uid)
usersExpRef.observeSingleEvent(of: .value) { snapshot in
if snapshot.exists() {
let value = snapshot.value as! [String: Any]
let ldg_day = value["LDG_DAY"] as? String ?? "123"
print("string = \(ldg_day)")
} else {
print("the \(uid) node does not exist")
}
}
I would also suggest safely unwrapping options before attempting to work with them as they could be nil, and that would crash your code.
guard let thisUser = Auth.auth().currentUser else { return }
let uid = thisUser.uid
Note I also replaced the old objc NSDictionary with it's Swifty counterpart [String: Any]
Assuming your struct is from the root, and Experience contains more than one user ID, your code is currently observing the value for all user IDs since the /*.child(userID!)*/ is commented out.
Therefore you are requesting every user's experience and checking on the client if the current user exists as a child – this will succeed if the current user's ID is present at Experience/$uid.
ref.child("Experience")/*.child(userID!)*/.observeSingleEvent(of: .value) { (snapshot) in
if snapshot.hasChild(userID!) {
let value = snapshot.value as? NSDictionary
Now we have a snapshot with all Experiences and we've confirmed that it has a child for the current user's ID – we would need to get that child and cast the value of that to a dictionary.
let value = snapshot.childSnapshot(forPath: userID).value as? NSDictionary
This fixes the issue but obviously, we don't want to download every experience on a single user's device, and they maybe shouldn't even have the permission to request that reference location either.
So if you uncomment .child(userID!), the snapshot will be of just one Experience, so snapshot.hasChild(userID!) will fail. Instead, you can use snapshot.exists() and/or a conditional cast to determine if the snapshot for the userID is existent and/or thereby castable.
func getUserExp() {
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let Date = self.flightDate.text
ref.child("Experience").child(userID!).observeSingleEvent(of: .value) { snapshot in
if snapshot.exists() {
let value = snapshot.value as? [String:String]
let ldg_day = value?["LDG_DAY"] ?? "123"
let ldg_night = value?["LDG_NIGHT"] ?? "0"
let apch_ifr = value?["APCH_IFR"] ?? "0"
self?.intLdgDay = Int(ldg_day)!
self?.intLdgNight = Int(ldg_night)!
self?.intApchIfr = Int(apch_ifr)!
print("string = \(ldg_day) int = \(self.intLdgDay)")
} else {
print("experience for \(snapshot.key) doesn't exist")
}
}
}
You can clean this up a bit with a struct and extension.
// Experience.swift
struct Experience {
var ldg_day: String
var ldg_night: String
var apch_ifr: String
}
extension Experience {
static var currentUserRef: DatabaseReference? {
return Auth.auth().currentUser.flatMap {
return Database.database().reference(withPath: "Experience/\($0.uid)")
}
}
init?(snapshot: DataSnapshot) {
guard snapshot.exists() else { return nil }
let value = snapshot.value as? [String:String]
self.ldg_day = value?["LDG_DAY"] ?? "123"
self.ldg_night = value?["LDG_NIGHT"] ?? "0"
self.apch_ifr = value?["APCH_IFR"] ?? "0"
}
}
Et voilà,
func getUserExp() {
Experience.currentUserRef?.observeSingleEvent(of: .value, with: { [weak self] in
if let experience = Experience(snapshot: $0) {
self?.intLdgDay = Int(experience.ldg_day)!
self?.intLdgNight = Int(experience.ldg_night)!
self?.intApchIfr = Int(experience.apch_ifr)!
print("string = \(experience.ldg_day) int = \(self.intLdgDay)")
} else {
print("experience for \($0.key) doesn't exist")
}
})
}

Getting values from Firebase snapshot in Swift

Im successfully getting data from Firebase but I can't manage to push it into array to use. My database is as follows:
users
-Wc1EtcYzZSMPCtWZ8wRb8RzNXqg2
-email : "mike#gmail.com"
-lists
-LJiezOzfDrqmd-hnoH-
-owner: Wc1EtcYzZSMPCtWZ8wRb8RzNXqg2
-LJif-UgPgbdGSHYgjY6
-owner: Wc1EtcYzZSMPCtWZ8wRb8RzNXqg2
shopping-lists
-LJh6sdBJtBCM7DwxPRy
-name: "weekly shopping"
-owner: "mike#gmail.com"
I have a home page after login that shows existing shopping lists on table if they exist. On viewDidLoad() I get shopping list IDs from the user and use those IDs as a reference to get details from shopping-lists.
However, I cant manage to save these data into an array as it gets deleted after closure. How can I do that in a clean way?
override func viewDidLoad() {
super.viewDidLoad()
SVProgressHUD.show()
tableView.allowsMultipleSelectionDuringEditing = false
// Sets user variable - must have
Auth.auth().addStateDidChangeListener { auth, user in
guard let user = user else { return }
self.user = User(authData: user)
// If new user, write into Firebase
self.usersRef.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.hasChild(self.user.uid) {
self.usersRef.child(user.uid).setValue(["email": user.email!])
}
})
// Get shopping lists data from "users/lists"
self.usersRef.child(user.uid).child("lists").observe(.value, with: { snapshot in
// Get list IDs
if snapshot.exists() {
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
self.listNames.append(child.key)
}
}
}
// Use list IDs - to get details
for item in self.listNames {
let itemRef = self.shoppingListsRef.child(item)
itemRef.observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let value = snapshot.value as? [String: Any] {
let name = value["name"] as? String ?? ""
let owner = value["owner"] as? String ?? ""
let shoppingList = ShoppingList(name: name, owner: owner)
self.items.append(shoppingList)
}
})
}
})
self.tableView.reloadData()
SVProgressHUD.dismiss()
}
}
(the question is a bit unclear so several parts to this answer to cover all possibilities. This is Swift 4, Firebase 4/5)
You don't really need to query here since you know which nodes you want by their key and they will always be read in the in order of your listNames array. This assumes self.listNames are the keys you want to read in.
for item in listNames {
let itemRef = shoppingListsRef.child(item)
itemRef.observe(.value, with: { (snapshot) in
if let value = snapshot.value as? [String: Any] {
let name = value["name"] as? String ?? ""
let owner = value["owner"] as? String ?? ""
print(name, owner)
}
})
}
Generally, queries are used when you are searching for something within a node - for example if you were looking for the node that contained a child name of 'weekly shopping'. Other than that, stick with just reading the nodes directly as it's faster and has less overhead. Keep reading...
I also removed the older NSDictionary and went with the Swift [String: Any] and modified your error checking
However, the real issue is reading that node with an .observe by .value. Remember that .value reads in all children of the node and then the children need to be iterated over to get each separate DataSnapshot. Also, .observe leaves an observer on the node notifying the app of changes, which I don't think you want. So this will answer the question as posted, (and needs better error checking)
for item in listNames {
let queryRef = shoppingListsRef
.queryOrdered(byChild: "name")
.queryEqual(toValue: item)
queryRef.observe(.value, with: { (snapshot) in
for child in snapshot.children { //even though there is only 1 child
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let name = dict["name"] as? String ?? ""
let owner = dict["owner"] as? String ?? ""
print(name, owner)
}
})
}
And the answer...
This is probably more what you want...
for item in listNames {
let queryRef = shoppingListsRef
.queryOrdered(byChild: "name")
.queryEqual(toValue: item)
queryRef.observeSingleEvent(of: .childAdded, with: { snapshot in
let dict = snapshot.value as! [String: Any]
let name = dict["name"] as? String ?? ""
let owner = dict["owner"] as? String ?? ""
print(name, owner)
})
}
note the .childAdded instead of .value which presents the snapshot as a single DataSnapshot and doesn't need to be iterated over and the .observeSingleEvent which does not leave an observer attached to each node.
Edit
Based on additonal information, it would be best too change the structure to this
shopping-lists
-LJh6sdBJtBCM7DwxPRy
-name: "weekly shopping"
-uid: "Wc1EtcYzZSMPCtWZ8wRb8RzNXqg2"
and then when the user logs in just query the shopping lists node for any uid that's theirs.