Counting Records In Swift From Firebase With A Where Statement - swift

I am trying to return the count of posts for a specific user where a field contains a certain value. The structure I am using in Firebase is here:
{
"posts" : {
"-Lyzpsb1hH4LUPvcXE3H" : {
"beverageCategory" : "Beer",
"beverageName" : "Dortmunder",
"beveragePrice" : "3.99",
"beverageRating" : 3,
"beverageType" : "Lager",
"imageUrl" : "https://firebasestorage.googleapis.com/v0/b/socialspirit-37cae.appspot.com/o/post-pics%2F3C23E923-A3BD-4393-B3EF-8F939D8B08B8?alt=media&token=ac80cb98-f2a1-4197-b328-2a69a2ac1fd1",
"wineVintage" : ""
}
},
"user" : "sdkjvksdjf",
"users" : {
"0hyN8N2klJWqPi2lkRqNK3vg7z63" : {
"posts" : {
"-Ly6ciYHm7v5JFy1VmVY" : true,
"-Ly6cyLukI6aRRki5yna" : true
},
"provider" : "Firebase"
},
"26OUuaRZEVWUmkLJp13LzirGbs13" : {
"posts" : {
"-Ly6fGmQEMZz3c-azMnJ" : true
},
"provider" : "Firebase"
},
"5r6FulsvIRap7pLK5D3zV8qyPVv1" : {
"provider" : "Firebase"
},
"gQpktBMh97hTqiysHBwvVLZl70y1" : {
"posts" : {
"-Lyzpsb1hH4LUPvcXE3H" : true,
"-LyzqFgvmrBgdsgKSt_5" : true,
"-Lz-fIMDal00ex3_viQo" : true,
"-Lz-lXsSBqKlcf8hBext" : true,
"-Lz2eNLfk1PFEVkEgmwa" : true,
"-Lz2etF0UqFqLkdGOR13" : true,
"-Lz2fGUi0qzJtniNr5LX" : true,
"-Lz2gP5c47yHDO2g0ljr" : true,
"-Lz2gdhfedZfTtzjP2ee" : true,
"-Lz2gllC3caXgPf2VAPU" : true,
"-Lz2hH8FbNmBEOLcHCJz" : true,
"-Lz2jzkXuEZ3Cfe96eGW" : true,
"-Lz3RvuW4fMXarhW7vLv" : true,
"-Lz3S8YA0vgk4ZrjL_Kk" : true
},
"provider" : "Firebase"
}
}
}
I am using the following code to get the count of records that have a beverageCategory of "Beer".
DataService.ds.REF_USERS.child("\(uid)").child("posts").child("beverageCategory")
.queryOrderedByValue().queryEqual(toValue: "Beer")
.observe(DataEventType.value, with: { (snapshot) in
print("SNAP - \(snapshot.childrenCount)")
})
The childrenCount returns 0, however. Any idea what I'm doing wrong here? Thanks!
EDIT: I added the JSON for the data structure and I think I left an important detail out. The data structure actually has users that have posts and the associated postid that links back to posts. I think what I have to do is first find the user's posts and then of those posts get a total count for each category. I'm guessing that is slightly different than the methods I tried. Any ideas on that?
EDIT 2: So my issues is that users and posts are in different structures. I need to find all of the user's posts and iterate through them to see if what the beverageCategory is. I have done that with the following code. The issue with the code below is that the count is doubled when I add a post and then open the menu. But when I exit the menu and re-open the count is correct. Why is it double-counting?
func myFirebaseNetworkDataRequest(finished: #escaping () -> Void) {
beerCountArray.removeAll()
wineCountArray.removeAll()
liquorCountArray.removeAll()
print("BEER ARRAY \(beerCountArray.count)")
guard let uid = Auth.auth().currentUser?.uid else {
return
}
let newPost = DataService.ds.REF_USERS.child("\(uid)").child("posts")
newPost.observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
//print("SNAPSHOT - \(snapshot)")
for snap in snapshot {
let postData = DataService.ds.REF_POSTS.child(snap.key)
//print("SNAP KEY - \(snap.key)")
let bevCat = DataService.ds.REF_POSTS.child(snap.key).child("beverageCategory")
//print("BEV CAT - \(bevCat)")
postData.observe(.value, with: { (snapshot) in
if let postDict = snapshot.value as? Dictionary<String, AnyObject> {
let key = snapshot.key
let post = Post(postKey: key, postData: postDict)
//print("POST DICT - \(String(describing: postDict["beverageCategory"]!))")
if postDict["beverageCategory"]! as! String == "Beer" {
self.beerCountArray.append(1)
//print("BEER ARRAY LOOP - \(self.beerCountArray)")
}
if postDict["beverageCategory"]! as! String == "Wine"{
self.wineCountArray.append(1)
}
if postDict["beverageCategory"]! as! String == "Liquor" {
self.liquorCountArray.append(1)
}
//self.posts.append(post)
}
finished()
})
}
}
})
}
And...
override func viewDidLoad() {
super.viewDidLoad()
beerCountArray.removeAll()
wineCountArray.removeAll()
liquorCountArray.removeAll()
myFirebaseNetworkDataRequest {
//print("BEER ARRAY - \(self.beerCountArray.count)")
self.beerCount.text = String(self.beerCountArray.count)
self.liquorCount.text = String(self.liquorCountArray.count)
self.wineCount.text = String(self.wineCountArray.count)
}
}

