This question already has answers here:
Retrieve data from array in another function
(1 answer)
Is Firebase getDocument run line by line?
(1 answer)
Storing asynchronous Cloud Firestore query results in Swift
(1 answer)
SwiftUI - Wait until Firestore getDocuments() is finished before moving on
(1 answer)
Closed last year.
I have been searching a while and I could not find a solution -> me opening a new thread.
I am making this register page which works with a token send to the users email, it has to be checked against the values in the database, however (in code below) it prints "false" before "hi", I have been trying to get it async, but it doesn't want to work. Can someone help me out? Thanks!
PS: The selection variable makes my app go to the next page in registration, which, because return is false, won't go to.
public static func tokenVerify(_ token: String) -> Bool{
// Get database
let db = Firestore.firestore()
let collection = db.collection("tokens")
let query = collection.whereField("token", isEqualTo: token)
// Make response
var response = false
// Get all documents
query.getDocuments { snapshot, error in
// Check for errors,
if error == nil{
// No error
if let snapshot = snapshot {
if snapshot.isEmpty {
print("No token in database")
} else {
response = true
print("hi")
}
} else {
print("Unknown Error")
}
} else {
print("Error retrieving documents.")
}
}
print(response)
return response
}
Main View:
DispatchQueue.main.async{
if Registration.tokenVerify(token) {
selection = "third"
}
}
Related
class UsersViewModel : ObservableObject {
#Published var users = [CurrentUser]()
init() {
fetchUserLists()
print(users)
}
func fetchUserLists() {
FirebaseManager.shared.firestore.collection("users")
.getDocuments { documentSnapshot, error in
if let error = error {
print("Error to get user lists")
return
}
//success
documentSnapshot?.documents.forEach({ snapshot in
let user = try? snapshot.data(as: CurrentUser.self)
if user?.uid != FirebaseManager.shared.auth.currentUser?.uid {
self.users.append(user!)
}
})
}
}
}
I'm trying to fetch all of users in my firestore database, but unfortunately my users array is empty. I don't know what my mistake is.
Please check my firestore screen shot, and give me tips!
Thank you!
You're having an issue with asynchronous code. Code is faster than the internet and you have to allow time for data to be retrieved from Firebase.
Additionally, Firebase data is only valid within the closure following the Firebase call. In this case your code is attempting to print an array before it's been filled.
Here's the issue
init() {
fetchUserLists() //<-takes time to complete
print(users) //this is called before fetchUserLists fills the array
}
here's the fetchUserLists function with where the print statement should be
func fetchUserLists() {
FirebaseManager.shared.firestore.collection("users").getDocuments { documentSnapshot, error in
if let error = error {
print("Error to get user lists")
return
}
documentSnapshot?.documents.forEach({ snapshot in
let user = try? snapshot.data(as: CurrentUser.self)
if user?.uid != FirebaseManager.shared.auth.currentUser?.uid {
self.users.append(user!)
}
})
print(self.users) //we are within the closure and the array is now populated
//this is a good spot to, for example, reload a tableview
// or update other UI elements that depend on the array data
}
}
This question already has answers here:
Storing asynchronous Cloud Firestore query results in Swift
(1 answer)
How do I save data from cloud firestore to a variable in swift?
(1 answer)
Closed 1 year ago.
I'm trying to set a variable to a boolean based on this code here:
Firestore.firestore().collection("users").whereField("username", isEqualTo: usernameTextField.text!).getDocuments
{ (querySnapshot, error) in
if let error = error { print(error.localizedDescription) /*ALERT*/ }
else
{
if querySnapshot!.isEmpty
{
print("QuerySnapshot is empty, this is unique")
retVal = true
}
else
{
print("There's a bloody username in here, get a new one")
retVal = false
}
}
}
return retVal
The issue is however, retVal is not changed. I understand what's going on here, it's an asynchronous block of code, however I don't understand how to fix it or work around it to fit my needs. How do I get the value of retVal, (or honestly just return true or false within this block of code) despite it being asynchronous. Is there anyway I can like wait for it to finish before further execution of code?
Since your call is asynchronous, you should use a callback or completion handler in your function, this way the retVal will be returned when the Firestore query finishes.
Something like this:
func userIsUnique(completionHandler: (Bool) -> Void) {
Firestore.firestore().collection("users").whereField("username", isEqualTo: usernameTextField.text!).getDocuments
{ (querySnapshot, error) in
if let error = error { print(error.localizedDescription) /*ALERT*/ }
else
{
if querySnapshot!.isEmpty
{
print("QuerySnapshot is empty, this is unique")
completionHandler(true)
}
else
{
print("There's a bloody username in here, get a new one")
completionHandler(false)
}
}
}
}
So I'm trying to make sure a set of async tasks get executed in a specific order when a user is being deleted.
So what I want to happen is :
Check if user has added guests with their purchase
if user has no guests or any purchases at all, return from the function and continue with deletion process (bullet point 6)
if user has guests for any of their purchases, delete every single guest
once all the guests are deleted, go ahead and delete every purchase they made
once all purchases made are deleted, go ahead and delete the actual user itself out of Firestore
2 seconds after that, I delete the user out of firebase auth just to make sure there are no crashes trying to delete documents with an empty user
then I simply segue to the main menu
So I'm trying to accomplish this with this block of code in my function:
let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { (deletion) in
let semaphore = DispatchSemaphore(value: 0)
self.deleteButton.isHidden = true
self.loadingToDelete.alpha = 1
self.loadingToDelete.startAnimating()
DispatchQueue.global(qos: .background).async {
self.db.collection("student_users/\(user.uid)/events_bought").getDocuments { (querySnapshot, error) in
guard error == nil else {
print("The docs couldn't be retrieved for deletion.")
return
}
guard querySnapshot?.isEmpty == false else {
print("The user being deleted has no events purchased.")
return
}
for document in querySnapshot!.documents {
let docID = document.documentID
self.db.collection("student_users/\(user.uid)/events_bought/\(docID)/guests").getDocuments { (querySnap, error) in
guard querySnap?.isEmpty == false else {
print("The user being deleted has no guests with his purchases.")
return
}
for doc in querySnap!.documents {
let guest = doc.documentID
self.db.document("student_users/\(user.uid)/events_bought/\(docID)/guests/\(guest)").delete { (error) in
guard error == nil else {
print("Error deleting guests while deleting user.")
return
}
print("Guests deleted while deleting user!")
semaphore.signal()
}
semaphore.wait()
}
}
}
}
self.db.collection("student_users/\(user.uid)/events_bought").getDocuments { (querySnapshot, error) in
guard error == nil else {
print("There was an error retrieving docs for user deletion.")
return
}
guard querySnapshot?.isEmpty == false else {
return
}
for document in querySnapshot!.documents {
let docID = document.documentID
self.db.document("student_users/\(user.uid)/events_bought/\(docID)").delete { (err) in
guard err == nil else {
print("There was an error deleting the the purchased events for the user being deleted.")
return
}
print("Purchases have been deleted for deleted user!")
semaphore.signal()
}
semaphore.wait()
}
}
self.db.document("student_users/\(user.uid)").delete(completion: { (error) in
guard error == nil else {
print("There was an error deleting the user document.")
return
}
print("User doc deleted!")
semaphore.signal()
})
semaphore.wait()
}
DispatchQueue.main.asyncAfter(deadline: .now()+1.5) {
user.delete(completion: { (error) in
guard error == nil else {
print("There was an error deleting user from the system.")
return
}
print("User Deleted.")
})
}
self.loadingToDelete.stopAnimating()
self.performSegue(withIdentifier: Constants.Segues.studentUserDeletedAccount, sender: self)
}
I've been trying to play around with DispatchSemaphore() for the last couple of hours and implement it into my code, but it just doesn't do what I expect it to do. I would read up on articles and examples of DispatchSemaphore() online but the scenarios aren't exactly the same as mine in regards to what I specifically want to do.
When this alert action gets triggered on tap, nothing prints, and it just ends up spinning forever, the user isn't deleted out of Firebase Auth and there is still leftover data in the Firestore database like so:
I just basically want to figure out the best way to order these async tasks in the ordered list above and have a clean user deletion with no leftover in the database. Thanks in advance.
You should be handling this with a Firebase Cloud Function which has multiple ways of reacting to client requests and database changes. This does require billing and migrating your code to javascript with node v10 which is fairly straightforward due to the consistent methods of firebase across most languages.
Firebase Function
Two popular methods are simply importing the firebase cloud functions module into your app or calling the request over https, each has its own entry points with pros and cons which are worth reading into.
https://firebase.google.com/docs/functions/callable
https://firebase.google.com/docs/functions/http-events
From there, you would delete the core files that would impact the user immediately, then updating the client on its result before proceeding with your clean-up of residual files.
Firestore Trigger
An alternative that is just as sound and more manageable from potential abuse is invoking a trigger based on a document deletion, you can use this to then trigger other documents to proceed to be removed and cleaned up
You can read about them below, and it can contain fundamentally the same logic in both situations but this option doesn't require the bulky firebase functions module.
https://firebase.google.com/docs/functions/firestore-events#function_triggers
Update: Async
Async methods are simply functions that are flagged as async that allow tasks to operate without blocking structure, this allows multiple tasks to be fired without depending on another. However, to pause your code to wait for something to be done, you can append the await flag
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
const result = await resolveAfter2Seconds();
console.log(result);
// expected output: "resolved"
}
asyncCall();
reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Update: Promises
Promises work the same as async functions and run independently to the parent function and itself can be flagged with await if need be.
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('foo');
}, 300);
});
myPromise
.then(handleResolvedA, handleRejectedA)
.then(handleResolvedB, handleRejectedB)
.then(handleResolvedC, handleRejectedC);
reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
I deleted an entry in the Firestore and also checked it manually to confirm that. However, as long as I do not close the application, I can send a request to fetch the data and I still get the result. This should not be the case.
If you imagine having a shared photo with some textual information and you delete those information, this would mean, other users can still see the textual information (fetched from the Firestore) but not the image anymore (store in Firestorage).
I want to display a message on the UI, something like "The content does not exist anymore".
How I can achieve that? I used the following approach so far but it does not work at the moment:
public func checkIfChallengeObjectExists(completionHandler:#escaping(Bool)->(), challengeId:String) {
CHALLENGE_COLLECTION?.document(challengeId).getDocument(completion: { (querySnapshot, error) in
if (error != nil) {
print(error?.localizedDescription as Any)
}
if (querySnapshot?.documentID == "" || querySnapshot!.metadata.isFromCache) {
completionHandler(false)
}
else {
completionHandler(true)
}
})
}
Any solutions?
Non-existent documents will still return document snapshots, but they will be empty. Therefore, you must check the contents of the snapshot for the document, not the snapshot itself. Also, you should handle errors and the overall flow of the return better.
public func checkIfChallengeObjectExists(completionHandler:#escaping(Bool)->(), challengeId:String) {
CHALLENGE_COLLECTION?.document(challengeId).getDocument(completion: { (querySnapshot, error) in
if let doc = querySnapshot,
doc.exists {
completionHandler(true) // only one possible true condition
} else {
if let error = error {
print(error.localizedDescription)
}
completionHandler(false) // all else false
}
})
}
As a side note, I recommend reordering the parameters of the function to make it easier to read when called (conventionally, the completion handler comes last) and giving the boolean argument a name so it's easier to read when referencing (sometime later or by other developers).
public func verifyChallengeObject(ID: String, _ completion: #escaping (_ exists: Bool) -> Void) {
...
}
verifyChallengeObject(ID: "abc123", { (exists) in
if exists {
...
} else {
...
}
})
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...