skip to Main Content

I’m getting this error in production and can’t find a way to reproduce it.

Fatal error > No ObservableObject of type PurchaseManager found. A
View.environmentObject(_:) for PurchaseManager may be missing as an
ancestor of this view. > PurchaseManager > SwiftUI

The crash comes from this view:

struct PaywallView: View {
    @EnvironmentObject private var purchaseManager: PurchaseManager
    var body: some View {
        // Call to purchaseManager causing the crash
    }
}

And this view is instantiated in subviews of the MainView

@main
struct MyApp: App {
    let purchasesManager = PurchaseManager.shared
    var body: some Scene {
        WindowGroup {
            MainView()
                .environmentObject(purchasesManager)
            }
        }
    }
}

or, when called from a UIKit controller, from this controler:

final class PaywallHostingController: UIHostingController<AnyView> {
    init() {
        super.init(rootView:
                    AnyView(
                        PaywallView()
                            .environmentObject(PurchaseManager.shared)
                    )
        )
    }
    @objc required dynamic init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

I tested all the use cases that trigger the PaywallView to show up, and I never got a crash.

FWIW, the PurchaseManager looks like this:

public class PurchaseManager: ObservableObject {   
    static let shared       = PurchaseManager()
    init() {
        setupRevenueCat()
        fetchOfferings()
        refreshPurchaserInfo()
    }
}

Why would the ObservableObject go missing? In which circumstances?

2

Answers


  1. Try not to use the singleton pattern here (.shared), EnvironmentObject is meant to be a replacement for it. You should instantiate PurchasesManager in MyApp.

    @main
    struct MyApp: App {
        @StateObject var purchasesManager = PurchaseManager() 
        var body: some Scene {
            WindowGroup {
                MainView()
                    .environmentObject(purchasesManager)
                }
            }
        }
    }
    

    without state object compiles fine but needed if you want child views to update automatically.

    Doing those things with a dummy PurchasesManager runs fine for me.

    Login or Signup to reply.
  2. The reason your problem is intermittent, is probably because the PurchaseManager init()
    could finish
    before all the data is setup properly, due to the "delays" of the
    async functions in init(). So sometimes the data will be available
    when the View wants it, and sometimes it will not be there and crash your app.

    You could try the following approach that includes @atultw advice of using
    StateObject.

    import SwiftUI
    
    @main
    struct TestApp: App {
        @StateObject var purchaseManager = PurchaseManager() // <-- here
        
        var body: some Scene {
            WindowGroup {
                MainView()
                    .onAppear {
                        purchaseManager.startMeUp()    // <-- here
                    }
                    .environmentObject(purchaseManager)
            }
        }
    }
    
    struct MainView: View {
        @EnvironmentObject var purchaseManager: PurchaseManager
        
        var body: some View {
            Text("testing")
            List {
                ForEach(purchaseManager.offerings, id: .self) { offer in
                    Text(offer)
                }
            }
        }
    }
    
    public class PurchaseManager: ObservableObject {
        @Published var offerings: [String] = []
        
        // -- here --
        func startMeUp() {
            // setupRevenueCat()
            fetchOfferings()
            // refreshPurchaserInfo()
        }
        
        func fetchOfferings() {
            DispatchQueue.main.asyncAfter(deadline: .now()+2) {
                self.offerings = ["offer 1","offer 2","offer 3","offer 4"]
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search