Firebase Observe method - sporadic downtime? - ios

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.

Related

Swift firebase when retrieving data from the database, how to delay the next code until the retrieval is done?

I am working with a Firebase Real Time database and I am having troubles with the execution of code at specific times. When I ask the database if there is a specific value in it, it executes the code to retrieve the data but then automatically continues unto the next line, not waiting for the retrieval of data. I am using this code to retrieve the data:
self.ref.child("Period \(periodListValue)").child("Students").child("\(studentName)").child("Novel Author").observeSingleEvent(of: .value) { (snapshot) in
self.CurrentAuthorTextField.text = (snapshot.value as! String)
}
The line of code right below this that is not associated with the database, executes right after this code above executes. Meaning I can't use any of the database information of the code right below the one shown above.
I am trying to explain this as best as I.
Any help?
Well, you have to use the completion handler for the following:-
func ifStudentPresent(studentName: String, completionHandler: #escaping ((_ exist : Bool) -> Void))
{
self.ref.child("Period \(periodListValue)").child("Students").child("\ .
(studentName)").child(studentName).observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists(){
completionHandler(true)
}else{
print("Student Don't exist")
completionHandler(false)
}
})
}
The reason you might be going through this problem is related to invalid database security rules. These can easily prevent you from retrieving your desired data and synchronizing your realtime database.
Let's assume you are trying to synchronize the ref database reference. You need to set the correct rules that allow reading from this database reference - something along the lines of ".read" = true".
[Warning] Please be careful with these database security rules. Incorrect rules can lead to drastically undesired behaviors, such as people illegally reading and/or writing from/into your database. A good video on how to set flawless security rules is The key to firebase security - Google I/O 2016
let dispatch = DispatchGroup.init()
dispatch.enter()
self.ref.child("Period \(periodListValue)").child("Students").child("\(studentName)").child("Novel Author").observeSingleEvent(of: .value) { (snapshot)
in
self.CurrentAuthorTextField.text = (snapshot.value as! String)
dispatch.leave()
}
dispatch.notify(queue: .main) {
//write code here, it will execute after database fetch
}

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

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.

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)
}
}
})

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)

iOS Firebase - how to force data reads to happen sequentially?

I'm trying to use multiple snapshots to get info that I need for my code. The problem is that sometimes they don't execute in the order that I need them to. Right now I'm nesting them like this:
_ = ref.child("users").child(self.userKey).child("name").observeSingleEvent(of: FIRDataEventType.value, with: { (snapshot) in
//execute block 1
let argToQuery2 = snapshot.value as! String
_ = ref.child("users").child(argToQuery2).child("job").observeSingleEvent(of: FIRDataEventType.value, with: { (snapshot) in
//execute block2
......
})
})
But as I said I'm finding that block 1 doesn't always execute before block2, and I need the information from block 1 to complete block 2. How can I force these queries to happen sequentially? If I only had two queries I would try using a boolean to see if the first block had finished or not, but in my actual code I have 5+ of these nested.
Thanks!

Resources