Your query is not correct. Since you're trying to filter on a child property of each node under posts, you should call queryOrdered(byChild:).
So:
DataService.ds.REF_USERS.child("posts")
.queryOrdered(byChild: "beverageCategory")
.queryEqual(toValue: "Beer")
.observe(DataEventType.value, with: { (snapshot) in
print("SNAP - \(snapshot.childrenCount)")
})
Also see the Firebase documentation on ordering and filtering data.

Related

Swift firebase pagination

I am working on my pagination feature and so far was able to paginate posts by timestamp.
The issue is in the version I use (from a course) that it queries posts depending on its value always starting from a value a bit higher than before which works fine when using timestamp as the numbers are really high and more likely to differ.
Now I want to do the pagination stuff with my posts ordered by likeCount. The issue now is that as there are many posts/comments with equal likes the pagination doesnt work properly anymore.
Here Is my code
func observeAllPostsForTrendByLikeCount(start likeCount: Int? = nil, limit: UInt, completionHandler: #escaping ([(Post, UserModel)]) -> Void) {
var postQuery = REF_POST.queryOrdered(byChild: "likeCount")
if let latestPostLikeCount = likeCount, latestPostLikeCount >= 0 {
postQuery = postQuery.queryStarting(atValue: latestPostLikeCount + 1, childKey: "likeCount").queryLimited(toLast: limit)
} else {
postQuery = postQuery.queryLimited(toLast: limit)
}
postQuery.observeSingleEvent(of: .value, with: { (snapshot) in
let items = snapshot.children.allObjects
let myGroup = DispatchGroup()
var results: [(post: Post, user: UserModel)] = []
for (index, item) in (items as! [DataSnapshot]).enumerated() {
myGroup.enter()
API.Post.observePost(withId: item.key, completion: { (post) in
API.User.observeUser(withId: post.userId!, completion: { (user) in
results.insert((post, user), at: index)
myGroup.leave()
})
})
}
myGroup.notify(queue: .main) {
results.sort(by: {$0.0.likeCount! > $1.0.likeCount!})
completionHandler(results)
}
})
}
func loadMoreTrendByLikeCount(start likeCount: Int, limit: UInt, completionHandler: #escaping ([(Post, UserModel)]) -> Void) {
let postOrderedQuery = REF_POST.queryOrdered(byChild: "likeCount")
let postLimitedQuery = postOrderedQuery.queryEnding(atValue: likeCount , childKey: "likeCount").queryLimited(toLast: limit)
postLimitedQuery.observeSingleEvent(of: .value, with: { (snapshot) in
let items = snapshot.children.allObjects
let myGroup = DispatchGroup()
var results = [(post: Post, user: UserModel)]()
for (index, item) in (items as! [DataSnapshot]).enumerated() {
myGroup.enter()
API.Post.observePost(withId: item.key, completion: { (post) in
API.User.observeUser(withId: post.userId!, completion: { (user) in
results.insert((post, user), at: index)
myGroup.leave()
})
})
}
myGroup.notify(queue: .main) {
results.sort(by: {$0.0.likeCount! > $1.0.likeCount! })
completionHandler(results)
}
})
}
This code works fine as long as the posts have different like counts but as I scroll deeper and face multiply posts with 0 likes it doesn't load further.
here is how I fetch the posts
func loadTrendByLikeCount() {
isLoadingPost = true
API.Post.observeAllPostsForTrendByLikeCount(start: posts.first?.likeCount, limit: 3) { (results) in
if results.count > 0 {
results.forEach({ (result) in
if self.segmentedControl.selectedSegmentIndex == 1 {
self.posts.append(result.0)
self.users.append(result.1)
}
})
}
self.isLoadingPost = false
if self.refreshControl.isRefreshing {
self.refreshControl.endRefreshing()
}
self.activityIndicatorView.stopAnimating()
self.tableView.reloadData()
}
}
and the scroll method
if scrollView.contentOffset.y >= scrollView.contentSize.height - self.view.frame.size.height {
guard !isLoadingPost else {
return
}
isLoadingPost = true
guard let lastPostLikeCount = self.posts.last?.likeCount else {
isLoadingPost = false
return
}
self.Indicator.startAnimating()
API.Post.loadMoreTrendByLikeCount start: lastPostLikeCount, limit: 4) { (results) in
if results.count == 0 {
self.Indicator.stopAnimating()
return
}
for result in results {
if !self.posts.contains(where: {$0.id == result.0.id}) {
self.posts.append(result.0)
self.users.append(result.1)
}
}
self.tableView.reloadData()
self.Indicator.stopAnimating()
self.isLoadingPost = false
}
}
As I said this works and loads more post as long as not too many posts are at the same likecount.
Moreover the loadMore() function ends in an infinity loop...
Thanks in advance
So first let me explain what is happening here. When you call loadMoreTrendByLikeCount you provide a starting like count let's say 10 likes in this example and you are asking Firebase to get the next limit number of posts. In this example let's assume the limit is 5 and we have the following posts:
"posts": {
"key-1" : {
"likes" : 10,
"timestamp" : 1514852613107,
"userID" : "iijhsPlIxbWrdQty5nn9IUAZRY63"
},
"key-2" : {
"likes" : 10,
"timestamp" : 1515369388635,
"userID" : "iijhsPlIxbWrdQty5nn9IUAZRY63"
},
"key-3" : {
"likes" : 10,
"timestamp" : 1515538024957,
"userID" : "iijhsPlIxbWrdQty5nn9IUAZRY63"
},
"key-4" : {
"likes" : 10,
"timestamp" : 1515700542687,
"userID" : "iijhsPlIxbWrdQty5nn9IUAZRY63"
},
"key-5" : {
"likes" : 10,
"timestamp" : 1515704869452,
"userID" : "iijhsPlIxbWrdQty5nn9IUAZRY63"
},
"key-6" : {
"likes" : 10,
"timestamp" : 1515704970865,
"userID" : "iijhsPlIxbWrdQty5nn9IUAZRY63"
},
"key-7" : {
"likes" : 10,
"timestamp" : 1515773432428,
"userID" : "iijhsPlIxbWrdQty5nn9IUAZRY63"
},
"key-8" : {
"likes" : 10,
"timestamp" : 1515872606809,
"userID" : "iijhsPlIxbWrdQty5nn9IUAZRY63"
},
"key-9" : {
"likes" : 0,
"timestamp" : 1515970044560,
"userID" : "iijhsPlIxbWrdQty5nn9IUAZRY63"
}
}
Now we perform the following query: loadMoreTrendByLikeCount(start likeCount: 10, limit: 5) We will get the following posts key-8, key-7, key-6, key-5, key-4. When you query again you will get the same 5 posts because the query starts in the same place. (i.e. your limit is not large enough to get all posts with 10 likes, thus the starting position stays the same)
Now, I am not sure how to resolve this issue. I have only ever used pagination with the autoID keys which are unique so this issue cannot occur. The only thing I can think of is to detect that you are querying the same posts again and increase the limit so you get new posts. You'll have to keep increasing this limit until you get past all the posts with the same number of likes. Depending on the number of posts this can take awhile and be very inefficient.
Ideally, Firebase would support querying all posts between say 10-11 likes to avoid this problem, but that isn't possible. It looks like you have to somehow store another value related to the likes that allows you to filter as desired (for example if you had "filterLikes": 10.0, "filterLikes": 10.1, "filterLikes": 10.2, etc) . Take a look at this question it might be helpful.

