Firebase don't send me my value into my variable - swift

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")
}
})
}

Related

Multiple if let statements in Swift and Firebase

I'm trying to compare my responses with other people's responses in the firebase database. My script currently has 2 if statements saying if it's my response, record my answers and then use that to compare against other responses, but it doesn't register my second if statement.
let responsesReference = Database.database().reference().child("responses")
responsesReference.observeSingleEvent(of: .value) { (snapshot: DataSnapshot) in
guard let json = snapshot.value as? [String: Any] else { return }
do {
var similarities = [Similarity]()
for answerElement in json {
if self.currentUser.uid == answerElement.key,
let myanswer = answerElement.value as? [String: Any] {
if self.currentUser.uid != answerElement.key, //DOES NOT REGISTER
let otheranswer = answerElement.value as? [String: Any] {
let percentage = myanswer.similarity(with: otheranswer)
similarities.append(
Similarity(name: answerElement.key, percentage: percentage, answer: otheranswer)
)
}
}
}
self.similarities = similarities.sorted(by: { (a, b) -> Bool in
return a.percentage > b.percentage
})
self.tableView.reloadData()
}
Here's your code properly formatted (I copy and pasted it with no changes other than formatting it)
do {
var similarities = [Similarity]()
for answerElement in json {
if self.currentUser.uid == answerElement.key, let myanswer = answerElement.value as? [String: Any] {
if self.currentUser.uid != answerElement.key, let otheranswer = answerElement.value as? [String: Any] {
let percentage = myanswer.similarity(with: otheranswer)
similarities.append( Similarity(name: answerElement.key, percentage: percentage, answer: otheranswer) )
}
}
Take a look here
if self.currentUser.uid == answerElement.key
and note the next if is nested inside that one
if self.currentUser.uid == answerElement.key
if self.currentUser.uid != answerElement.key
If those two vars are equal in the outside if, they will be equal with the inside if as well so the second check will always fail.
The generic solution is to use and else with your if
if self.currentUser.uid == answerElement.key {
let myanswer = answerElement.value as? [String: Any] <- OPTIONAL!
//do something because they are equal
} else {
let otheranswer = answerElement.value as? [String: Any] <- OPTIONAL!
// do something else because they are NOT equal
}
also note that you've got some optionals in that code and if those go to nil your app will either crash or silently fail with no indication as to why.
The logic isn't exactly clear in this code
let percentage = myanswer.similarity(with: otheranswer)
as each time through the loop
for answerElement in json {
}
there will only be one answer in an answerElement. e.g. there won't be a myAnswer and otherAnswer, there will only be theAnswer. Perhaps there should be a comparison to the prior answer from the loop; I'll expand on that
Here's an example based on reading in all users, getting the answer for this user, removing the user from the results and then comparing that to other users answers. Assume users uid's are used at the key to each user node (which also contains an answer they provides) and we know the auth'd users uid.
let thisUsersUid = "uid_1"
let usersRef = self.ref.child("users") //self.ref points to MY firebase
usersRef.observeSingleEvent(of: .value, with: { snapshot in
var allUsersSnap = snapshot.children.allObjects as! [DataSnapshot]
guard let index = allUsersSnap.firstIndex { $0.key == thisUsersUid } else {
print("no user: \(thisUsersUid) found")
return
}
let thisUserSnap = allUsersSnap[index] //keep this so it can be compared later
allUsersSnap.remove(at: index)
let thisUsersAnswer = thisUserSnap.childSnapshot("answer").value as? String ?? "No Answer"
for otherUserSnap in allUsersSnap {
let otherUsersAnswer = otherUserSnap.childSnapshot("answer").value as? String ?? "No Answer"
if orherUsersAnswer == thisUsersAnswer {
//do something because the answers match
}
}
})

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)
}
}
}

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.

Firebase one of two observers not working

