iOS callback if firebase query doesn't run (Swift Firebase) - ios

I have a query running to check .childAdded at a location in my database.
It works well when data is found, however, if there is not data at the location, it can't fire the query and therefore this does not allow me to use snapshot.exists because it doesn't even run the query.
This is my current code
let favouriteRef = self.databaseRef.child("users").child(userID!).child("Favourites")
// Code doesn't run past this line when no data at location
favouriteRef.queryOrderedByKey().observe(.childAdded, with: { (snapshot) in
let favouriteID = "\(snapshot.value!)"
let usersRef = self.databaseRef.child("users")
usersRef.observeSingleEvent(of: .value, with: { (users) in
for user in users.children {
let favCleaner = UserClass(snapshot: user as! DataSnapshot)
if favouriteID == favCleaner.uid {
tempFav.append(favCleaner)
}
}
self.hasFavourites = true
self.tableView.reloadData()
self.usersArray = tempFav
self.collectionView.reloadData()
})
})
I would like to find a way to receive a callback if the query doesn't run (no data at location)
Thanks.

If there is absolutely no data at the location, then obviously this event trigger is not sufficient for you to get the data because this event only gets triggered when something gets added.
So you have two options
Add another trigger of type .observeSingleEvent(of: .value, with: { (snapshot) in
// Get the values and write the relevant actions
}
Or you can update this event to type .observe(.value, with: {snapshot in // write the relevant code
}
Both approaches have their advantage and disadvantage.
First approach will need you to write more code while minimising the number of triggers from your database to your UI. In second approach, there will be more triggers to the UI but it can be fairly easy to code.
From what I can see, you are first trying to establish whether data exists in the favorite node of your database and then comparing it to another snapshot. So if the number of delete or update events are relatively small, my suggestion is to go for approach two.

Related

Firebase observe slow for large database

I am working on an app displaying places (downloaded from firebase) based on user location.
I have currently 5k entries and they are displayed in about 10seconds.
I plan to have 80k entries and I don't want users to wait that long.
What I did :
I created a Place class, I do 'observe'(.value) on my firebase ref and on each child I put each element in an attribute of the Place class.
Then the place:Place = Place(attributes) id added to an array:Place until all places have been downloaded.
self.ref.queryOrderedByKey().observe(.value, with: {(snapshot) in
if snapshot.childrenCount > 0 {
for place in snapshot.children.allObjects as! [DataSnapshot] {
When all places are in the array I compare places locations with the user location and sort the array to display them by distance in a tableview.
What I tried:
I also tried to use GeoFire but it is slower.
How the db looks like (80k elements) :
{
"users": {
"DFkjdhfgYG": {
"id":"DFkjdhfgYG"
,"key2":"value"
,"key3":"value"
,"key4":"value"
,"key5":"value"
,"key6":"value"
,"key7":"value"
,"key8":"value"
,"key9":"value"
,"key10":"value"
,"key11":"value"
,"key12":value
,"key13":value
,"key14":"value"
,"key15":"value"
,"key16":"value"
,"key17":"value"
,"key18":"value"
,"key19":"value"
,"key20":"value"
,"key21":value
,"key22":value
,"key23":value
,"key24":value
,"key25":value
,"key26":"value"
,"key27":value
,"key28":value
,"key29":"value"
},
"BVvfdTRZ": {
"id":"BVvfdTRZ"
,"key2":"value"
,"key3":"value"
,"key4":"value"
,"key5":"value"
,"key6":"value"
,"key7":"value"
,"key8":"value"
,"key9":"value"
,"key10":"value"
,"key11":"value"
,"key12":value
,"key13":value
,"key14":"value"
,"key15":"value"
,"key16":"value"
,"key17":"value"
,"key18":"value"
,"key19":"value"
,"key20":"value"
,"key21":value
,"key22":value
,"key23":value
,"key24":value
,"key25":value
,"key26":"value"
,"key27":value
,"key28":value
,"key29":"value"
}
}
}
Now I don't know what to do and I absolutely need to user Firebase.
Can you help me to improve the way I download firebase db elements, or to show me another way to do it, to make the whole process faster ?
Thanks !
You're using a for loop in a function that is being called the same number of times as there are children in your database path, making the for loop completely useless and overkill, which can add extra time to the whole process.
Another thing that you can do is have this be called on a different thread and making it the highest priority over the rest of your code. Here's how to do both of those:
func handleFirebase() {
DispatchQueue.global(qos: .userInteractive).async {
self.ref.queryOrderedByKey().observe(.value, with: { (snapshot) in
guard let value = snapshot.value as? String else { return }
let key = snapshot.key
print("KEY: \(key), VALUE: \(value)")
}, withCancel: nil)
}
}

ObserveSingleEvent returns old data

I want to fetch the required app version number when the app starts. But I can't get the right key.
I have this code to fetch. I use observe single event because I use this method to check the required app version number. This method is only fired when the app starts to do the check.
func getVersion(completionHandler: #escaping (Result<Any?>) -> ()) {
let ref: DatabaseReference! = Database.database().reference().child("version").child("IOS")
ref?.observeSingleEvent(of: .value , with: { snapshot in
if snapshot.exists() {
let recent = snapshot.value as! NSDictionary
print(recent)
}
})
}
But it is returning old results? I have isPersistenceEnabled enabled at my Appdelegate.
This is the database structure:
I get no results when I use Database.database().reference().child("version").child("IOS").
snapshot.exists is false when I use that.
What I previously had was:
- version
|
IOS - 1.0
And i get result when I use Database.database().reference().child("version"), namely {iOS => 1.0}. I don't get it because it was my old structure.
The Firebase Realtime Database synchronizes and stores a local copy of the data for active listeners. In addition, you can keep specific locations in sync.
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)
The Firebase Realtime Database client automatically downloads the data at these locations and keeps it in sync even if the reference has no active listeners. You can turn synchronization back off with the following line of code.
scoresRef.keepSynced(false)
Haven't really tried it but it should work.
The observeSingleEvent method is used for data that doesn't really change, and as such it will fetch from the local cache if you have persistence enabled.
This Android solution (https://stackoverflow.com/a/40477280/883413) provides a workaround by using an alternate method, runTransactonBlock.
Transactions give an opportunity to edit any data before they are saved. Here, we can simply accept the data as correct as we are only interested in reading the latest values.
let ref: DatabaseReference! = Database.database().reference().child("version").child("IOS")
ref.runTransactionBlock({ (data) -> TransactionResult in
return TransactionResult.success(withValue: data)
}, andCompletionBlock: { (error, success, snapshot) in
if let snapshot = snapshot, snapshot.exists() {
if let recent = snapshot.value as? NSDictionary {
print(recent)
}
}
})

Firebase Observe method - sporadic downtime?

I am having random downtimes of connection to firebase using the observeSingleEvent and observe methods in Swift for iOS.
I use multiple ways of connecting to my Firebase database using their REST API.
I use sometimes the method of going through the full URL, like for example,
https://example.firebaseio.com/problems.json.
This always returns data correctly.
When I for example use, observeSingleEvent, that sometimes no code in this gets ran, at all! And that is consistent across view controllers.
The database is still up as using the URL method 100% works, but sometimes using observeSingleEvent it does work, perfectly! But without changing any code, sometimes these events just stop working. I try rebuilding, logging in and out, combination of both, and ive even come to the conclusion that if I leave it for a while, it works again.
Does anyone have any logical reason why the sporadic downtimes occur, and what I can do to fix it? As the code works and i dont change it, but then it stops, for a while across the whole app.
Thanks for your help. Below is an example of code that sometimes runs, and sometimes doesnt.
func getComments() -> Int{
print("getting comments")
let ref = FIRDatabase.database().reference(withPath: "comments")
let query = ref.queryOrdered(byChild: "problem_id").queryEqual(toValue: self.id)
print("Starting observing");
query.observeSingleEvent(of: .value, with: { (snapshot) in
print("Got snapshot");
print(snapshot.childrenCount)
self.commentCount = Int(snapshot.childrenCount)
})
print("returning the comment count");
return commentCount
}
I am fairly new to Firebase, but from my limited experience of observing references, I've learned that observing things takes time to complete, so it is not guaranteed that the code in the closure is executed before what comes next in code. What I would suggest trying (and I'm not positive that it will work) is to modify your code so that it takes a completion handler, guaranteeing that the code in the observing block is completed before getting your result.
So your new code would be
func getComments(completion: #escaping (Int) -> Void) {
print("getting comments")
let ref = FIRDatabase.database().reference(withPath: "comments")
let query = ref.queryOrdered(byChild: "problem_id").queryEqual(toValue: self.id)
print("Starting observing");
query.observeSingleEvent(of: .value, with: { (snapshot) in
print("Got snapshot");
print(snapshot.childrenCount)
print("returning the comment count")
let commentCount = Int(snapshot.childrenCount)
self.commentCount = commentCount
completion(commentCount)
})
}
Then, when you want to use the code, you call it like
getComments(completion: { commentCount in
print(commentCount)
//Do other stuff with comment count
})
It's kinda ugly, but that's the solution that I came up with when I first had similar problems.

Receive limited query with custom start point

I'm building a basic chat app with swift for iOS with firebase realtime database.
The Messages are observed with a limit for the least 10.
Now, I want to implement the functionality of loading earlier send messages. Currently I'm trying to achieve this by using this function:
let query = threadRef.child("messages").queryOrderedByKey().queryStarting(atValue: "2").queryLimited(toLast: 2)
Which returns this query:
(/vYhNJ3nNQlSEEXWaJAtPLhikIZi1/messages {
i = ".key";
l = 2;
sp = 2;
vf = r;
})
And this should give me the data:
query.observeSingleEvent(of: .value, with: { (snap) in
But it just limits the query and not set the start point to a specific position.
Here is the firebase database structure:
messages
-Kgzb3_b26CnkTDglNd8
date:
senderId:
senderName:
text:
-Kgzb4Qip6_jQdKRWFey
-Kgzb4ha0KZkLZeBIaxW
-Kgzb577KlNKOHxsQo9W
-Kgzb5cqIVMhRmU019Jf
Anyone have an idea on how to implement a feature like that?
Okay I finally found a way to do what I wanted.
First of all I misunderstood the way to access data from Firebase.
This is now how I get the query:
let indexValue = messages.first?.fireBaseKey
let query = messageRef.queryOrderedByKey().queryEnding(atValue:indexValue).queryLimited(toLast: 3)
1) get the FireBase key I previously saved to my custom chat messages
2) construct the query:
order it by key
set the ending to oldest message
limit the array to query to desired length
Then to actually get the query I used:
query.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children.dropLast().reversed() {
let fireSnap = (child as! FIRDataSnapshot)
//do stuff with data
}
})
1) get the query as a single event
2) iterate over children and I needed to dropLast() to make sure I don't have any duplicated messages and reverse it to get the correct order.
3) cast the current child as a FIRDataSnapshot to access the data
Since I couldn't find a simple example for this so I thought I leave my solution here incase other people running into the same problem.

