Reputation: 91
I'm trying to figure out how to delete all saved User's data, and once that data is deleted, Signout the user and delete their account when deleteUserAccount()
is called.
Right now, when the User presses the "Delete Account" button, their account gets deleted, but the data still remains on firebase because the completion handler gets called before the deleteAllUserTicketData()
function has enough time to execute the query and delete the appropriate documents.
How can I wait for the queries to execute and all the documents to get deleted before executing the completion handler in order to sign the user out and delete their account?
func deleteAllUserTicketData(completion: @escaping () -> Void) {
let group = DispatchGroup()
group.enter()
self.rootWsrCollection?.whereField("uid", isEqualTo: userIdRef).getDocuments { (querySnapshot, err) in
guard let snapshot = querySnapshot else { return }
for wsr in snapshot.documents{
print("Deleting WSR: \(wsr)")
wsr.reference.delete()
}
group.leave()
}
group.enter()
self.rootExerciseCollection?.whereField("uid", isEqualTo: userIdRef).getDocuments { (querySnapshot, err) in
guard let snapshot = querySnapshot else { return }
for exercise in snapshot.documents {
print("Deleting Exercise: \(exercise)")
exercise.reference.delete()
}
group.leave()
}
group.enter()
self.rootWorkoutsCollection?.whereField("uid", isEqualTo: userIdRef).getDocuments { (querySnapshot, err) in
guard let snapshot = querySnapshot else { return }
for workout in snapshot.documents {
print("Deleting Workout: \(workout)")
workout.reference.delete()
}
group.leave()
}
group.notify(queue: DispatchQueue.global()) { [weak self] in
self?.workoutsCollection.daysCollection.removeAll()
print("Calling Notify Method")
completion()
}
}
//This method will only delete the user from the Firestore, but will not delete the User Auth credentials.
func deleteUserFromFirestore(completion: @escaping () -> Void) {
self.rootUserCollection?.whereField("uid", isEqualTo: userIdRef).getDocuments { (querySnapshot, err) in
guard let snapshot = querySnapshot else { return }
for user in snapshot.documents {
print("Deleting User: \(user)")
user.reference.delete()
}
completion()
}
}
func deleteUserAccount() {
deleteAllUserTicketData {
print("First Completion is called")
self.deleteUserFromFirestore {
print("Second Completion is called")
Auth.auth().currentUser?.delete()
}
}
}
}
Upvotes: 0
Views: 377
Reputation: 542
You can try DispatchGroup
to wait for all async then trigger when they all finish. If you enter()
5 times, you also need to leave()
5 times. You need to balance the leave()
with enter()
.
If you call leave()
more than enter()
, it will crash because of unbalanced call.
And, please do not use addSnapShotListener if you don't need to make a real-time observer. Use this instead https://firebase.google.com/docs/firestore/query-data/get-data#get_a_document
Here is your updated logic [1st Update]
func deleteAllUserTicketData(completion: @escaping () -> Void) {
let dispatchGroup = DispatchGroup()
// Inner function
func deleteWithWaiting(snapShot: QuerySnapshot?) {
guard let snapshot = querySnapshot else {
dispatchGroup.leave() // leave 1 after fetching documents
return
}
for document in snapshot.documents {
dispatchGroup.enter() // enter 2 before async delete
document.delete() { err in
if let err = err {
print("Error removing document: \(err)")
} else {
print("Document successfully removed!")
}
dispatchGroup.leave() // leave 2 after async delete
}
}
dispatchGroup.leave() // leave 1 after fetching documents
}
// PLEASE use the .getDocuments() { (querySnapshot, err) in
// instead of .addSnapshotListener {(querySnapshot, err) in
// You just need to fetch data once, not making a new event listener, though it only triggers once.
dispatchGroup.enter() // enter 1 before fetching documents
self.rootWsrCollection?.whereField("uid", isEqualTo: userIdRef).getDocuments() { (querySnapshot, err) in
deleteWithWaiting(snapShot: querySnapshot)
}
dispatchGroup.enter() // enter 1 before fetching documents
self.rootExerciseCollection?.whereField("uid", isEqualTo: userIdRef).getDocuments() { (querySnapshot, err) in
deleteWithWaiting(snapShot: querySnapshot)
}
dispatchGroup.enter() // enter 1 before fetching documents
self.rootWorkoutsCollection?.whereField("uid", isEqualTo: userIdRef).getDocuments() { (querySnapshot, err) in
deleteWithWaiting(snapShot: querySnapshot)
}
// When no waiting item remains (all entered and left), this block will be triggered on queue, in this case, is it Global queue (like background thread
// So if you need to update UI from this, you need to switch to Main Queue or DispatchQueue.main.async { }
dispatchGroup.notify(queue: DispatchQueue.global()) { [weak self] in
// refer to self with a weak reference to avoid retain cycle, prevent memory leak
self?.workoutsCollection.daysCollection.removeAll()
completion()
}
}
func deleteUserFromFirestore(completion: @escaping () -> Void) {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter() // enter 1 before fetching documents
self.rootUserCollection?.whereField("uid", isEqualTo: userIdRef).getDocuments() { (querySnapshot, err) in
guard let snapshot = querySnapshot else {
dispatchGroup.leave() // leave 1 after fetching documents
return
}
for document in snapshot.documents {
dispatchGroup.enter() // enter 2 before async delete
document.delete() { err in
if let err = err {
print("Error removing document: \(err)")
} else {
print("Document successfully removed!")
}
dispatchGroup.leave() // leave 2 after async delete
}
}
dispatchGroup.leave() // leave 1 after fetching documents
}
dispatchGroup.notify(queue: DispatchQueue.global()) {
completion() // now we finished the deleting
}
}
func deleteUserAccount() {
// Swich to Background Thread to perform big-calculation or long-waiting task
DispatchQueue.global().async { [weak self] in
self?.deleteAllUserTicketData {
self?.deleteUserFromFirestore {
Auth.auth().currentUser?.delete()
DispatchQueue.main.async { [weak self] in
// Update your new UI state here
self?.doSomething()
}
}
}
}
}
Upvotes: 3