Swift -Firebase how to know if .childAdded is empty? - ios

I know when using .value I can check to see if there is anything there using .exists()
In this situation I’m running my queries in a SearchController and I need multiple results to get returned. Everything works fine when there are results but if there aren’t any I have no way of knowing.
How can I check if .childAdded returns nothing?
// if the user types in McDonalds and there are some then multiple will appear but if none then show a label that says “no results”
let searchText = searchController.searcchBar.text?.loweredCase() else { return }
Database.reference.child("restaurants")
.queryOrdered(byChild:"restaurantName")
.queryStarting(atValue: searchText)
.queryEnding(atValue: searchText + “\u{f8ff}”)
.observe( .childAdded, with: { (snapshot) in
// no McDonalds appear so show a no results label
})

Since .childAdded only fires for existing child nodes that match the query, you can't use it to detect when there are no matches. To detect if there are no matches, you'll need to use a .value listener and check snapshot.exists.
You can either use that in addition to your existing .childAdded listener (the Firebase client deduplicates the requests behind the scenes, so no extra data will be transferred, or you can use the .value listener to handle the results too. In the latter case you will need to loop over snapshot.children to get to the individual result nodes.

Related

iOS Firebase Paginate by Date

I have a notes app. I normally paginate the below posts-userIds node by childByAutoId which works fine. I allow users to make edits, at which point I just update everything and add an editDate to the post itself. But that editDate is at the posts ref, not the posts-usersIds ref.
How can I paginate the posts-usersIds ref by editDate? The issue is editDates are optional, meaning they might make 200 posts, but only 2 edits or even none at all. Either way if the user wants to see the editDates first they still need to see the postDates along with them
Order would be editDates first then postDates second or if there aren't any editDates just show them all of the postDates
I was thinking instead of using a 1 as the value maybe I should put a postDate or editDate (if they made one) as the value to the posts-userIds ref as in
-postId-123: 1 // this what I normally use, it has no meaning, just a 1 so that it has a value
-postId-123: replace the 1 with the postDate and change this to editDate when they make an edit
Maybe I could then use ref.queryOrderedByValue() but I'm not sure.
My structure is the following:
#posts
#postId-123 // childByAutoId
-uid: "userId-ABC"
-postDate: 1640898417.456389
-editDate: 1640814049.713224 // edit date is only there if the user made an edit
#posts-userIds
#userId-ABC
-postId-123: 1
I paginate
let ref = Database.database().reference().child("posts-userIds").child(Auth.auth().currentUser!.uid)
if startKey == nil {
ref.queryOrderedByKey()
.queryLimited(toLast: 20)
.observeSingleEvent(of: .value, with: { (snapshot) in
// get posts and set startKey
})
} else {
ref.queryOrderedByKey()
.queryEnding(atValue: startKey!)
.queryLimited(toLast: 21)
.observeSingleEvent(of: .value, with: { (snapshot) in
})
}
Firebase queries can only order/filter on a value that is (in a fixed path) under the node that it's considering to return. So you can't posts-userIds on values from under posts.
The solution is to duplicate the value that you want to order/filter on under posts-userIds, either directly as the value (in which case you'll use queryOrderedByValue for the order), or in a child property (in which case you'll use queryOrdered(byChild:) for the order. I tend to favor the latter, simply because once there's one value you want to order/filter on, there'll often be more of them down the line.
This sort of data duplicating is quite common in NoSQL databases, so if you're not yet comfortable with it, I recommend reading NoSQL data modeling, and watching Firebase for SQL developers.

ios - Repeating Keys when paginating with Firebase

I am learning pagination with Firebase. I am using a method in which I store the key of the last added item in the previous page, so the next page can continue from there.
The problem is that when using ref.queryStarting(at value: lastItemKey) to keep retrieving items from the last added key, the last item gets repeated twice (since queryStarting is inclusive).
And so if I limit to 5 the query I would end up with only 4 new items as 1 would be a duplicate.
The only solution I came up is requesting one more item and remove the repeated one, but I wonder if it´s efficient at all doing it this way. (since we are wasting one item in each query)
If it´s any help, my code looks like this:
// rest of the pages
if let lastItemID = lastItemKey {
itemPageRef = self.itemsRef.queryOrderedByKey().queryStarting(atValue: lastItemID)
.queryLimited(toFirst: UInt(amount))
} else {
// First page of data: we retrieve the first (amount) items
print("We are in the first page of DATA")
itemPageRef = self.itemsRef.queryOrderedByKey().queryLimited(toFirst: UInt(amount))
}
itemPageRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in
Requesting an overlapping child node between the pages is the only way the Firebase API supports. Since there is no other way to do this, there isn't a more efficient way.
That said, it's typically quite efficient, especially if you use a page size of 25+ child nodes, which is also more reasonable on most use-cases I've seen.

Remove an entire child from Firebase using Swift

I'd like to write a function to remove an entire child from Firebase. I'm using XCode 10 and swift 3+.
I have all the user info of the child I'd like to delete so I assume the best call would be to iterate through every child and test for the matching sub child value but it would be great if there was a faster way.
Thanks for the help!
Heres what I'd like to delete
I assume testing for epoch time then removing the whole node would be ideal. Also not sure how to do this
I understand you don't have access to the key of the node you want do delete. Is that right? Why not? If you use the "observe()" function on a FIRDatabaseQuery object, each returned object should come with a key and a value.
Having a key it is easy to remove a node, as stated in the Firebase official guides.
From the linked guide,
Delete data
The simplest way to delete data is to call removeValue on a reference
to the location of that data.
You can also delete by specifying nil as the value for another write
operation such as setValue or updateChildValues. You can use this
technique with updateChildValues to delete multiple children in a
single API call.
So, you could try:
FirebaseDatabase.Database.database().reference(withPath: "Forum").child(key).removeValue()
or
FirebaseDatabase.Database.database().reference(withPath: "Forum").child(key).setValue(nil)
If you can't get the key in any way, what you said about "iterating" through the children of the node could be done by using a query. Here's some example code, supposing you want all forum posts by Jonahelbaz:
return FirebaseDatabase.Database.database().reference(withPath: "Forum").queryOrdered(byChild: "username").queryEqual(toValue: "Jonahelbaz").observe(.value, with: { (snapshot) in
if let forumPosts = snapshot.value as? [String: [String: AnyObject]] {
for (key, _) in forumPosts {
FirebaseDatabase.Database.database().reference(withPath: "Forum").child(key).removeValue()
}
}
})
Here you create a sorted query using as reference "username" then you ask only for the forum posts where "username" are equal to Johanelbaz. You know the returned snapshot is an Array, so now you iterate through the array and use the keys for deleting the nodes.
This way of deleting isn't very good because you might get several posts with the same username and would delete them all. The ideal case would be to obtain the exact key of the forum post you want to delete.

Firebase observeEventType .childAdded doesn't work as promised

When I call this observe function from in my viewcontroller, the .childadded immediately returns a object that was already stored instead of has just bin added like .childadded would suspect.
func observe(callback: RiderVC){
let ref = DBProvider.Instance.dbRef.child("rideRequests")
ref.observe(DataEventType.childAdded) { (snapshot: DataSnapshot) in
if let data = snapshot.value as? NSDictionary {
let drive = cabRide(ritID: ritID, bestemming: bestemming,
vanafLocatie: vanaf, taxiID: taxiID, status: status)
print(drive)
callback.alertForARide(title: "Wilt u deze rit krijgen?", message: "Van: \(vanaf), Naar: \(bestemming)", ritID: ritID)
}
}
}
When I try this function with .childchanged, I only get a alert when it is changed like it suppose to do, but when doing .chiladded, it just gets all the requests out of the database and those requests were already there.
When I add a new request, it also gives an alert. So it works, but how can I get rid of the not added and already there requests?
Does anybody know this flaw?
This is working exactly as promised. From the documentation:
Retrieve lists of items or listen for additions to a list of items.
This event is triggered once for each existing child and then again
every time a new child is added to the specified path. The listener is
passed a snapshot containing the new child's data.
That might seem weird at first, but this is generally what most developers want, as it's basically a way of asking for all data from a particular branch in the database, even if new items get added to it in the future.
If you want it to work the way you're describing, where you're only getting new items in the database after your app has started up, you'll need to do a little bit of work yourself. First, you'll want to add timestamps to the objects you're adding to the database. Then you'll want to do some kind of call where you're asking to query your database by those timestamps. It'll probably look something like this:
myDatabaseRef.queryOrdered(byChild: "myTimestamp").queryStarting(atValue: <currentTimestamp>)
Good luck!

Can someone clearly explain difference between .Value, .ChildAdded, .ChildChanged, .ChildRemoved for FIRDataEventType?

I'm having trouble putting it into words. Can someone explain what the difference between the different FIRDataEventTypes and examples of when it would be used?
Example (SWIFT):
let queryRef = FIRDatabase.database().reference().child("user")
queryRef.observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
or
queryRef.observeEventType(.Value, withBlock: { (snapshot) -> Void in
From testing, .Value returns one object while .ChildAdded returns multiple; When doing advanced queries .ChildAdded doesn't work but .Value somewhat works (deeper children are null).
tl;dr - Watch this video. It uses the old SDK in Android, but the idea is the exact same even for iOS.
Each one of these events is a specific way to handle synchronization of data across clients.
The Value event will fire each time any piece of data is updated. This could be a newly added key, a deletion of a key, or an update of any value at the reference. When the change happens the SDK sends back the entire state of the object, not the delta just change that occurred.
The Child added event will fire off once per existing piece of data, the snapshot value will be an individual record rather than the entire list like you would get with the value event. As more items come in, this event will fire off with each item.
The Child removed and changed events work almost the same. When an item is deleted or has it's value changed, the individual item is returned in the callback.

Resources