4

I need my app to configure the backend at start, here's the function to do so:

// Initializes Amplify
final func configureAmplify() async {
    do {
//            Amplify.Logging.logLevel = .info
        let dataStore = AWSDataStorePlugin(modelRegistration: AmplifyModels())
        let syncWithCloud = AWSAPIPlugin()
        let userAuth = AWSCognitoAuthPlugin()

        try Amplify.add(plugin: userAuth)
        try Amplify.add(plugin: dataStore)
        try Amplify.add(plugin: syncWithCloud)
        try Amplify.configure()
        print("Amplify initialized")
    } catch {
        print("Failed to initialize Amplify with \(error)")
    }
}

I tried placing it in the @main init like so:

init() async {
    await networkController.configureAmplify()
}

but I get the following error:

Type 'MyApplicationNameApp' does not conform to protocol 'App'

I try to apply the suggestions after that which is to initialize it:

init() {
        
}

but it seems odd, so now I have 2 init. What is going on here and what is the correct way to initialize multiple async functions at the start of the app, example:

  1. Code above (configure amplify)
  2. Check if user is logged in
  3. Set session

etc

Note: The init() async never gets called in the example above which is another problem within this question, so what is the correct way to initialize async function when the app starts.

Arturo
  • 3,254
  • 2
  • 22
  • 61

1 Answers1

12

Use the ViewModifier

.task{
    await networkController.configureAmplify()
}

You can add a Task to the init but you might have issues because SwiftUI can re-create the View as it deems necessary

init(){
    Task(priority: .medium){
        await networkController.configureAmplify()
    }
}

Or you can use an ObservableObject that is an @StateObject

With an @StateObject SwiftUI creates a new instance of the object only once for each instance of the structure that declares the object.

https://developer.apple.com/documentation/swiftui/stateobject

@main
struct YourApp: App {
    @StateObject var networkController: NetworkController = NetworkController()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
class NetworkController: ObservableObject{
    
    init() {
        Task(priority: .medium){
            await configureAmplify()
        }
    }
    // Initializes Amplify
    final func configureAmplify() async {
        do {
            //            Amplify.Logging.logLevel = .info
            let dataStore = AWSDataStorePlugin(modelRegistration: AmplifyModels())
            let syncWithCloud = AWSAPIPlugin()
            let userAuth = AWSCognitoAuthPlugin()
            
            try Amplify.add(plugin: userAuth)
            try Amplify.add(plugin: dataStore)
            try Amplify.add(plugin: syncWithCloud)
            try Amplify.configure()
            print("Amplify initialized")
        } catch {
            print("Failed to initialize Amplify with \(error)")
        }
    }
}
lorem ipsum
  • 21,175
  • 5
  • 24
  • 48
  • So how do I make it in a way that I don't have issues? lol This initialization of the API is very important, I don't want my view to dismiss it once it's recreated. I'm watching the WWDC videos now, If I find a better alternative I'll post it here :) – Arturo Aug 05 '21 at 16:29
  • You can’t upon init, all of SwiftUI comes with that disclosure. Your best bet is using task. Custom inits are a world of trouble in a View. You can also put it in the init of a ViewModel class that is an ObservableObject and a StateObject – lorem ipsum Aug 05 '21 at 16:43
  • That's how I currently have it, the whole logic is in an `@ObservableObject`, so you are saying to put the logic there and then how do I initialize it once I hit the view under `@main`? Wouldn't it be the same thing? – Arturo Aug 05 '21 at 16:55
  • See the code I pasted. it isn't the same because a `@StateObject` is guaranteed to only load once per Apple. Don't use `@ObservedObject` it has to be `@StateObject` – lorem ipsum Aug 05 '21 at 17:09
  • What about passing the environment in the main view? `.environmentObject(networkController)` shouldn't I be passing it along? – Arturo Aug 05 '21 at 17:16
  • You can that is up to you, if you need it lower in the chain you can pass it along. Your question doesn't talk about the environment. The Source of truth has to be a `@StateObject`. Just don't call `NetworkController()` again to initialize because you will have a separate instance that will not talk to the original instance – lorem ipsum Aug 05 '21 at 17:20
  • Yep I see what you are saying, I'll implement it in a sec and see but I think this answer will serve this purpose very nice, I'll let you know in a couple of min. thanks you :) – Arturo Aug 05 '21 at 17:22
  • It looks like there is a little loop, the initializer gets called twice – Arturo Aug 05 '21 at 17:33
  • Edit: it gets called as many times as async calls are inside of it... very weird – Arturo Aug 05 '21 at 17:40
  • For the `@StateObject`? it shouldn't. For Amplify maybe based on your code. I don't use amplify so I can't be sure but you are calling static methods 4 times – lorem ipsum Aug 05 '21 at 17:44
  • I recreated it into a different file and it gets called correctly, it seems I made a mistake along the way which I'll check. So this works perfect, many thanks! – Arturo Aug 05 '21 at 17:47
  • Just watch those `@ObservableObject` if you are initializing in a `View`. SwiftUI lets you do it but the `ObservableObject` gets recreated every time the `View` is refreshed. – lorem ipsum Aug 05 '21 at 17:59