Is it possible to get database reference without childByAutoId (). Key?

Is there a way to dynamically get a child database from the parent instance, without having to use childByAutoId () ?
{
"artists" : {
"artists_id_1" : {
"name" : "Atif Aslam",
"genre" : "Rock"
},
"artists_id_2" : {
"name" : "Arijit Singh",
"genre" : "Rock"
}
}
}
we usually refer to the path, and listener the items the DataBase...Example:
Database.database().reference().child("artists").child("artist_id_1").observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
print("\(String(describing: dictionary["name"] as? String))")
}
}, withCancel: nil)
it would only work for the first item. Because, it would not be dynamic. What is normally used is the following example below ...
{
"artists" : {
"-KeZUDrJv555kteAcssL-" : {
"name" : "Atif Aslam",
"genre" : "Rock"
},
"-KeZUVXFIQdO7JiyRYk-" : {
"name" : "Arijit Singh",
"genre" : "Rock"
}
}
}
Database.database().reference().child("artists").childByAutoId().observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
print("\(String(describing: dictionary["name"] as? String))")
}
}, withCancel: nil)
Anyway, I do not know if it was clear, anything can comment before answering. Thank you
To display all artists in your database, load the artists node and loop over snapshot.children:
Database.database().reference().child("artists").observe(.value) { (snap:DataSnapshot) in
for child in snapshot.children {
print(child.key)
}
}
For this and more examples, I recommend reading the Firebase documentation, specifically the section on reading and writing lists.
I dont know if i understand your question correctly or what you are trying to do but maybe you can try
Database.database().reference().child("artists").observe(.value) { (snap:DataSnapshot) in
// This should print out your childIds
print(snap.key)
// This should print out the values after them
print(snap.value)
}

