So my goal is to have the UI update based on a network call depending if there is an error or not. Currently, my network call succeeds and the completion handler that updates the UI fails to show the correct UI despite the successful network call.
here is my current network API call:
func makeRefundRequest(refundMade: @escaping (_ done: Bool) -> Void) {
getStripePaymentIntentID { (paymentid) in
guard let id = paymentid,
let url = URL(string: "https://us-central1-xxxxx-41f12.cloudfunctions.net/createRefund") else {
refundMade(false)
return
}
let json: [String: Any] = ["payment_intent": id]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: json)
let task = URLSession.shared.dataTask(with: request) { [weak self] (data, response, error) in
if let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let _ = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
refundMade(true)
} else {
if error != nil {
self?.showAlert(title: "Refund Request Error", message: "There was an error making the refund request. Please check your connection and try again.")
}
refundMade(false)
}
}
task.resume()
}
}
This is how it was before I decided to update the UI in a failure case, which made sense, because you wouldn't want to see the UI update as if things have succeeded when they really failed.
So I have the function that processes the refund and updates the UI, I will specifically add the completion handler where I'm having the issue:
let cancelPurchase = UIAlertAction(title: "Cancel Purchase", style: .default) { (purchaseCancel) in
self.viewPurchaseButton.isHidden = true
self.cancelPurchaseButton.isHidden = true
self.refundLoading.alpha = 1
self.refundLoading.startAnimating()
self.getEventDocumentID { (id) in
guard let id = id else { return }
self.makeRefundRequest { (done) in
if done == false {
DispatchQueue.main.async {
self.refundLoading.stopAnimating()
self.refundLoading.alpha = 0
self.viewPurchaseButton.isHidden = false
self.cancelPurchaseButton.isHidden = false
}
return
} else {
let group = DispatchGroup()
self.db.collection("student_users/\(user.uid)/events_bought/\(id)/guests").getDocuments { (querySnapshot, error) in
guard error == nil else { return }
guard querySnapshot?.isEmpty == false else { return }
for guest in querySnapshot!.documents {
let name = guest.documentID
let batch = self.db.batch()
let docRef = self.db.document("student_users/\(user.uid)/events_bought/\(id)/guests/\(name)")
batch.deleteDocument(docRef)
group.enter()
batch.commit { (err) in
guard err == nil else { return }
group.leave()
}
}
}
group.notify(queue: .main) {
DispatchQueue.main.asyncAfter(deadline: .now()+2) {
self.db.document("student_users/\(user.uid)/events_bought/\(id)").delete { (error) in
guard error == nil else { return }
self.refundLoading.stopAnimating()
self.refundLoading.alpha = 0
self.ticketFormButton.isHidden = false
self.cancelPurchaseButton.isHidden = true
self.viewPurchaseButton.isHidden = true
}
}
}
}
}
}
}
So where it says if done == false, I basically want the UI to return to it's previous state so that the user can clearly see the refund was a failure. Now if I was to do that without the DispatchQueue.main.async call, the app would crash and show a thread error with the purple highlighting saying the " self.refundLoading.stopAnimating() can only be called on the main thread".
This was working fine before I implemented the extra async call in the if done == false {} block but nonetheless, I need this block of code. When I currently run it, the network call always succeeds, meaning the completion call would be true, hence it should show the correct UI. Instead, it shows the UI as if the network call failed and nothing gets deleted from Firestore. How can I solve this thread/Firestore issue?
UPDATE -> so I looked up on some more articles and decided to add a [weak self] into the makeRefundRequest call inside my alert action and also decided to remove the first call of refundMade(false) since calling it twice apparently causes an app crash.
func makeRefundRequest(refundMade: @escaping (_ done: Bool) -> Void ) {
getStripePaymentIntentID { (paymentid) in
guard let id = paymentid,
let url = URL(string: "https://us-central1-xxxxx-41f12.cloudfunctions.net/createRefund") else {
return
}
let json: [String: Any] = ["payment_intent": id]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: json)
let task = URLSession.shared.dataTask(with: request) { [weak self] (data, response, error) in
if let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let _ = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
refundMade(true)
} else {
if error != nil {
self?.showAlert(title: "Refund Request Error", message: "There was an error making the refund request. Please check your connection and try again.")
}
refundMade(false)
}
}
task.resume()
}
}
Here is the actual method itself being called in the alert action :
func showFailureUI() {
DispatchQueue.main.async {
self.refundLoading.stopAnimating()
self.refundLoading.alpha = 0
self.viewPurchaseButton.isHidden = false
self.cancelPurchaseButton.isHidden = false
}
}
//Handles the action of cancelling the purchase
@IBAction func cancelPurchasePressed(_ sender: UIButton) {
guard let nameOfEvent = selectedEventName else { return }
guard let user = Auth.auth().currentUser else { return }
let alertForCancel = UIAlertController(title: "Cancel Purchase", message: "Are you sure you want to cancel your purchase of a ticket to \(nameOfEvent)? You will receive full reimbursement of what you paid within 5 - 10 days.", preferredStyle: .alert)
let cancelPurchase = UIAlertAction(title: "Cancel Purchase", style: .default) { (purchaseCancel) in
self.viewPurchaseButton.isHidden = true
self.cancelPurchaseButton.isHidden = true
self.refundLoading.alpha = 1
self.refundLoading.startAnimating()
self.getEventDocumentID { (id) in
guard let id = id else { return }
self.makeRefundRequest { [weak self] (done) in
if done == false {
self?.showFailureUI()
} else {
let group = DispatchGroup()
self?.db.collection("student_users/\(user.uid)/events_bought/\(id)/guests").getDocuments { (querySnapshot, error) in
guard error == nil else { return }
guard querySnapshot?.isEmpty == false else { return }
for guest in querySnapshot!.documents {
let name = guest.documentID
let batch = self?.db.batch()
guard let docRef = self?.db.document("student_users/\(user.uid)/events_bought/\(id)/guests/\(name)") else { return }
batch?.deleteDocument(docRef)
group.enter()
batch?.commit { (err) in
guard err == nil else { return }
group.leave()
}
}
}
group.notify(queue: .main) {
DispatchQueue.main.asyncAfter(deadline: .now()+2) {
self?.db.document("student_users/\(user.uid)/events_bought/\(id)").delete { (error) in
guard error == nil else { return }
self?.refundLoading.stopAnimating()
self?.refundLoading.alpha = 0
self?.ticketFormButton.isHidden = false
self?.cancelPurchaseButton.isHidden = true
self?.viewPurchaseButton.isHidden = true
}
}
}
}
}
}
}
alertForCancel.addAction(UIAlertAction(title: "Cancel", style: .cancel))
alertForCancel.addAction(cancelPurchase)
present(alertForCancel, animated: true, completion: nil)
}
Thinking this would work, when I run my app with some breakpoints and try to print the value of refundMade,
this is the outcome:
I actually searched up that exact error and tried to take what I could from the StackOverflow posts but nothing helped. This is like my last major issue for now so if anybody can help out that would be appreciated, thanks.

This is generated with the application PAW
follow this tutorial for how to use it : https://www.youtube.com/watch?v=44APgBnapag
and later
MyRequestController().sendRequest....