47

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?

Arturo
  • 3,254
  • 2
  • 22
  • 61

2 Answers2

49

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) }

}
ios coder
  • 1
  • 4
  • 31
  • 91
  • 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 – Arturo Aug 25 '21 at 22:27
  • 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 Aug 25 '21 at 22:33
  • This the most common question also the most common mistake to doing stuff in initialization of a View! You should not do such things there, use onAppear with some condition that your app would show kind of loading View until that part of code done it`s work then turn again to normal way. I update the code for you. – ios coder Aug 25 '21 at 22:38
  • Alrighty, I didn't know this question was so common, it's actually pretty misunderstood then haha thanks – Arturo Aug 25 '21 at 22:45
  • 1
    @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` – ios coder Aug 25 '21 at 23:02
  • 1
    I like that! Definitely a good idea, I'll implement it. Thank you and ++1 – Arturo Aug 25 '21 at 23:04
  • 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. – ios coder Aug 25 '21 at 23:05
1

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()
    }
}
MrAlirezaa
  • 328
  • 3
  • 9
  • 1
    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. – lorem ipsum Dec 15 '22 at 12:19
  • @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. – MrAlirezaa Dec 16 '22 at 15:46
  • `@StateObject` is a source of through you pass the object with `@ObservedObject` or `@EnvironmentObject` – lorem ipsum Dec 16 '22 at 17:23
  • @loremipsum I did as you said, but I got the warning anyway. – MrAlirezaa Dec 17 '22 at 13:10
  • 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. – lorem ipsum Dec 17 '22 at 13:38
  • @loremipsum I can share the code with you if you wish. I think we are in a misunderstanding. – MrAlirezaa Dec 17 '22 at 19:40