Duck
Duck

Reputation: 36013

SwiftUI – Not clear how to handle the alert with asynchronous methods

I have to present an Alert on a view if the user taps on it.

My alert depends on several situations:

  1. Item is purchased. Show the item.
  2. Item have not be purchased. Show an alert telling the user the item have to be purchased. This alert must show two buttons, OK to purchase, Cancel to dismiss.
  3. User taps to purchase the item.
  4. Purchase is successful, show the item.
  5. Purchase fails, show error.

This is how I did it.

  class AlertDialog {
    enum SelectedType {
      case none
      case purchase
      case mustBePurchased
      case purchaseError
    }
    
    var selectedType:SelectedType = .none

  }


struct FilteredListItem: View {

  @State var showAlert: Bool = false
  private var alertDialog:AlertDialog?

 var body: some View {
      Text(item.termLowerCase)
        .font(fontItems)
        .foregroundColor(.white)
        .onTapGesture {
          DispatchQueue.main.async {            
            appStoreWrapper.verifyPurchase(productID: item.package!)
            { // run if purchased
              purchased = true
            } runIfNotPurchased: {
              purchased = false
              alertDialog!.selectedType = .mustBePurchased
              showAlert = true
            } 
          }
        }
    .alert(isPresented: $showAlert) {       
      if alertDialog!.selectedType  == .purchase {
        appStoreWrapper.purchase(productID: item.package!) {
          // run if purchased
          purchased = true
        } runIfPurchaseFailed: { (error) in
          alertDialog!.selectedType = .purchaseError
          appStoreWrapper.purchaseError = error
          showAlert = true
        }
        
      } else if alertDialog!.selectedType  == .purchaseError {
        let primaryButton = Alert.Button.default(Text("OK")) {
          showAlert = false
        }
        return Alert(title: Text(appStoreWrapper.makeString("ERROR")),
                     message: Text(appStoreWrapper.purchaseError),
                     dismissButton: primaryButton)
        
      }
    
      
      let dismissButton = Alert.Button.default(Text(appStoreWrapper.makeString("CANCEL"))) {
        showAlert = false
      }
      
      let primaryButton = Alert.Button.default(Text("OK")) {
        appStoreWrapper.purchase(productID: item.package!) {
          // run if purchased
          purchased = true
        } runIfPurchaseFailed: { (error) in
          appStoreWrapper.purchaseError = error
          alertDialog!.selectedType = .purchaseError
          showAlert = true
          print(erro)
        }
      }
      
      return Alert(title: Text(appStoreWrapper.makeString("ERROR")),
                   message: Text(appStoreWrapper.purchaseError),
                   primaryButton: primaryButton,
                   secondaryButton: dismissButton)
    }

This is my problem: the modifier .alert(isPresented: $showAlert) expects an Alert() to be returned, right? But I have these asynchronous methods

appStoreWrapper.verifyPurchase(productID: item.package!)
        { // run if purchased }, 
        runIfNotPurchased: { }

that cannot return anything to the alert modifier. How do I do that? Is what I am doing right?

Upvotes: 1

Views: 1074

Answers (1)

nicksarno
nicksarno

Reputation: 4245

There's a lot going on in your code and you didn't post the code for appStoreWrapper, but here's some code that should be able to point you in the right direction.

FYI:

  • You can use a Button with an Action instead of using Text with .onTapGesture

  • The code within .Alert should only function to get an Alert. You shouldn't be doing other actions within the .Alert closure.

     struct FilteredListItem: View {
    
         @State var showAlert: Bool = false
         private var alertDialog: AlertDialog?
    
         var body: some View {
             Button(action: {
                 verifyItem()
             }, label: {
                 Text("ITEM NAME")
                     .foregroundColor(.white)
             })
             .accentColor(.primary)
             .alert(isPresented: $showAlert, content: {
                 getAlert()
             })
         }
    
         func verifyItem() {
             // FUNCTION TO VERIFY ITEM HERE
             var success = true //appStoreWrapper.verifyPurchase...
    
             if success {
                 // Handle success
             } else {
                 alertDialog?.selectedType = .mustBePurchased
                 showAlert.toggle()
             }
         }
    
         func purchaseItem() {
             // FUNCTION TO PURCHASE ITEM HERE
             var success = true //appStoreWrapper.purchase...
    
             if success {
                 // Handle success
             } else {
                 alertDialog?.selectedType = .purchaseError
                 showAlert.toggle()
             }
         }
    
         func getAlert() -> Alert {
             guard let dialog = alertDialog else {
                 return Alert(title: Text("Error getting alert dialog."))
             }
    
             switch dialog.selectedType {
             case .purchaseError:
                 return Alert(
                     title: Text("Error purchasing item."),
                     message: nil,
                     dismissButton: .default(Text("OK")))
             case .mustBePurchased:
                 return Alert(
                     title: Text("Items have to be purchased."),
                     message: nil,
                     primaryButton: .default(Text("Purchase"), action: {
                         purchaseItem()
                     }),
                     secondaryButton: .cancel())
             case .none, .purchase:
                 return Alert(title: Text("Purchased!"))
             }
         }
    
     }
    

Upvotes: 2

Related Questions