swift firebase nested children count - swift

groups
--group1(autoid)
---subgroup1(autoid)
----id
----ownerId
----description
--group2(autoid)
---subgroup2(autoid)
----id
----ownerId
----description
In a structure like over here i have to count all occurrencies of ownerId that are equal to my id (currentUserId) in all groups, can somebody help me?
what i've done so far:
root.child("groups").observe(.value, with: {(snapshot) in
if let result = snapshot.children.allObjects as? [DataSnapshot] {
var count = 0
for child in result {
let orderID = child.key as String //get autoID
self.root.child("groups/\(orderID)/").queryOrdered(byChild: "ownerId").queryEqual(toValue: self.currentUserId).observe(.value, with: { (snapshot: DataSnapshot!) in
print(snapshot.childrenCount, "quanti sono")
count += (Int(snapshot.childrenCount))
print(count)
})
}
}
})
with this i can get a count but it updates all cycles... i need i need the final value outside

One important aspect of Firebase Structures is denormalizing or flattening the structure. Denormalized data generally makes queries much easier and while conceptually the structure you are using works for some tasks, it makes doing the query you want challenging.
So, I would suggest an alternate structure that would make the query super simple, and not loose other functionality.
A change to the structure like this:
groups
group1: true
group2: true
subgroups
subgroup1(autoid)
id
ownerId
description
belongs_to_group: "group1"
subgroup2(autoid)
id
ownerId
description
belongs_to_group: "group2"
Then if you want to count all of subgroups with a particular ownerId
let subGroupsRef = self.ref.child("subgroups")
let query = subGroupsRef.queryOrdered(byChild: "ownerId").queryEqual(toValue: "their id")
query.observeSingleEvent(of: .value) { snapshot in
let count = snapshot.childrenCount
print(count)
}
Edit:
Based on the comment, here's an way to get the count based on your current structure. It's pretty brute force and the code could be reduced considerably but I left it verbose for readability
let groupsRef = self.ref.child("groups")
groupsRef.observeSingleEvent(of: .value, with: { snapshot in
var count = 0
for groupChild in snapshot.children {
let groupSnap = groupChild as! DataSnapshot
for subGroupChild in groupSnap.children {
let subGroupSnap = subGroupChild as! DataSnapshot
let dict = subGroupSnap.value as! [String: Any]
let uid = dict["owner_id"] as! String
if uid == "uid_0" {
count += 1
print(count)
}
}
}
print("total found \(count)")
})
Where this fails is if you have a lot of nodes as they are all initially loaded in (by .value) so it could be iterated over in code. If it's a few thousand it works well and is very fast.

You can achieve this step by doing a single observe of .childAdded type. The .childAdded on an sub child, in your example groups, is like a for loop that iterate all nodes.
With this configuration you can append a .queryOrdered(byChild:) and a .queryEqual(toValue:):
ref.child("groups")
.queryOrdered(byChild: "ownerID")
.queryEqual(toValue: 123)
.observe(.childAdded)
{ (snap) in
print(snap)
}
After that, if you want to count all this child, you need to add a property on your class
This is a test example:
To optimize performance remember to add a .indexOn rule on your firebase app:
"groups" : {
".indexOn" : "ownerID"
}
Hope this help you ;)

Related

Parse Large Firebase Snapshot

