2

I use a singleton to access subscription offerings from RevenueCat as an @ObservedObject on various views in the app:

import Foundation
import Purchases
import SwiftUI

class SubscriptionManager: ObservableObject {
    static let shared = SubscriptionManager()

    @Published var offerings: Purchases.Offerings? = nil
    
    public func loadOfferings() {
        Purchases.shared.offerings { (offerings, error) in
            self.offerings = offerings
        }
    }
}

and then in the view

struct MyView: View {
    @ObservedObject var subManager = SubscriptionManager.shared

    // Currently selected package. Select initially the first one
    // A) like this the var doesn't update when the ObservedObject updates the offerings var
    @State private var selectedPackage: Purchases.Package? = SubscriptionManager.shared.offerings?.current?.availablePackages.first

    // B) like this it gives the error "Cannot use instance member 'subManager' within property initializer; property initializers run before 'self' is available"
    @State private var selectedPackage: Purchases.Package? = subManager.offerings?.current?.availablePackages.first

    var body: some View {
        // Paywall UI which displays the packages once the singleton loaded them and. which changes var selectedPackage if user selects a package
        // ...
    }
}

How can I properly define a @State var depending on the @ObservedObject published property which doesn't give me the error A or B

FrankZp
  • 2,022
  • 3
  • 28
  • 41
  • Your `subManager` is already observed, why do you need states? Use observed directly. – Asperi Jan 31 '22 at 14:39
  • @Asperi The purpose of the var selectedPackage is to keep track of the user's selected package. If the user taps a package, the var is updated and all the related UI will update accordingly. subManager doesn't keep track of the user's selected package, that's only relevant for MyView and related child views – FrankZp Jan 31 '22 at 14:42

1 Answers1

3

A possible variant is to observe changes in manager at set state correspondingly, like

struct MyView: View {
    @ObservedObject var subManager = SubscriptionManager.shared

    @State private var selectedPackage: Purchases.Package?

    // ... 

    var body: some View {
      
       SomeViewHere()
          .onChange(of: subManager.offerings) {
             self.selectedPackage = $0?.current?.availablePackages.first  // << here !!
          }

    }
}

also you should update published property on main queue (or make SubscriptionManager main actor, if min spec allows), like

public func loadOfferings() {
    Purchases.shared.offerings { (offerings, error) in
        DispatchQueue.main.async {
           self.offerings = offerings
        }
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690