Firebase query ordering by child not working - swift

I try to sort my table view content with creation date. The newest should be above. My code seems ok but it does not show the correct order. It is the opposite.
ref = Database.database().reference().child("placeID/\(placeIdFromSearch)")
ref.queryOrdered(byChild: "userTime").queryLimited(toLast: 10).observe(DataEventType.value, with: {(snapshot) in
if snapshot.childrenCount > 0 {
self.table.removeAll()
for video in snapshot.children.allObjects as! [DataSnapshot] {
let Object = video.value as? [String: AnyObject]
let userName = Object?["userName"]
let userGroup = Object?["userGroup"]
let userComment = Object?["userComment"]
let userTime = Object?["userTime"]
let userLikes = Object?["userLikes"]
let video = Videos(userName: userName as! String, userGroup: userGroup as! String, userComment: userComment as! String, userTime: userTime as! Int, userLikes: userLikes as! String)
self.table.append(video)
self.tableView.reloadData()
}
}
})

Firebase Realtime Database results are always in ascending order. There is no way to get them in descending order from the database, so you will have to reverse the results in your application code.
A simple option for this is to insert each item at the top of the table, instead of appending it to the bottom as you do now:
self.table.insert(video, at: 0)
The alternative would be to store inverted values in your database, like: userTime: -1654342137807. That way the ascending order that the database uses, will actually be what you want.
Also see:
How to get data from firebase in descending order of value?
Swift - How to create Sort query as Descending on Firebase?
Firebase query sort order in swift?

Related

SwiftUI, Firestore get array of object within object