I am downloading a large file structure from Firebase. However when printing the keys and values one of the subfolders is showing as <__NSArrayM> I tried to cast it to [String:Any] but it is crashing. Here is my code:
DATABASE.mainOrder.child("incomplete").observe(DataEventType.value, with: { (snapshot) in
let array = snapshot.value as! [String:[String:Any]]
for (_, value) in array {
for number in value {
if "\(number.key)" == "itemsInOrder" {
let items = number.value as! [String:Any]
//let items = Array(arrayLiteral: number.value)
print("itemsInOrder: \(items)")
} else {
print("\(number.key): \(number.value)")
}
}
}
})
However it is crashing and giving me this error:
Could not cast value of type '__NSArrayM' (0x2037d2608) to 'NSDictionary' (0x2037d21d0).
This is the code that I am trying to get:
itemsInOrder: [<__NSArrayM 0x281c060a0>({
amount = "2";
length = "5";
height = "7";
width = "10";
})]
Below is the Firebase json export
{
"TH000" : {
"docUUIDStatus" : "Sent",
"expectedShip" : "May 12, 2021",
"itemsInOrder" : [ {
"amountOrdered" : "2000 sq ft",
"bevel" : "None",
"floorLength" : "2-8",
"floorWidth" : "4",
"specialOrder" : "None",
"specialOrderLength" : "",
"status" : "completed"
} ],
"orderDate" : "May 11, 2021",
"orderNumber" : "TH000",
"orderNumberLowercased" : "th000",
"orderTime" : "2:30:30 PM",
"orderType" : "Order",
"purchaseOrderNumber" : "TH10051",
"purchaseOrderNumberLowercased" : "th10051",
"status" : "completed"
}
}
What you have in itemsInOrder is an array of dictionaries, so to get the data from it, you either have to loop over number in a nested loop, or extract it with:
let items = number.value as! [[String:Any]]
Since I find the double [[ a bit hard to see at first glance, I actually prefer this equivalent syntax:
let items = number.value as! [Dictionary<String:Any>]
Unrelated to the actual problem: your data structure is a bit of an antipattern in Firebase, as reading any order also now means that you end up loading all items in that order. This becomes wasteful when you want to show a list of order names.
The more idiomatic data structure it so have two top-level nodes:
orders
orderItems
In the first you store the properties for each order, such as orderType and status, under the order ID. In the second node you store the list of order times, also under the order ID.
With that structure, your current query will require two steps:
A query to determine the IDs of the orders with the requested status.
Then an additional load for each order to get the items for that order.
This second step is not nearly as slow as you may think, as Firebase pipelines the requests over a single existing connection.
The answer provided by Frank is spot on. I would like to suggest another approach.
We've found that DataSnapshots are super flexible and easy to work with - keeping data as a DataSnapshot for a long as possible provides easier access, especially when the structure is a bit deep. When casting to array's and dictionaries you end up with this kind of thing
[String: [String: [String: Any]]]
which is really cumbersome to work with and troubleshoot.
So here's the code that will read in the data as specified in the question. Note we never convert anything to a Dictionary and use arrays to guarantee ordering will be maintained from the retrieved DataSnapshots.
func readData() {
let ref = self.ref.child("main_order").child("incomplete") //self.ref points to my firebase
ref.observe(.value, with: { snapshot in //first node will be TH000
let childSnapArray = snapshot.children.allObjects as! [DataSnapshot]
for childSnap in childSnapArray {
print("parent node: \(childSnap.key)")
let status = childSnap.childSnapshot(forPath: "docUUIDStatus").value as? String ?? "No Status"
print(" status: \(status)")
let itemsInOrder = childSnap.childSnapshot(forPath: "itemsInOrder") as DataSnapshot
let itemSnapArray = itemsInOrder.children.allObjects as! [DataSnapshot]
print(" \(itemsInOrder.key)")
for itemChildSnap in itemSnapArray {
let amount = itemChildSnap.childSnapshot(forPath: "amountOrdered").value as? String ?? "No amount"
let length = itemChildSnap.childSnapshot(forPath: "floorLength").value as? String ?? "No length"
let width = itemChildSnap.childSnapshot(forPath: "floorWidth").value as? String ?? "No width"
let status = itemChildSnap.childSnapshot(forPath: "status").value as? String ?? "No status"
print(" ", amount, length, width, status)
}
}
})
}
I dropped in a few print statements along the way to show the code execution sequence. The output to console is this
parent node: TH000
status: Sent
itemsInOrder
2000 sq ft 2-8 4 completed
The one other thing is the array stored within itemsInOrder. It's not clear why that's an array and in general Arrays should be avoided in The Realtime Database if possible. There are usually better ways to structure the data. Arrays cannot be directly altered - if you want to insert or delete an element the entire array must be read in, modified and written back out.
So the above code may need to be slightly modified to handle the itemsInOrder node if there are multiple array elements, for example.

Instead of running a snapshot for all users, how do you set up multiple queries to limit the number of users sent to the device?

What I have: A snapshot of all users with a bunch of if statements that eventually returns an array of users that get displayed.
What I need: The array of end users to be used in a .query in the line preceding the snapshot.
Why do I need this: This line is so that the entire database of users is not run on the client.
More specifically, what do I need to query for: A) Users who have a child "caption"(timestamp) with a timestamp that is in today, AND, B) who are 3000 miles from the current user.
JSON of DB
"people" : {
"02PdiNpmW3MMyJt3qPuRyTpHLaw2" : {
"Coordinates" : {
"latitude" : -25.809620667034363,
"longitude" : 28.321706241781342
},
"PhotoPosts" : "https://firebasestorage.googleapis.com/v0/b/daylike-2f938.appspot.com/o/images%2F02PdiNpmW3MMyJt3qPuRyTpHLaw2%2FPhotoPosts?alt=media&token=24fee778-bcda-44e3-aa26-d7c2f8509740",
"caption" : 1602596281762, /// this is timestamp
"postID" : "02PdiNpmW3MMyJt3qPuRyTpHLaw2"
},
"e1" : “cvvvv666",
"e2" : "aol.com",
" "postID" : "0RnqWV7Gd9Z0bUW9nUvizMQOjK73",
"users" : "cvvvv666#aol.com"
},
.
var dict = CLLocation()
...
dict = CLLocation(latitude: lat, longitude: lon)
...
let thisUsersUid = Auth.auth().currentUser?.uid
//this line below is where the refArtists2 query should go. in other words send all users to device that meet the 2 if statements, which is represented by self.people.append(peopl)//
let refArtists2 = Database.database().reference().child("people").queryOrdered(byChild: "caption").queryEqual(toValue: ANY Timestamp in today).queryOrdered(byChild:Coordinates). queryEqual(toValue:ThoseCoordinates which make the distance to current user less than 3000 miles)
refArtists2.observe(DataEventType.value, with: { snapshot in
if snapshot.childrenCount>0{
self.people.removeAll()
for people in snapshot.children.allObjects as! [DataSnapshot] {
if people.key != thisUsersUid {
let peopleObject = people.value as? [String: AnyObject]
let peopleCoordinates = peopleObject?["Coordinates"] as? String
let peoplecaption = peopleObject?["caption"] as? Int //is timestamp
let peoplepostID = peopleObject?["postID"] as? String
let coordSnap = people.childSnapshot(forPath: "Coordinates")
guard let lat = coordSnap.childSnapshot(forPath: "latitude").value as? CLLocationDegrees else { return }
guard let lon = coordSnap.childSnapshot(forPath: "longitude").value as? CLLocationDegrees else { return }
let locCoord = CLLocation(latitude: lat, longitude: lon)
let coordSnap12 = people.childSnapshot(forPath: "caption").value as? Int ?? 0
let date = Date(timeIntervalSince1970: TimeInterval(coordSnap12)/1000.0)
//let secondsInDay = 86400
**if Calendar.current.isDateInToday(date)** {
let distance = locCoord.distance(from: self.dict)
print(distance, "distancexy")
**if distance/1609.344 < 3000**{
let peopl = Userx(Coordinates: peopleCoordinates, distance:distance, caption: peoplecaption, postID: peoplepostID)
self.people.append(peopl)
let d = people.key as! String
self.printPersonInfo(uid:d) ///////This is used to reload the data
} else {
print ("w")
}
} else {
print ("alphaaa")
}
}
print("aaaaaaaa", self.people.map {$0.distance})
}
self.people.sort { ($0.distance ?? 0) < ($1.distance ?? 0) } ////////This sorting with distance is used with returning the cell. people is used as uid array to return the cell.
}
})
} else {
print("no")
}
})
Ancillary caveat: the self.people.sort { ($0.distance ?? 0) < ($1.distance ?? 0) }sorting is important, so the queries should not impede that. I am a bit concerned with using queryOrdered in that it orders the array of users in the wrong order. If it does, a C) query should be: The order of the users must be with the closest users to the logged in user first. The furthest from the logged in user must go last in the array.
Another way of asking this would be: Instead of running a snapshot of all users, how do you query the snapshot's 'end result sort' when making the snapshot?
The timestamp is seconds since 1970
My attempt at the date query below. I took the code and tried to put the code that gets the date before the actual query(currently the code that gets the date is after the snapshot of all users).
var ppp: String! ////this should be the uid of all users in db
let people = Database.database().reference().child("people").child(self.ppp).child("captions")
people.observe(DataEventType.value, with: { snapshot in
let captionss = snapshot.value as? Int ?? 0
let date = Date(timeIntervalSince1970: TimeInterval(captionss)/1000.0)
let query1 = Database.database().reference().child("people").queryOrdered(byChild: "caption").where?(isDateInToday(date))
Edit: This answer is in Firestore, not Realtime Database. However, the concepts are the same.
The question is several questions in one; asking about distance, compound queries and how to query Firebase in general. I will post this answer to address the second two and distance queries are addressed in the comment to the question.
Once the query pattern is understood, they become easier and most importantly; it becomes more obvious that how your data is structured depends on what queries you want to run against that data.
Suppose we have a users collection with user documents - each documentId is the users uid
users
uid_0
name: "Leroy"
and then we have the posts for the users - each post contains some text, a timestamp of the post, the uid of the user that posted it, what the topic is and a url of a picture that appears in the post. Notice I am storing posts in a separate collection; why read in a bunch of user data when we only want to know about their post.
posts
post_id
postText: "pretty flowers"
postDate: "20201103"
postUrl: "www....."
postUid: "uid_0"
postTopic: "flowers"
Let suppose we want to get posts from today that are about flowers, and then also get the posters name and output who posted the message and what they said.
To do this we will need a compound query and then a subquery to retrieve the posters name as well.
func getTodaysPostsAboutFlowers() {
let postsCollection = self.db.collection("posts")
let query = postsCollection.whereField("postDate", isEqualTo: "20201103").whereField("postTopic", isEqualTo: "flowers")
query.getDocuments(completion: { snapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else { return }
for doc in docs {
let postText = doc.get("postText") as? String ?? "No text"
guard let postersUid = doc.get("postUid") as? String else { return }
self.outputPostTextAndUserName(withText: postText, andUid: postersUid)
}
})
}
The above performs a compound query on both the postDate field as the postTopic field.
The above then calls another function to retrieve the users name and output both the name and what they said
func outputPostTextAndUserName(withText: String, andUid: String) {
let usersCollection = self.db.collection("users")
let theUserDoc = usersCollection.document(andUid)
theUserDoc.getDocument(completion: { documentSnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
if let doc = documentSnapshot {
let postersName = doc.get("name") as? String ?? "No Name"
print("\(postersName) posted: \(withText)")
}
})
}
and the output
Leroy posted: pretty flowers
As you can see, there's no need to load all of the users, no need to iterate over results etc. Even if you have a billion users, this will only return a subset of that data which is a best practice when working with huge data sets; only get the data you're interested in.
Edit. The OP is asking about querying for nodes containing today. The simple solution is to have one child node containing a timestamp which would contains specific date data and then another child node just containing today data in YYYYMMDD format.
people
uid_x
timetamps: 9023490823498 //Date(timeIntervalSince1970:
todaystamp: "20201106" // yyyymmdd format
that makes querying for nodes that contain today very simple.

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.

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

Crash while retrieving data from Firebase [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
My Code
let ref = Database.database().reference().child("PostInfo")
let query = ref.queryOrdered(byChild: "post_title").queryEqual(toValue: self.retrieve_title)
query.observeSingleEvent(of: .value) { (snapshot) in
print((snapshot.childSnapshot(forPath: "status").value as? String)!)
}
}
jSon Data
{"PostInfo":{
"-KyjkkEAZeHLjdRLg20w" : {
"postImage" : "https://firebasestorage.googleapis.com/v0/b/hobbyquest-ee18d.appspot.com/o/8C40BA04-6D8D-4A23-B8BB-E1B3AC64E66F.png?alt=media&token=3f0f10e3-a64b-4187-9259-3c25bfc4a9e5",
"post_title" : "hahahahah",
"status" : "a banana an",
"threadForHobby" : "Bowling",
"userID" : "ccuvHt6feYVIO6GUXKo3OpO6VUn2"}
}
I am trying to get the status data from firebase but the app keeps crashing. Please help!
The problem is this line
(snapshot.childSnapshot(forPath: "status").value as? String)!
Your code is reading the data by .value.
.value returns ALL nodes within the given node and you will need to iterate over them. For example. Suppose our database looks like the following and you are querying for posts with a post_title of title_0
PostInfo
post_0
postImage = "www.thing.com"
status = "status_0"
post_title = "title_0"
post_1
postImage = "www.yipee.com"
status = "status_1"
post_title = "title_1"
post_2
postImage = "www.dude.com"
status = "status_2"
post_title = "title_0"
When running your query, both post_0 and post_2 will be returned because they both have title_0
You would need to iterate over the snapshot to get the results.
let ref = self.ref.child("PostInfo")
let query = ref.queryOrdered(byChild: "post_title").queryEqual(toValue: "title_0")
query.observeSingleEvent(of: .value) { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let x = (snap.childSnapshot(forPath: "status").value as? String)!
print(x)
}
}
If you notice - your line of code works in this scenario because it's examining each child as a separate snapshot.
On the other hand, if you only want to return the first match, you can use .childAdded, which will return just an individual node:
let ref = self.ref.child("PostInfo")
let query = ref.queryOrdered(byChild: "post_title").queryEqual(toValue: "title_0")
query.observeSingleEvent(of: .childAdded) { (snapshot) in
let x = (snapshot.childSnapshot(forPath: "status").value as? String)!
print(x)
}
I don't know why but you could do it a different way but changing the layout of your data.
The data would be like this:
{"PostInfo":{
"searchidentifier" : { //whatever identifier you want, I think you're trying to use the post title
"object" : {
"postImage" : "https://firebasestorage.googleapis.com/v0/b/hobbyquest-ee18d.appspot.com/o/8C40BA04-6D8D-4A23-B8BB-E1B3AC64E66F.png?alt=media&token=3f0f10e3-a64b-4187-9259-3c25bfc4a9e5",
"post_title" : "hahahahah",
"status" : "a banana an",
"threadForHobby" : "Bowling",
"userID" : "ccuvHt6feYVIO6GUXKo3OpO6VUn2"}
}
}
And you would retrieve your data like this:
let ref = Database.database().reference().child("PostInfo").child("\(self.retrieve_title)")
ref.observe(.value, with: { (snapshot) in
if snapshot.childrenCount > 0 {
for classes in snapshot.children.allObjects as![FIRDataSnapshot] {
let classesObject = classes.value as? [String: AnyObject]
let postImage = classesObject?["postimage"]
//Retrieve all the other objects here as well
}
}
})