I have two observers, the second observer is dependent on the first observers value. I can't seem to get the first observer to work, I am not getting any errors on Xcode. The first function has to check the Users profile for information and then use that information to search for different information in the database. Here is my code:
func loadposts() {
ref = Database.database().reference()
let trace = Performance.startTrace(name: "test trace")
trace?.incrementCounter(named:"retry")
let userID = Auth.auth().currentUser?.uid
print(userID!)
ref.child("Users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let one1 = value?["Coupon Book"] as? String ?? ""
print("one1: \(one1)")
self.bogus.set(one1, forKey: "bogus")
}) { (error) in
print(error.localizedDescription)
}
delay(0.1) {
print("bogus: \(self.bogus.string(forKey: "bogus"))")
Database.database().reference().child("Coupons").child(self.bogus.string(forKey: "bogus")!).observe(.childAdded) { (Snapshot : DataSnapshot) in
if let dict = Snapshot.value as? [String: Any] {
let captiontext = dict["company name"] as! String
let offerx = dict["offer count"] as! String
let logocomp = dict["logo"] as! String
let actchild = dict["childx"] as! String
let post = Post(captiontext: captiontext, PhotUrlString: actchild, offertext: offerx, actualphoto: logocomp)
self.posts.append(post)
self.tableview.reloadData()
print(self.posts)
}
}
}
trace?.stop()
}
Any help is appreciated.
self.bogus.string(forKey: "bogus"))" is nil because observeSingleEvent is an async method, so to get the required results you need to call the second observer inside the first observer or you can use the completion handler
You can use the completionHandler like this:
guard let uid = Auth.auth().currentUser?.uid else {
return
}
func firstObserverMethod(completionCallback: #escaping () -> Void) {
ref.child("Users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
if let value = snapshot.value as? [String: Any] {
let one1 = value["Coupon Book"] as? String
print("one1: \(one1)")
self.bogus.set(one1, forKey: "bogus")
completionCallback()
}
}) { (error) in
print(error.localizedDescription)
}
}
Now using the above method:
firstObserverMethod {
print("bogus: \(self.bogus.string(forKey: "bogus"))")
guard let bogusString = self.bogus.string(forKey: "bogus") else {
print("bogus is not set properly")
return
}
Database.database().reference().child("Coupons").child(bogusString).observe(.childAdded) { (Snapshot : DataSnapshot) in
if let dict = Snapshot.value as? [String: Any] {
let captiontext = dict["company name"] ?? ""
let offerx = dict["offer count"] ?? ""
let logocomp = dict["logo"] ?? ""
let actchild = dict["childx"] ?? ""
let post = Post(captiontext: captiontext, PhotUrlString: actchild, offertext: offerx, actualphoto: logocomp)
self.posts.append(post)
DispatchQueue.main.async {
self.tableview.reloadData()
}
print(self.posts)
}
}
}
Note: You should use optional binding to get the values from optional
Since you are using the result of the 1st observer in the reference of your 2nd observer, it's a very bad idea to add the 2nd observer right below the first observer. And adding a delay won't be a viable solution : these two calls are asynchronous, which means that the reason why you are not getting might very likely be because the 2nd observer is triggered even before the 1st has returned any data.
The solution here, would be using a completion handler, or you could just incorporate your 2nd observer inside the completion block of the 1st, to be make sure that the proper order (1st observer -> 2nd observer) will always be respected.
It would look somehow like this:
func loadposts() {
// ...
// 1st Observer here
ref.child("Users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get your value here
guard let one1 = snapshot.childSnapshot(forPath: "Coupon Book").value as? String else { return }
// 2nd Observer here. Now you can use one1 safely:
Database.database().reference().child("Coupons").child(one1).observe(.childAdded) { (Snapshot : DataSnapshot) in
// ...
}
})
}
Now, a couple of things that you could also improve in your code, while not directly related to the question:
I would suggest you to make use of guard statements instead force-unwrapping, which may end up in crashing your app at some point.
For example, you could check whether your current user exist or not like so:
guard let currentUserID = Auth.auth().currentUser?.uid else {
return
}
// Now you can use safely currentUserID
Also, when you try to get the data out of the snapshot, it's not a good idea either, to use force-casting. You would better write it in this way:
yourRef.observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
guard let text = child.childSnapshot(forPath: "text").value as? String, let somethingElse = child.childSnapshot(forPath: "otherValue").value as? NSNumber else {
return
}
// And so on, depending of course on what you have in your database.
}

Ambiguous Use of Subscript (Swift 3)

I am using the subscript in the following code incorrectly for this Firebase data pull, but I can't figure out what I am doing wrong. I get an error of Ambiguous use of subscript for the let uniqueID = each.value["Unique ID Event Number"] as! Int line.
// Log user in
if let user = FIRAuth.auth()?.currentUser {
let uid = user.uid
// values for vars sevenDaysAgo and oneDayAgo set here
...
let historyRef = self.ref.child("historyForFeedbackLoop/\(uid)")
historyRef.queryOrdered(byChild: "Unix Date").queryStarting(atValue: sevenDaysAgo).queryEnding(atValue: oneDayAgo).observeSingleEvent(of: .value, with: { snapshot in
if (snapshot.value is NSNull) {
print("user data not found")
}
else {
if let snapDict = snapshot.value as? [String:AnyObject] {
for each in snapDict {
// Save the IDs to array.
let uniqueID = each.value["Unique ID Event Number"] as! Int
self.arrayOfUserSearchHistoryIDs.append(uniqueID)
}
}
else{
print("SnapDict is null")
}
}
})
}
I tried to applying what I learned from this post, but I couldn't figure out what I am missing because I thought I was letting the compiler know what type of dictionary it is with the "as? [String:AnyObject]"
Any thoughts or ideas would be greatly appreciated!
My preferred way of dealing with data is to unwrap the FIRDataSnapshot as late as possible.
ref!.observe(.value, with: { (snapshot) in
for child in snapshot.children {
let msg = child as! FIRDataSnapshot
print("\(msg.key): \(msg.value!)")
let val = msg.value! as! [String:Any]
print("\(val["name"]!): \(val["message"]!)")
}
})
Taking Frank's feedback into account, here is the actual working code I used that follows that approach in case it's helpful.
// Log user in
if let user = FIRAuth.auth()?.currentUser {
let uid = user.uid
// values for vars sevenDaysAgo and oneDayAgo set here
...
let historyRef = self.ref.child("historyForFeedbackLoop/\(uid)")
historyRef.queryOrdered(byChild: "Unix Date").queryStarting(atValue: sevenDaysAgo).queryEnding(atValue: oneDayAgo).observeSingleEvent(of: .value, with: { snapshot in
if (snapshot.value is NSNull) {
print("user data not found")
}
else {
for child in snapshot.children {
let data = child as! FIRDataSnapshot
let value = data.value! as! [String:Any]
self.arrayOfUserSearchHistoryIDs.append(value["Unique ID Event Number"] as! Int)
}
}
})
}