I am working on a recipe-app connected to firestore and have trouble reading the data saved in the database. I save a recipe that consists of title, id etc but it also contains an array of ingredients. This array is a struct containing id, name and amount. I am able to get the recipe object but the array of ingredients is empty. This is how is get the recipe
private func listenForRecipes() {
db.collection("recipe").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.recipes = documents.map { queryDocumentSnapshot -> RecipePost in
let data = queryDocumentSnapshot.data()
let title = data["title"] as? String ?? ""
let steps = data["steps"] as? [Step] ?? []
let ingredients = data["ingredients"] as? [Ingredient] ?? []
let serves = data["serves"] as? Int ?? 0
let author = data["author"] as? String ?? ""
let authorId = data["authorId"] as? String ?? ""
let category = data["category"] as? String ?? ""
let image = data["image"] as? String ?? ""
print("\(ingredients)")
return RecipePost(title: title, steps: steps, ingredients: ingredients, serves: serves, author: author, authorId: authorId, category: category, image: image)
}
}
}
Thankful for any help.
The data that you're getting from Firebase is coming back to you in the form of a [String:Any] dictionary. Your current code is taking those dictionary keys (title, author, etc) and doing optional casts (the as?), telling the system "if this data is actually a String, then set my variable to that value. If not (??), here's the default value to use instead"
The problem comes when you introduce custom types. The system doesn't inherently know that your item is an Ingredient or Step. So, the cast fails, and you get the default value of [].
You have two options:
Use a custom type for your entire document (see Firebase documentation on this here: https://firebase.google.com/docs/firestore/query-data/get-data#swift_3). This SO question is also relevant: How to convert document to a custom object in Swift 5?
Convert the [String:Any] dictionary (or array of dictionaries as it may be in this case) yourself. First step might be to print data["ingredients"] to the console to see what it really has in it. Without being able to see what you actually have in Firestore, Let's assume it is a [[String:Any]] (an array of dictionaries). Then your conversion might look like this:
let ingredients = (data["ingredients"] as? [[String:Any]]).map { item in
return Ingredient(id: item["id"] as? String ?? "", name: item["name"] as? String ?? "", amount: item["amount"] as? String ?? "")
}
You can also experiment with using Codable, which could allow you to automate some of this process, say with JSONDecoder to do some of the work for you. Relevant SO: How can I use Swift’s Codable to encode into a dictionary?

My app just show one of the values of my database in swift

When I read data from my database (that includes 9 or 10 child) it just shows me the last value and the other labels are null.
I appreciate your help.
let Readuserdata = Database.database().reference().child("Users").child("Customers").child(userid)
Readuserdata.observeSingleEvent(of: .value, with: { (snapshot) in
let post = snapshot.value as! [String:Any]
self.lblgiftprofile.text = post["discountcode"] as? String
self.lblprofileusername.text = post["name"] as? String
self.lblemailprofile.text = post["email"] as? String
})
This shows the last label, that is "email". All the other values are null and if I put lblemailprofile at first line it will be null and lblprofileusername will appear.

Firebase-Swift How to sum a child values together

I need to retrieve price value of each product in the cart child, but how should I retrieve it and sum the retrieved value together?
Picture of my Firebase database structure
let uid = Auth.auth().currentUser?.uid
refProduct = Database.database().reference().child("users").child(uid!).child("cart")
refProduct.observeSingleEvent(of: .value) { (snapshot) in
for cartchild in snapshot.children{
let snap = cartchild as! DataSnapshot
let key = snap.value
.....
}
}
I would not store the price as a string, but as a number. You might want to add another field with currency if needed.
guard let uid = Auth.auth().currentUser?.uid else { return }
var sum: Double = 0
refProduct = Database.database().reference().child("users").child(uid).child("cart")
refProduct.observeSingleEvent(of: .value) { (snapshot) in
for cartchild in snapshot.children{
let snap = cartchild as! DataSnapshot
let data = snap.value as? [String: Any]
let price = data["ProductPrice"] as? Double ?? 0
sum += price
}
print("Final sum: \(sum)")
}
Not really tested, but this is the idea
Arvidurs is correct about storing the price as an int and the currency as a string, but the reason the answer isn't working for you is that it doesn't address that you're not correctly retrieving the data you want in the first place.
You have your cart folder, and it contains two product folders whose properties you're trying to retrieve. You can't retrieve and unwrap the values contained in those two folders by just referencing the parent cart folder. You need to individually access each folder within cart:
Database.database().reference().child("users").child(uid).child("cart").child("-Lf59bkQ5X3ivD6ue1SA")
Database.database().reference().child("users").child(uid).child("cart").child("-Lf5MiEGU357HWTMbxv8")
However, for this to work, you'll need access to each products autoID value, so you'll need to be storing each new product's childByAutoID value into an array or a dictionary so that you have them all available to access whatever data you need.
You'll need to implement this as you're storing the new product to the cart folder. I don't know exactly how you're currently saving each product, but you'll need to do something like this when you create your reference that you'll be saving to:
let newProductRef = Database.database().reference().child("users").child(uid).child("cart").childByAutoId()
let autoID = newProductRef.key
At that point, you'll be able to store autoID however you choose, and you'll have access to everything within the cart folder, and you can loop through all of your autoIDs and get whatever data you need. Example:
func getCartPriceSum(finished: #escaping ([String : Double]) -> Void){
let myGroup = DispatchGroup()
var sum = Double()
var currency = String()
for autoID in autoIdArray{
myGroup.enter()
let productRef = Database.database().reference().child("users").child(uid).child("cart").child(autoID)
productRef.observe(.value) { (snapshot) in
guard
let snapshotValue = snapshot.value as? NSDictionary,
let productPrice = snapshotValue["ProductPrice"] as? Double,
let priceCurrency = snapshotValue["PriceCurrency"] as? String//assuming you've adopted Arvidurs' method of storing the price data
else {
print("productPrice/priceCurreny nil")
return
}
sum += productPrice
currency = priceCurrency
}
myGroup.leave()
}
let priceSum = [currency : sum]
myGroup.notify(queue: .main) {
finished(priceSum)
}
}
And you could call the function like this:
getCartPriceSum { (priceSum) in
//do whatever you want with the priceSum
}
The only thing left for you to figure out is how you want to store those autoIDs.

Reading Firebase child by auto ID swift

This is a quick output of my Firebase tree, id like to access people_on_this_planit / userID node. My only issue is that node is within an auto ID; please any help would be appreciated. Thanks
planits
-LEmgxuG_13KNA5inRaB
Planit Title: "Some title"
people_on_planit
-LEmh6IxqguVBJEwghZv (auto ID)
userID: "kkdiEW0D9"
senderId: "39FdLfdIO8832"
Right now the code i am using is as follows, but i am getting nil when i print peopleonplanit.
ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let planitsRef = ref.child("planits")
for child in snapshot.children {
let childSnap = child as! DataSnapshot
let dict = childSnap.value as! NSDictionary
let planKey = childSnap.key
//print(planKey)
print(dict)
let title = dict["Planit Title"] as! String
let senderID = dict["senderId"] as! String
let peopleOnPlanit = dict["people_on_planit"] as? String
print(peopleOnPlanit)
}
Here's the code to read the Firebase structure proposed in the question
let planitsRef = self.ref.child("planits")
planitsRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let planSnap = child as! DataSnapshot
let planDict = planSnap.value as! [String: Any]
let title = planDict["plan_title"] as! String
let senderId = planDict["sender_id"] as! String
print("plan title: \(title) senderId: \(senderId)")
let peopleOnPlanitSnap = planSnap.childSnapshot(forPath: "people_on_planit")
for peopleChild in peopleOnPlanitSnap.children {
let peopleChildSnap = peopleChild as! DataSnapshot
let userSnapDict = peopleChildSnap.value as! [String: Any]
let userId = userSnapDict["user_id"] as! String
print(" userId: \(userId)")
}
}
})
and the output
plan title: Some title senderId: 39FdLfdIO8832
userId: uid_0
userId: uid_1
-- A couple of notes --
Since the associated users appear to be stored by their user Id and that's the only data stored within that node, you may want to change this node
people_on_planit
-LEmh6IxqguVBJEwghZv (auto ID)
userID: "kkdiEW0D9"
to look like this
people_on_planit
uid_x: true //uid_x would be kkdiEW0D9 etc
uid_y: true
it's a bit shallower and cleaner and requires less code to read it. Also, you can query it more easily if that ever comes up.
also note that I changed the naming convention slightly; you are welcome to format keys however you want but I went with keys like plan_title instead of PlanIt Title and sender_id and user_id instead of senderId and userId. My practice is for actual firebase keys I use all lowercase, underscore instead of spaces, and in code, I use lower/uppercase with no spaces. So if you copy paste my code, you'll need to change those.

