I'm working on a payment review screen, and I need to make a network call to get some legal agreement content. The problem is, having this call as a task attached to the View makes the View unresponsive to state changes, and I'm seeing a lot of "=== AttributeGraph: cycle detected through attribute xxxxx ===" console warnings. Here's a simplified version of the view:
struct OneTimePayReviewExperienceView: View {
@State var authorized: Bool = false
@StateObject var paymentExpViewModel = PaymentExpViewModel()
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 24) {
if let agreementText = paymentExpViewModel.agreementHtml {
Text(html: agreementText)
}
HStack(spacing: 8) {
CheckboxTwoToneView(selected: authorized)
Text("I authorize this payment to be made.")
Spacer()
}
.onTapGesture {
self.authorized = true
}
}//VStack
.task {
await paymentExpViewModel.fetchPartnerPaymentAgreement(
agreementType: .onetimepayment,
policySourceSystemName: paymentInput.policySourceSystem,
partnerId: paymentInput.policyPartnerId
)
}
}//ScrollView
}//body
}
And here's a simplified version of the ViewModel:
@MainActor
class PaymentExpViewModel: ObservableObject {
@Published var agreementHtml: String?
let dataManager = AFIDataManager.sharedManager
func fetchPartnerPaymentAgreement(agreementType: AgreementType,policySourceSystemName: String,partnerId: String) async {
do {
let agreementRequest = APIRouter().createRequest(.agreements(agreementType, policySourceSystemName))
let richContent = try await billingService.fetch(type: AgreementResponse.self, request: agreementRequest)
var html = richContent.richContent
let partnerPhoneNumber = dataManager.partnerPhoneNumbeer(for: partnerId)
html += partnerPhoneNumber
self.agreementHtml = html
} catch {
self.agreementHtml = nil
}
}
}
The problem is that the "authorized" state var isn't being set (or the state changes aren't being picked up?) when I have the code to call the network. (If I comment that code out, the state updates fine). I'm guessing the AttributeGraph warnings mean the view updates are getting stuck in a loop somehow, but I'm not sure why. Is it a threading issue with the async call? I tried adding @MainActor to the ViewModel to force it to execute on the main thread, but it didn't seem to work. Is it the way I'm updating the agreementText?
Is there a good way to debug this so I can understand what's going on?