Firebase with Swift 3 counting the number of children
Firebase data is loaded (and synchronized) asynchronously. This is easiest to see if you add some debug logging:
let ref = firebase.child("users").child(fUID).child("participating")
print("Starting observing");
ref.observe(.value, with: { (snapshot: FIRDataSnapshot!) in
print("Got snapshot");
print(snapshot.childrenCount)
rooms.count = snapshot.childrenCount
})
print("Returning count");
return rooms.count
When you run this snippet, the logging output will be:
Start observing
Returning count
Got snapshot
This is probably not the order you expected the output to be in. And it also explains why your count will never be correct: the data hasn't been loaded yet, so it can't be counted.
This is the reason why Firebase listeners work with callback blocks: the block is invoked when the data is synchronized.
how do you get children count from Firebase in swift?
That's weird... what you posted works for me... maybe the DataObject is not what is expected... Test this by adding a breakpoint and seeing what snapshot is.
You can try this instead... it should yield same result as the one that doesn't work for you, so maybe it won't work either:
snapshot.value.count
EDIT:
Ah, I think I know why you're not able to get the children count property! Try casting snapshot to FDataSnapShot! So try
ref.observeEventType(.Value, withBlock: { (snapshot: FDataSnapshot!) in
count += snapshot.childrenCount
})
Count Firebase Database children and display
If you're listening for .childAdded
already, you can just keep a counter and increment that:
var nodeCount: Int = 0
ref.child("offers").observe(.childAdded, with: { snapshot in
nodeCount = nodeCount + 1
counterLbl.text = String(nodeCount)
}
If your use-case also makes it possible that nodes are removed from the database, you should also listen for .childRemoved
to decrement the counter:
ref.child("offers").observe(.childRemoved, with: { snapshot in
nodeCount = nodeCount - 1
counterLbl.text = String(nodeCount)
}
More advanced scenario
Note that this approach requires that you download all nodes that you want to count. This should work fine in your current scenario, since you're downloading all offers anyway. But as you get more data, you might want to only read/display a subset of the offers, and in that case the code above would only count the nodes in that subset.
If you still want the count of all offers in that case, the common approach is to keep a separate counter value in the database, that you update every time you add/remove an offer. For more on this, see:
- In Firebase, is there a way to get the number of children of a node without loading all the node data?
- How to get size of an element/list in Firebase without get it all?
swift firebase nested children count
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.
Swift 4 and Firebase How to count child values
The code you shared doesn't read any data, but only updates it with updateChildValues
.
To count the number of child nodes, you'll need to read those nodes and then call DataSnapshot.childrenCount
.
FIRDatabase.database().reference().child("going").child(postId).observe(DataEventType.value, with: { (snapshot) in
print(snapshot.childrenCount)
})
If you only want to count the child nodes that have a value of 1, you'd do:
FIRDatabase.database().reference().child("going").child(postId)
.queryOrderedByValue().queryEqual(toValue: 1)
.observe(DataEventType.value, with: { (snapshot) in
print(snapshot.childrenCount)
})
For more on this, read the Firebase documentation on sorting and filtering data.
Swift -FirebaseDatabase how to count and iterate through children that are only keyIds?
Mixing different entity types under a node is a bad idea for many reasons, one of them being the one you encountered here. I recommend restructuring your data model to have two top-level lists, with the names properties under one of these, and the push IDs under another.
That said, on your current structure you can get the count by:
- Excluding the properties you don't want to count.
- Subtracting the number of properties you don't want to count.
The first of these would look like this:
let excludedKeys = ["address", "city", "state", "zipcode", "country"]
Database.database().reference().child("locationRef")
.queryOrderedByKey()
.queryLimited(toLast: 10)
.observeSingleEvent(of: .value) { (snapshot) in
self.totalCountOfKeyIds = 0
for childSnapshot in snapshot.children.allObjects as! [DataSnapshot] {
for child in childSnapshot.children.allObjects as! [DataSnapshot] {
if !excludedKeys.contains(child.key) {
self.totalCountOfKeyIds = self.totalCountOfKeyIds + 1
}
}
}
})
Firebase: Query ordered by children count
There's no native query that lets you order by number of children. Instead, you'll have to maintain a count of the posts, stored in another child, and order the hashtags by the value of that.
You have two choices for this. You can maintain the count on the client whenever it adds a post to a hashtag. Or, write a Cloud Function that automatically keeps the count up to date whenever something changes. In either case, be sure to use a transaction to avoid conflict from multiple writers.
Also bear in mind that, because you want the reverse order of the count of posts, you'll have to store a value that naturally orders from most posts to least posts. You could store a negative count to make sure the most popular posts (which therefore have the smallest values) are ordered ahead of the least popular (which have the largest values).
Related Topics
Gamecenter Authentication in Landscape-Only App Throws Uiapplicationinvalidinterfaceorientation
How to Apply a Vignette Cifilter to a Live Camera Feed in iOS
Reasons for Rejecting iPhone Application by Apple Store
Using Custom Font in a Uiwebview
Auto-Layout: What Creates Constraints Named Uiview-Encapsulated-Layout-Width & Height
Module Compiled with Swift 4.2.1 Cannot Be Imported by the Swift 5.0 Compiler
How to Use Cgaffinetransformmakerotation
Open Mobile Safari from a Link in a Webview
Creating Framework That Requires (Depends On) Another Framework
Xcode 10.2 with Swift 5.0 Compiler - Protocol Inheritance Issue
Core Data in Swift: Only Saving Last Object in a for Loop
Xcode 6 - How to Pick Signing Certificate/Provisioning Profile for Ad-Hoc Distribution
Difference Between _ and Self. in Objective-C
Why Push Notifications Is Not Working on Testflight
This Certificate Has an Invalid Issuer:Keychain Marks All Certificates as "Invalid Issuer"
Centering Mkmapview on Spot N-Pixels Below Pin
How to Get Only Images in the Camera Roll Using Photos Framework