Fix SwiftUI environnementObject error crash on released app


My app on AppStore got two crashes that look very similar.
This crash seems to append when I call function that use a environnement object SubscriptionManager.

By analysing the error stack I think It happens in the function safeAddExercice() when I try to get self.subscriptionManager that is the environnement object.

These are errors stacks :



That is the function safeAddExercice() :
Code Block
private func safeAddExercice() {
if Exercice.canAddNew(for: self.subscriptionManager) {
self.showExerciceAdd = true
}else{
self.showMember = true
}
}

It's a function of ExerciceList :
Code Block
struct ExerciceList: View {
//some other environnement objects...
@EnvironmentObject var subscriptionManager: SubscriptionManager
...
}

ExerciceList is an item of TabBar :
Code Block
struct MainApp: View {
//some other envObjects...
@EnvironmentObject var subscriptionManager: SubscriptionManager
...
var body: some View {
UIKitTabView{
ExerciceList(selection: .constant(nil))
.environment(\.managedObjectContext, managedObjectContext)
.environmentObject(userSettings)
.environmentObject(subscriptionManager)
.tab(title: NSLocalizedString("My exercises", comment: ""), image: "dumbell")
...}
}

SubscriptionManager is instantiated in AppDelegate :
Code Block
var subscriptionManager: SubscriptionManager = {
SubscriptionManager()
}()

And pass to MainApp in SceneDelegate:
Code Block
let subscriptionManager = (UIApplication.shared.delegate as! AppDelegate).subscriptionManager
let contentView = MainApp().environment(\.managedObjectContext, context)
.environmentObject(subscriptionManager)
...

Crash appends both on iOS 13 and 14. I try to find a moment where I missed to inject object but I'm struggling a bit to find something.

Replies


Are you invoking safeAddExercice asynchronously? If so, the EnvironmentObjects may not be set at the time the callback is applied because it is not happening during the lifecycle of the view.

You could instead pass the environment object at the point when you schedule the exercise, thereby guaranteeing that the object will be available. E.g.:

Code Block
var body: some View {
Button("Add Exercise") {
let subman = self.subscriptionManager
DispachQueue.main.async {
safeAddExercice(subman)
}
}
}
func safeAddExercice(_ subman: SubscriptionManager) {
// operate on subman rather than self.subscriptionManager
}


I'm not doing asynchronously.
safeAddExercice() is call from .onTapGesture() and just passing true to state property linked to .sheet().