Using Swift pull data from firebase

This is how my Firebase looks like
Firebase ScreenShot
This is my code
override func viewDidLoad() {
var sum = 0
var counter = 0
super.viewDidLoad()
ref = Database.database().reference()
ref.child("WaterAdded").observe(.childAdded, with: { (snapshot) in
if let totalwateradded = snapshot.value as? [Int]{
while counter < (totalwateradded.count) {
var newValue = totalwateradded[counter]
sum += newValue
}
self.totalAdded.text = "\(sum)"
}
})
}
I want to grab all the number in Firebase and display the sum. But it display nothing.
You cannot directly cast snapshot.value to [Int], instead if you want to get all objects in your node you should use
let ref = Database.database().reference()
ref.child("WaterAdded").observe(.childAdded, with: { (snapshot) in
if let water = snapshot.value as? String{
guard let waterAmount = Int(water) else{return}
sum += waterAmount
}
})
that while loop is not needed as this will give you all the values in the database.
Edit
In the database you can use Strings and Ints to store numbers. You store them as Strings. If you load them in Swift you have to conditionally cast the value to String (as? String). The problem, however is that you can not do any arithmetic operations on strings so using the Int(water) statement you can convert it to Int. This operation can give an integer from a string if it contains a number, but it can also fail (e.g. Int("two")) and therefore we use guard let to make sure we only proceed to the next line if it can successfully convert to an Int. Afterwards we just add the int value to sum and done.
try...
if let snap = snapshot.value as? Dictionary [Sting: Any], let totalWaterAdded =
snap["\(yourDictionaryKey)"] as? Int{
//enter your code...
}
Here's the answer that reads the node, sums the values and print it to console.
let ref = self.ref.child("WaterAdded")
ref.observeSingleEvent(of: .value, with: { snapshot in
var x = 0
for child in snapshot.children {
let val = (child as! DataSnapshot).value as! Int
x = x + val
}
print("sum: \(x)")
})
We're using observeSingleEvent of .value in this case as there's really no reason for firebase to iterate over each child when we can do it a lot faster in code. Also, we don't need to be notified of childAdded events.
Tossing a guard statement in the mix may be a good idea but as long as you are sure the values will only be Int's, it's good to go as is.
Tested with the following structure
WaterAdded
a: 0
b: 1
c: 2
d: 3
and the output is
6