ObserveSingleEvent does not work good

My realtime database looks like this:
It contains only 1 child as you can see. I had 4 more children in RunningGames a few minutes before. I deleted them in the browser. When now calling this:
private lazy var runningGamesRef: FIRDatabaseReference = FIRDatabase.database().reference().child("RunningGames")
self.runningGamesRef.observeSingleEvent(of: .value, with: { (snapshot) -> Void in
for gameSnap in snapshot.children {
let id = (gameSnap as! FIRDataSnapshot).key
print(id)
}
})
It still prints those games I deleted in the browser. Calling runningGameRef!.removeValues() in my app does deletes it in the browser and on the iPhone (the print(id) is fixed). I have this error on multiple observeSingleEvent functions on different children, not only children of RunningGames. What would cause this annoying error?
Some children in RunningGames also have children, but they do auto remove themselves in the app. However, these values are also still visible when calling observeSingleEvent.
It's probably your local cache that's still holding the outdated info. This often happens when you're manipulating data from multiple sources.
I would try using observe instead of observeSingleEvent. I know it's a bit odd (and not really what you want if you only want to load the data once) but that should keep your info up to date.
Maybe by doing this you could fetch the info just once.
var handle: UInt = 0
handle = ref.observe(.value, with: { snapshot in
for gameSnap in snapshot.children {
let id = (gameSnap as! FIRDataSnapshot).key
print(id)
}
ref.removeObserver(withHandle: handle)
})
Source of the code (Frank van Puffelen)

Resources