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)
Related
I have an app that uses a snapshot listener to listen to data in a particular document. However, when a field in the document is updated, the data is read 7-10x over. Never read once, and never read the number of fields that are in my document, it always seems to be an arbitrary number. Also, when the read data prints out, it seems like every printout is the same except for a couple of fields that I'm not setting (like an array prints out "<__NSArrayM 0x282d9f240>" but the number changes on each print). As a result, minimal usage of my app is causing 5-10k reads. I'm trying to reduce the number of reads and I don't know exactly how, but the app has to read as data is updated, but my two questions are:
when I print the data from the listener, does each data print out signify a separate read operation? and
is there any way for the listener to be alerted of the update but wait to actually perform the read until the data is updated, then perform one read instead of multiple reads every time any field is updated? Or another strategy to reduce reads when multiple writes occur?
Not sure if this is helpful, but here is the code I'm using to perform the read...its pretty much the standard code from the firestore sdk:
env.db.collection(env.currentSessionCode!).document(K.FStore.docName).addSnapshotListener { [self] documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching snapshot: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
self.env.data1 = data[K.FStore.data1] as? String ?? "????"
self.env.data2 = data[K.FStore.data2] as? String ?? "????"
self.env.data3 = data[K.FStore.data3] as? [String] ?? ["????"]
self.env.data4 = data[K.FStore.data4] as? [String] ?? ["????"]
self.env.data5 = data[K.FStore.data5] as? Double ?? 0
self.env.data6 = data[K.FStore.data6] as? Double ?? 0
self.env.data7 = data[K.FStore.data7] as! Bool
self.env.data8 = data[K.FStore.data8] as! Bool
print("Current data: \(data)")
Update - For clarification, the way I have been updating my data to firebase is with a environment object, and using "didSet" when the new data is changed/updated in the environment to update it on firebase...I think this might be the root of the problem, as the function called on didSet runs 4-5 times each time it is called...
relevant code:
#Published var data1: String {
didSet {
postValuesToFB(fb: K.FStore.data1, string: data1)
}
}
func postValuesToFB(fb: String, string: String) {
guard let code = currentSessionCode else {
fatalError("Error - Connection Check - no value for current session code in Global Env")
}
let docRef = db.collection(code).document(K.FStore.docName)
docRef.getDocument { document, _ in
guard let document = document else {
return
}
if document.exists {
let session = self.db.collection(code).document(K.FStore.docName)
session.updateData([
fb: string,
K.FStore.dateLastAccessed: FieldValue.serverTimestamp(),
])
return
}
}
}
Based on your comments, it sounds as if you've written no code to remove a listener after it's been added. Based on this, it's relatively safe to assume that your code could be adding many listeners over time, and each one is getting called for each change.
You should take a moment to think about the architecture of your app and figure out when is the appropriate time to remove listeners when they're no longer needed. Usually this corresponds with the lifecycle of whatever component is responsible for display of the data from the query. Review the documentation for getting realtime updates, especially the section on detaching a listener. It's up to you to determine the right time to remove your listener, but you definitely don't want to "leak" a listener as you are now.
A common source of unexpected read charges for developers who are new to Firestore is the Firebase console itself. When that console displays Firestore content, you are charged for those read too. To ensure you measure the impact of your code correctly, test it with the Firebase console closed.
when I print the data from the listener, does each data print out signify a separate read operation?
Not really. You get charged for a document read, when the document is read on your behalf on the server. You are not charted for printing the same DocumentSnapshot multiple times.
is there any way for the listener to be alerted of the update but wait to actually perform the read until the data is updated
Nope. To know the document has changed, the server needs to read it. So that requires a charged read operation.
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.
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)
}
}
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)
}
}
})
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.