Home > Net >  Calling an asynchronous method (getNotificationSettings) in the onAppear() method becomes nil - Swif
Calling an asynchronous method (getNotificationSettings) in the onAppear() method becomes nil - Swif

Time:04-12

I have a NotificationsManager class to manage all notification-related code, including the code to check if the user has turned off local notifications. I use this class in multiple parts of my code and it works fine, my issue is that I want to call the getNotificationSettings through the NotificationsManager class in the onAppear method in SwiftUI but it always becomes nil. I know it has to do something with the fact that the getNotificationSettings is an asynchronous method, but I'm not sure how to call it and get the result on the onAppear method.

In the following code, notificationManager.notificationAuthorizationStatus always becomes nil in the onAppear method.

Any idea how can I call the reloadAuthorizationStatus() method in the onAppear method in SwiftUI and right after check the notificationAuthorizationStatus property?

NotificationsManager Class

class NotificationsManager: ObservableObject{
    @Published private(set) var notificationAuthorizationStatus: UNAuthorizationStatus?

    func reloadAuthorizationStatus() {
        UNUserNotificationCenter.current().getNotificationSettings { settings in
            DispatchQueue.main.async {
                self.notificationAuthorizationStatus = settings.authorizationStatus
            }
        }
    }
}

ContentView

struct ContentView: View {
    @StateObject private var notificationManager = NotificationsManager()

    var body: some View {
        NavigationView {
            Form {
                // textFields...
            }
            .onAppear(){
                notificationManager.reloadAuthorizationStatus() 
                // here the notificationAuthorizationStatus is always nil              
                switch notificationManager.notificationAuthorizationStatus{
                case .denied:
                    hasAuthorized = false
                default:
                    break
                }
            }
        }
    }
}

If I add a 1-second delay before calling the notificationAuthorizationStatus it works but I know this is not right since sometimes it may take longer than just a second to get the results.

Call with a 1 Second Delay

Works but I don't feel it's the right thing to do

.onAppear(){
    notificationManager.reloadAuthorizationStatus()                
    let secondsDelay = DispatchTime.now()   1
    DispatchQueue.main.asyncAfter(deadline: secondsDelay){
        switch notificationManager.notificationAuthorizationStatus{
        case .denied:
            hasAuthorized = false
        default:
            break
        }
    }
}

CodePudding user response:

With an asynchronous function, you have to wait until it has finished before you can use any of its results. Using a completion handler is a common approach to do just that. So try this approach:

func reloadAuthorizationStatus(completion: @escaping (Bool) -> ()) {  // <-- here
    UNUserNotificationCenter.current().getNotificationSettings { settings in
        DispatchQueue.main.async {
            self.notificationAuthorizationStatus = settings.authorizationStatus
            completion(true) // <-- here
        }
    }
}

and in ContentView

    .onAppear {
        notificationManager.reloadAuthorizationStatus { isDone in  // <-- here
            switch notificationManager.notificationAuthorizationStatus{
            case .denied:
                hasAuthorized = false
            default:
                break
            }
        }
    }

Another approach is to leave your code as is, and use this .onReceive:

.onReceive(notificationManager.notificationAuthorizationStatus.publisher) { status in
    switch status{
    case .denied:
        hasAuthorized = false
    default:
        break
    }
}
.onAppear {
    notificationManager.reloadAuthorizationStatus()
}
  • Related