Arturo
Arturo

Reputation: 4200

How to initialize app with an Async function?

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.

Upvotes: 6

Views: 9368

Answers (2)

malhal
malhal

Reputation: 30746

Normally we use .task for async/await but that is only for Views (it starts on appear and is cancelled on disappear) so that doesn't suit your use case of running the task on app launch. The documentation for Scene suggests to use .onChange as the place to start tasks, and this is their example for doing background processing:

.onChange(of: locale) {
    Task.detached(priority: .background) {
        // ...
    }
}

However that doesn't really suit your use case either because detached tasks can't update any view data. So, perhaps it would be a better idea to start your task from a global like this:

WindowGroup {
    ContentView(model: Manager.shared.model)
}
struct Manager {
    let shared = Manager()
    let model = Model()
    init() {
        Task { [model] in
            // observe, read or write to the model
        }
    }
}

@Observable class Model {

}

Upvotes: -1

lorem ipsum
lorem ipsum

Reputation: 29614

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

Upvotes: 12

Related Questions