Loading data from firestore while offline works as expected but a call to save never returns and there seems to be no timeout either.
This is a example save that works online but not offline:
func save() {
guard let uid = user?.uid else {
return
}
let db = Firestore.firestore()
var ref: DocumentReference? = nil
ref = db.collection("users").document(uid).collection("properties").addDocument(data: ["name": "test"]) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document added with ID: \(ref!.documentID)")
}
}
}
Is there any known workaround?
UPDATE: Firebase support have confirmed it's a bug and that it "is now being worked on by our engineers". They are unable to give a timescale for when it will be fixed.
This is the expected behaviour - you should assume that the write will happen when the device comes back online. In my use cases, I've just continued with the normal flow and used my local data as my source of truth.
It's mentioned here: https://youtu.be/XrltP8bOHT0?t=680
Related
I am struggling to understand why my event listener that I initialize on a document is not being triggered whenever I update the document within the app in a different UIViewController. If I update it manually in Google firebase console, the listener event gets triggered successfully. I am 100% updating the correct document too because I see it get updated when I update it in the app. What I am trying to accomplish is have a running listener on the current user that is logged in and all of their fields so i can just use 1 global singleton variable throughout my app and it will always be up to date with their most current fields (name, last name, profile pic, bio, etc.). One thing I noticed is when i use setData instead of updateData, the listener event gets triggered. For some reason it doesn't with updateData. But i don't want to use setData because it will wipe all the other fields as if it is a new doc. Is there something else I should be doing?
Below is the code that initializes the Listener at the very beginning of the app after the user logs in.
static func InitalizeWhistleListener() {
let currentUser = Auth.auth().currentUser?.uid
let userDocRef = Firestore.firestore().collection("users").document(currentUser!)
WhistleListener.shared.listener = userDocRef.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
print("INSIDE LISTENER")
}
}
Below is the code that update's this same document in a different view controller whenever the user updates their profile pic
func uploadProfilePicture(_ image: UIImage) {
guard let uid = currentUser!.UID else { return }
let filePath = "user/\(uid).jpg"
let storageRef = Storage.storage().reference().child(filePath)
guard let imageData = image.jpegData(compressionQuality: 0.75) else { return }
storageRef.putData(imageData) { metadata, error in
if error == nil && metadata != nil {
self.userProfileDoc!.updateData([
"profilePicURL": filePath
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
}
}
}
}
You can use set data with merge true it doesn't wipe any other property only merge to specific one that you declared as like I am only update the name of the user without wiping the age or address
db.collection("User")
.document(id)
.setData(["name":"Zeeshan"],merge: true)
The answer is pretty obvious (and sad at the same time). I was constantly updating the filepath to be the user's UID therefore, it would always be the same and the snapshot wouldn't recognize a difference in the update. It had been some time since I had looked at this code so i forgot this is what it was doing. I was looking past this and simply thinking an update (no matter if it was different from the last or not) would trigger an event. That is not the case! So what I did was append an additional UUID to the user's UID so that it changed.
I am working on an iOS app using Firebase as backend. I am encountering a problem where a listener on a sub collection is behaving unexpectedly. Let me explain my data models first:
I have a top-level collection called "families". Within this collection, I have a sub-collection called "chores". It looks something like this:
Within my iOS app, I am adding a listener to this "chores" sub collection like this:
func readChoreCollection(_ familyId: String) {
if familyChoresListener == nil {
let choreCollection = database.collection("families").document(familyId).collection("chores")
familyChoresListener = choreCollection.order(by: "created")
.addSnapshotListener(includeMetadataChanges: false) { [weak self] querySnapshot, error in
print("\(#fileID) \(#function): \(choreCollection.path)")
guard let querySnapshot = querySnapshot else {
print("\(#fileID) \(#function): Error fetching documents: \(error!)")
return
}
let chores: [Chore] = querySnapshot.documents
.compactMap { document in
do {
return try document.data(as: Chore.self)
} catch {
print("\(#fileID) \(#function): error")
return nil
}
}
if chores.isEmpty {
print("\(#fileID) \(#function): received empty list, publishing nil...")
self?.familyChoresPublisher.send(nil)
} else {
print("\(#fileID) \(#function): received chores data, publishing ... \(querySnapshot.metadata.hasPendingWrites)")
self?.familyChoresPublisher.send(chores)
}
}
}
}
According to the Firestore doc:
The snapshot handler will receive a new query snapshot every time the query results change (that is, when a document is added, removed, or modified
So, when I add a new document to the "chores" sub-collection, the listener did trigger, that is expected. However, it is triggered twice, one from local change, and one from remote change. As shown in the log below:
ChoreReward/ChoreRepository.swift readChoreCollection(_:): received chores data, publishing ... true
ChoreReward/ChoreService.swift addSubscription(): received and cached a non-nil chore list
ChoreReward/ChoreRepository.swift readChoreCollection(_:): families/tgO0B4bjq8uwAzmBaOtL/chores
ChoreReward/ChoreRepository.swift readChoreCollection(_:): received chores data, publishing ... false
ChoreReward/ChoreService.swift addSubscription(): received and cached a non-nil chore list
You can see that the listener is called twice, one with hasPendingWrites = true and one with hasPendingWrites = false. So the documentation did mentioned that the local changes will fire-off the callback to listener first before sending data back to Firestore. So this behavior is kinda expected??? On my other listeners (document listeners) within the app, they are only getting called once by the remote changes, not twice. Maybe there is a different in behavior of document vs. collection/query listener? Can anybody verify this difference?
In my app, I have a problem with the memory.
If I write ref.getDocuments() everything works fine. The memory stay at 40mo. However, if I add .whereField("movementID", isEqualTo: PRID) the memory go from 40mo to more than 1Go ...
ref.whereField("movementID", isEqualTo: PRID).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if querySnapshot!.isEmpty {
print("EMPTY")
}
for document in querySnapshot!.documents {
let dict = document.data()
}
}
}
I call this function multiple times, so if the function is called 15-20 times the memory goes to 1Go, but if it's more, the app crash.
Do you have an idea about why there is this memory issue, and how I could correct the problem?
I tried several times with various ways to assign the collected values of documents from firestore into an array. Unfortunately, I could't find a way to solve this issue. I attached the code that I recently tried to implement. It includes before Firestore closure a print statement which print the whole fetched values successfully. However, after the closure and I tried to print the same array and the result is an empty array.
I tried to implement this code
var hotelCities: [String] = []
func getCities() {
db.collection("Hotels").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
var found = false
let documentDetails = document.data() as NSDictionary
let location = documentDetails["Location"] as! NSDictionary
let city = location["city"]!
if (self.hotelCities.count == 0) {
self.hotelCities.append(String(describing: city))
}
else{
for item in self.hotelCities {
if item == String(describing: city){
found = true
}
}
if (found == false){
self.hotelCities.append(String(describing: city))
}
}
}
}
print(self.hotelCities)
}
print(self.hotelCities)
}
That's actually the expected result, since data is loaded from Firestore asynchronously.
Once you call getDocuments(), the Firestore client goes of and connects to the server to read those documents. Since that may take quite some time, it allows your application to continue running in the meantime. Then when the documents are available, it calls your closure. But that means the documents are only available after the closure has been called.
It's easiest to understand this flow, by placing a few print statements:
print("Before starting to get documents");
db.collection("Hotels").getDocuments() { (querySnapshot, err) in
print("Got documents");
}
print("After starting to get documents");
When you run this code, it will print:
Before starting to get documents
After starting to get documents
Got documents
Now when you first saw this code, that is probably not the output your expected. But it completely explains why the print(self.hotelCities) you have after the closure doesn't print anything: the data hasn't been loaded yet.
The quick solution is to make sure that all code that needs the documents is inside of the close that is called when the documents are loaded. Just like your top print(self.hotelCities) statement already is.
An alternative is to define your own closure as shown in this answer: https://stackoverflow.com/a/38364861
I am getting an extra argument 'data' in call at the "addDocument(data: [".It was working fine all along,it was fine yesterday.I woke up today and run it and get this error.I am totally confused.
var db = Firestore.firestore()
func sendDataToDatabase(message: String){
let senderIDNumber = Auth.auth().currentUser?.uid
let timeStampString = String(Int(Date().timeIntervalSince1970))
db.collection("chats").addDocument(data: [
"message" : messageText.text!, "senderID" : senderIDNumber!, "receiverID" : receiverIDNumber!, "timestamp" : timeStampString!, "conversationsCounter" : conversationsCounterInt!
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
}
The answer is there is no problem with my code! It's firebase/firestore that is causing the problem.
I have been talking ,waiting for them to clean up the database for days but no one is solving the problem.
A simple debugging found that there the old data that has been deleted still remains in the firebase system.I emailed them but no one is solving my problem.