Get child nodes from firebase (Swift)

How can i get child nodes from firebase?
Firebase struct is here:
Me need to get a child "Photos" for each child above. And later add to collectionView each child "Photo".
My firebase struct:
{
"Студии" : {
"Бабулька" : {
"Photo" : {
"image" : "https://firebasestorage.googleapis.com/v0/b/test2-29e4b.appspot.com/o/city3.jpg?alt=media&token=12a87c0c-ade1-43f9-b76b-ecdaa86cf185"
},
"address" : "Сатурн",
"image" : "https://firebasestorage.googleapis.com/v0/b/test2-29e4b.appspot.com/o/city3.jpg?alt=media&token=12a87c0c-ade1-43f9-b76b-ecdaa86cf185",
"image1" : "https://firebasestorage.googleapis.com/v0/b/test2-29e4b.appspot.com/o/city3.jpg?alt=media&token=12a87c0c-ade1-43f9-b76b-ecdaa86cf185",
"name" : "Карапулька"
},
"Дубаи" : {
"Photo" : {
"image" : "https://firebasestorage.googleapis.com/v0/b/test2-29e4b.appspot.com/o/city1.jpg?alt=media&token=c442fd8f-4cc4-4e30-8242-975aaf5427dc"
},
"address" : "Тутушка",
"image" : "https://firebasestorage.googleapis.com/v0/b/test2-29e4b.appspot.com/o/city1.jpg?alt=media&token=c442fd8f-4cc4-4e30-8242-975aaf5427dc",
"name" : "Дубаи"
},
"Лондон" : {
"Photo" : {
"image" : "https://firebasestorage.googleapis.com/v0/b/test2-29e4b.appspot.com/o/oko1.jpg?alt=media&token=fedea2cb-93b1-496c-9392-0b95f4ada7b5"
},
"address" : "Бейкер стрит",
"image" : "https://firebasestorage.googleapis.com/v0/b/test2-29e4b.appspot.com/o/oko1.jpg?alt=media&token=fedea2cb-93b1-496c-9392-0b95f4ada7b5",
"name" : "Лондон"
}
}
}
Thanks for help.
To get to the data you want, iterate over all of the child notes of the main parent node. Assume the following structure
posts
post_0
Photo
image: "https://www....."
post_1
Photo
image: "https://www....."
and the code to get the image urls...
let ref = self.ref.child("posts")
ref.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let imageSnap = snap.childSnapshot(forPath: "Photo")
let dict = imageSnap.value as! [String: Any]
let url = dict["image"] as! String
print(url)
}
})

