So, I have a program that, when it opens, looks for a specific document name in a specific collection (both specified) and, when it is found, copies the document name and starts a listener. If it doesn't find the document name after 5 x 5 second intervals, the app stops. For some reason, when I run the code, after it does the first check I get about a thousand writes of this error:
[Firebase/Firestore][I-FST000001] WriteStream (7ffcbec0eac8) Stream error: 'Not found: No document to update:
Here's the code I'm using to call firestore:
let capturedCode: String? = "party"
.onAppear(perform: {
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { timer in
print("running code check sequence")
if let code = capturedCode {
calcCloud.checkSessionCode(code)
if env.doesCodeExist {
print("code found! applying to environment!")
env.currentSessionCode = code
calcCloud.watchCloudDataAndUpdate()
allClear(env: env)
timer.invalidate()
}
else if timerCycles < 5 {
timerCycles += 1
print("code not found, this is cycle \(timerCycles) of 5")
} else {
print("could not find document on firebase, now committing suicide")
let x = ""
let _ = Int(x)!
}
}
}
})
here is the code I'm using to check firebase:
func checkSessionCode(_ code: String) {
print("checkSessionCode running")
let docRef = self.env.db.collection(K.sessions).document(code)
docRef.getDocument { (document, error) in
if document!.exists {
print("Document data: \(document!.data())")
self.env.doesCodeExist = true
} else {
print("Document does not exist")
self.env.doesCodeExist = false
}
}
}
and here is the code that should be executed if the code is found and applied:
func watchCloudDataAndUpdate() {
env.db.collection(K.sessions).document(env.currentSessionCode!).addSnapshotListener { (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
}
Where did I go wrong, and what is this error all about...thanks in advance :)
EDIT: For clarity, it seems that the errors begin once the onAppear finishes executing...
This is why I need to stop coding after 1am...on my simulator, I deleted my app and relaunched and everything started working again...sometimes the simplest answers are the right ones...
Related
Still learning some swift and managed to advance and retrieving data from a firestore database. I have a Data Controller whose task is to offload all the data retrieving from firestore. It does the calls and gets data, but when returning the info from the first function I have implemented on it, it's empty.
Here's an example of the funcion:
func fetchUnidades(for foo: MyFirstEnum, and bar: MySecondEnum ) -> [MyClassType]{
let db = Firestore.firestore()
let colPath = "my/firebase/path"
let results = [MyClassType]()
let collection = db.collection(colPath)
collection.whereField("myField", isEqualTo: foo.rawValue).getDocuments() { querySnapshot, err in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
do {
print("\(document.documentID) => \(document.data())") // 1st print
let newMyClass = try document.data(as: MyClassType.self)
results.append(newMyClass)
print("here") // 2nd print - debug breakpoint
}catch (let error) {
print("\(error)")
}
}
}
}
print("DC - Recovered \(results.count) results")
return results
}
Assume MyFirstEnum, MySecondEnum and MyClassType are correct, because the database retrieves info. On the 1st print line, there's output for data retrieved, and on the 2nd print - debug breakpoint line, if I do a po results, it has one value, which is the one retrieved as you can see here:
unidades being the name on my code of results on this example.
But right after continuing with the execution, unidades, aka results is empty, the line:
print("DC - Recovered \(results.count) results")
prints DC - Recovered 0 results and the return also returns an empty array with zero values on it.
Any idea about why this might be happening? And how to solve the issue? Obviously the goal is to return the info...
That's because the result comes asynchronously. Your fetchUnidades returns results array before it's populated.
You need to add a completion closure in this case. Instead of returning results you call that completion closure and pass the results as its argument.
func fetchUnidades(for foo: MyFirstEnum, and bar: MySecondEnum, completion: (results: [MyClassType]?, error: Error?) -> Void) {
let db = Firestore.firestore()
let colPath = "my/firebase/path"
let collection = db.collection(colPath)
collection.whereField("myField", isEqualTo: foo.rawValue).getDocuments() { querySnapshot, err in
if let err = err {
print("Error getting documents: \(err)")
completion(nil, err)
} else {
let results = [MyClassType]()
for document in querySnapshot!.documents {
do {
print("\(document.documentID) => \(document.data())") // 1st print
let newMyClass = try document.data(as: MyClassType.self)
results.append(newMyClass)
print("here") // 2nd print - debug breakpoint
}catch (let error) {
print("\(error)")
}
}
completion(results, nil)
}
}
All that I'm trying to do is to check whether the value for a 'key' exists in a Firestore collection as a document and return a Bool.
But I can't seem to return anything within the getDocument,
so I thought that I should then keep a results var and update the results var but the changes I make to results don't stick and it stays default false.
How do I simplify this whole mess?
func checkIfValid(db: Firestore, key: String) -> Bool {
let resolve = db.collection("keys").document(key)
var results = false
resolve.getDocument{ (document, error) in
if let document = document, document.exists {
var results = true
} else { results = false }
}
print(results)
return results
}
Reading the firebase docs, they have a small warning below the sample code.
Note: If there is no document at the location referenced by docRef,
the resulting document will be empty and calling exists on it will
return false.
However, you need to add a completion handler to the function given you're working with network requests. Swift will return the result variable given you specified it; ignoring any response from the getDocuments handler.
I changed the function to fix your mess.
func checkIfValid(db: Firestore, key: String, completion: #escaping(Bool) -> ()) {
let docRef = db.collection("user").document(key)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
print("document exists.")
completion(true)
} else {
print("document does not exists.")
completion(false)
}
}
}
And to get the response, just use this.
checkIfValid(db: db, key: "", completion: {success in
print(success)
})
I created a basic Google Places app that lets users check-in to a location. When a user tries to check in, I loop through the list of likelihood places to verify that the user is actually at the location in the app. However, when I try to escape the loop after confirming the location is correct, my function still ends up going to my "else" situation (an error message that asks the user to please check in to the correct location).
The following function gets called in viewWillAppear:
func checkIn(handleComplete:#escaping (()->())){
guard let currentUserID = User.current?.key else {return}
// Specify the place data types to return.
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue))!
placesClient.findPlaceLikelihoodsFromCurrentLocation(withPlaceFields: fields, callback: {
(placeLikelihoodList: Array<GMSPlaceLikelihood>?, error: Error?) in
if let error = error {
print("An error occurred: \(error.localizedDescription)")
return
}
if let placeLikelihoodList = placeLikelihoodList {
for likelihood in placeLikelihoodList {
let place = likelihood.place
if likelihood.likelihood >= 0.75 && place.placeID! == self.hangoutID {
let place = likelihood.place
print("Current Place name \(String(describing: place.name!)) at likelihood \(likelihood.likelihood)")
print("Current PlaceID \(String(describing: place.placeID!))")
self.delta = 0.0
// update checkin
DispatchQueue.main.async {
let hangoutRef = self.db.collection("users").document(currentUserID).collection("hangout").document(self.hangoutID).updateData([
"lastCheckin": Date()
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
}
}
handleComplete()
}
}
self.presentDismissableAlert(title: "", message: "Please check in to the hangout to join this chat", button: "OK", dismissed: { (UIAlertAction) in
self.performSegue(withIdentifier: "unwindSegueToChats", sender: self)
})
}
})
}
If the correct conditions are met, the code will land on the handleComplete() line but then it will still execute the dismissableAlert underneath and segue the user out of the room. How can I fix the flow so that the app will cycle through the list of likely Places and stop the function on handleComplete if the correct condition is met, or else then proceed to the error message if the correct conditions are not met (user is not at the correct Place)?
Thanks
I am trying to query my firestore database and count the number of times a field is set to pre-defined enum. However, when I run it or step through it with a breakpoint, the closure never returns and my dispatch.wait() hangs forever. I am not sure why the query isn't working, as the collection exists and I have test data in there for this query. I am also able to read and write to the evaluations collection so I don't think it is a permissions issue.
I would expect at least to get an error if the query failed but it just skips over it and hangs on the wait until I stop the run.
let user = self.user
let evalRef = self.db.evaluations(forUser: user.userID)
let dispatchGroup = DispatchGroup()
for keys in self.stat.enumCases.keys {
dispatchGroup.enter()
evalRef.whereField(self.stat.queryName, isEqualTo: keys).getDocuments { (querySnapshot, err) in
if let err = err {
print(err.localizedDescription)
dispatchGroup.leave()
return
}
guard let querySnapshot = querySnapshot else {
//error
dispatchGroup.leave()
return
}
guard let enumCaseName = self.stat.enumCases[keys] else {
//error
dispatchGroup.leave()
return
}
if querySnapshot.count > 0 {
self.stat.primaryStatStruct[keys] = primaryStatForEachCase(enumCaseTitle: enumCaseName, enumCaseValue: Double(querySnapshot.count))
dispatchGroup.leave()
} else {
self.stat.primaryStatStruct[keys] = primaryStatForEachCase(enumCaseTitle: enumCaseName, enumCaseValue: 0.0)
dispatchGroup.leave()
}
}
}
dispatchGroup.wait()
Here are other snippets to give a better picture:
/// Returns a reference to the user evaluation collection.
func evaluations(forUser userID: String) -> CollectionReference {
return self.collection("users/\(userID)/evaluations")
}
print of keys and self.stat.queryName
evaluations collection with documents
document data matching keys and queryName
Any help would be greatly appreciated.
I am trying to read the value of a Firestore document. I have tried doing it two different ways, but each fails.
In the first one, an error is thrown on the return line: Unexpected non-void return value in void function. I found out why this happened, and so, I implemented the second way.
import UIKit
import Firestore
func readAvailableLists(forUser user: String) -> [String] {
let db = Firestore.firestore()
db.collection("userslist").document(user).getDocument { (document, err) in
if let document = document, document.exists {
return UserInformationDocument(dictionary: document.data()!)?.lists!
} else {
print("Document does not exist")
}
}
}
In the second method, I assign the UserInformationDocument(dictionary: document.data()!)?.lists! to a variable and return that variable at the end of the function (see code below). However, when I do this, it the function returns an empty array. What surprises me is that the print return the correct value, but after long after the function has executed the return statement. Is it because it is an async demand? And if so, how should I fix this?
import UIKit
import Firestore
func readAvailableLists(forUser user: String) -> [String] {
let db = Firestore.firestore()
var firestoreUserDocument: [String] = []
db.collection("userslist").document(user).getDocument { (document, err) in
if let document = document, document.exists {
firestoreUserDocument = (UserInformationDocument(dictionary: document.data()!)?.lists!)!
print((UserInformationDocument(dictionary: document.data()!)?.lists!)!)
} else {
print("Document does not exist")
}
}
return firestoreUserDocument
}
The Firebase call is an asynchronous function. It takes extra time to execute because it's talking to a server (as you've noted) - as a result, the completion block (the block that defines document and err in your example) happens at a different time, outside of the rest of the body of the function. This means you can't return a value from inside it, but you can pass another closure through to it, to execute later. This is called a completion block.
func readAvailableLists(forUser user: String, completion: #escaping ([String]?, Error?) -> Void) -> [String] {
let db = Firestore.firestore()
db.collection("userslist").document(user).getDocument { (document, err) in
if let document = document, document.exists {
// We got a document from Firebase. It'd be better to
// handle the initialization gracefully and report an Error
// instead of force unwrapping with !
let strings = (UserInformationDocument(dictionary: document.data()!)?.lists!)!
completion(strings, nil)
} else if let error = error {
// Firebase error ie no internet
completion(nil, error)
}
else {
// No error but no document found either
completion(nil, nil)
}
}
}
You could then call this function elsewhere in your code as so:
readAvailableLists(forUser: "MyUser", completion: { strings, error in
if let strings = strings {
// do stuff with your strings
}
else if let error = error {
// you got an error
}
})