66

I have to do some initial setup each time the app starts, but I'm getting the error:

enter image description here

The error is clear, the answer is not. I tried putting the init in a subview, but I can't, it needs to be in the root @main. This is how I have it defined:

@StateObject private var amplifyConfig: AmplifyConfig = AmplifyConfig()

init() {
    if(amplifyConfig.isAmplifyConfigured == false) {
        amplifyConfig.dataStoreHubEventSubscriber()
        amplifyConfig.configureAmplify()
    }
}

How do I get rid of that warning and actually implement it so it doesn't create multiple instances, at the end of the day that's why I'm using @EnvironmentObject for?

3 Answers 3

56

You cannot access any value before they get initialized, use onAppear():

import SwiftUI

@main
struct YourApp: App {
    
    @StateObject private var amplifyConfig: AmplifyConfig = AmplifyConfig()
    
    var body: some Scene {
        
        WindowGroup {
            ContentView()
                .onAppear() {
                    if (!amplifyConfig.isAmplifyConfigured) {
                        amplifyConfig.dataStoreHubEventSubscriber()
                        amplifyConfig.configureAmplify()
                    }
                }
        }
    }
}

Update: An actual use case

import SwiftUI

@main
struct YourApp: App {
    
    @StateObject private var amplifyConfig: AmplifyConfig = AmplifyConfig()
    
    @State private var isLoaded: Bool = Bool()
    
    var body: some Scene {
        
        WindowGroup {
            
            VStack {
                if (isLoaded) { ContentView() }
                else { Text("Loading . . .") }
            }
            .onAppear() {
                if (!amplifyConfig.isAmplifyConfigured) {
                    amplifyConfig.dataStoreHubEventSubscriber()
                    amplifyConfig.configureAmplify()
                    completionHandler { value in isLoaded = value }   
                }
                else {
                    isLoaded = true  
                }
            }
        }   
    }
}


func completionHandler(value: @escaping (Bool) -> Void) {
    
    // Some heavy work here, I am using DispatchQueue.main.asyncAfter for replicating that heavy work is happening! But you use your own code here.
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.milliseconds(3000)) { value(true) }

}
Sign up to request clarification or add additional context in comments.

7 Comments

nop, that doesn't work, the Amplify framwork fails as it looks for authentification and it's not configured, let me see if it's because I have another init asking for it hold on
Isn't there any other way to have it in the init? Putting the config in a view is going to add a log of wait time
@Arturo: See the update! I wanted gave you the idea how it works! you can use a completion handler to waiting to done everything then you make isLoaded = true, everything is in your hand. Go finish all the things you want then make isLoaded = true
I like that! Definitely a good idea, I'll implement it. Thank you and ++1
You are welcome. There is no rush that we should make everything done in initialization. We have other options as well that the result would be like we done it there.
|
6

I got this when wrongly mixing up @StateObject and @ObservedObject.

One or more @ObservedObjects should react to a single @StateObject

If you create a View holding a @StateObject and multiple subviews should react to changes of this object, you should have @ObservedObjects in all of these subviews

Don't pass a @StateObjects wrapped value to initializers of subviews for also creating @StateObjects in them

That is what I did first, leading to that error. Instead, the idea is that you should have only one @StateObject (which is the single source of truth) and one or more @ObservedObject reacting to changes of this single source of truth.

Comments

6

Update:

Apparently it's better to pass the initializer of the model directly to the StateObject(wrappedValue:). (Watch this Youtube Video and follow the description for more.)

The sample code can be changed to:

@StateObject private var amplifyConfig: AmplifyConfig

init() {
    self._amplifyConfig = StateObject(wrappedValue: AmplifyConfig())

    if !_amplifyConfig.wrappedValue.isAmplifyConfigured {
        _amplifyConfig.wrappedValue.dataStoreHubEventSubscriber()
        _amplifyConfig.wrappedValue.configureAmplify()
    }
}

From iOS 15.0 onwards, there is a new modifier available named .task(priority:_:). You can use it to also run some of your initialization code for amplifyConfig.

@StateObject private var amplifyConfig: AmplifyConfig = .init()

init() {}

var body: some View {
    VStack {
        // Your content
    }
    .task { // This can be attached to all `View` types, not only a `VStack`.
        if !amplifyConfig.isAmplifyConfigured {
            amplifyConfig.dataStoreHubEventSubscriber()
            amplifyConfig.configureAmplify()
        }
    }
}

Original Answer

I came across this thread, accepted answer doesn't work for my use case although it is correct. I fixed it using the following solution.

You could also initialize the AmplifyConfig in the init yourself, then assign it to @StateObject var .... Like below:

@StateObject private var amplifyConfig: AmplifyConfig

init() {
    let amplifyConfig = AmplifyConfig()
    self._amplifyConfig = StateObject(wrappedValue: amplifyConfig)

    if !amplifyConfig.isAmplifyConfigured {
        amplifyConfig.dataStoreHubEventSubscriber()
        amplifyConfig.configureAmplify()
    }
}

6 Comments

Just FYI SwiftUI wrappers don’t work outside of a view. They require a body to get updates and work properly. If your use case uses a StateObject in a struct, class, globally, etc. that isn’t a view it is an incorrect setup.
@loremipsum Thank you for the point. Actually my setup was a View, but I wanted to pass the object marked with @StateObject to another view which was created and stored in a property in the initializer of the view.
@StateObject is a source of through you pass the object with @ObservedObject or @EnvironmentObject
@loremipsum I did as you said, but I got the warning anyway.
You will only get that warning if a SwiftUI wrapper has been put in something other than a View it as to be a struct level.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.