Trouble trying to retrieve data from Firebase

I have this piece of working code that saves data to Firebase:
let locRef = locationRef.childByAutoId()
let locItem = [
senderId : [
"location": getLocationID()
]
]
locRef.setValue(locItem)
And I want to retrieve the user's (identified by senderID) "location", so I tried this piece of code:
locationRef.child("location").child(senderId).observeSingleEventOfType(.Value, withBlock: { (snapshot) in
self.locationId = snapshot.value!["location"] as! String
}) { (error) in
print(error.localizedDescription)
}
However my app is crashing when the piece of code is run, and there are no errors. I think my mistake may be that .child("location") must be something else, but I do not know what.
Database structure (JSON):
{
"locations" : {
"-KLEdoj2eiF7EW9m0815" : {
"W6vSOHZLTwNM33JYqkKHvaIVRF13" : {
"location" : "Seattle, WA"
}
},
"-KLLfcOvYHwIufBALM0-" : {
"W6vSOHZLTwNM33JYqkKHvaIVRF13" : {
"location" : "London, United Kingdom"
}
},
Any help would be appreciated thanks!
You should be referring to your location as the following
FIRDatabase.database().reference().child("locations").child(locationId).child(senderId).observeSingleEventOfType(.Value, withBlock: { (snapshot) in
self.location = snapshot.value!["location"] as! String
}
For example:
If you have locationId = "-KLEdoj2eiF7EW9m0815" and senderId = "W6vSOHZLTwNM33JYqkKHvaIVRF13" the above call will set the self.location to "Seattle, WA"

Swift & Firebase | Checking if a user exists with a username

I am trying to allow users to start games with and follow other users by searching their username. I need to be able to make sure that a user with that username exists. I was using the following code but although the if is called the else does not get called when it should.
let checkWaitingRef = Firebase(url:"https://test.firebaseio.com/users")
checkWaitingRef.queryOrderedByChild("username").queryEqualToValue("\(username!)")
.observeEventType(.ChildAdded, withBlock: { snapshot in
if snapshot.value.valueForKey("username")! as! String == username! {
} else {
}
JSON data tree
{
"097ca4a4-563f-4867ghj0-6209288bd7f02" : {
"email" : "test1#tes1.com",
"uid" : "097ca4a4-563f-4867ghj0-6209288bd7f02",
"username" : "test1",
"waiting" : "0"
},
"55a8f979-ad0d-438u989u69-aa4a-45adb16175e7" : {
"email" : "test2#test2.com",
"uid" : "55a8f979-ad0d-438u989u69-aa4a-45adb16175e7",
"username" : "test2",
"waiting" : "0"
}
}
Easy fix:
Don't use .childAdded as the block will not execute when the query doesn't find anything.
Instead use .Value and check for NSNull
let checkWaitingRef = Firebase(url:"https://test.firebaseio.com/users")
checkWaitingRef.queryOrderedByChild("username").queryEqualToValue("\(username!)")
.observeEventType(.Value, withBlock: { snapshot in
if ( snapshot.value is NSNull ) {
print("not found)")
} else {
print(snapshot.value)
}
})