How to handle actions on tapping push notification message in tray when app is closed/terminated

Hi All,

We are redeveloping the ios app using flutter where it receives the push notification from APNs server that is further processed by clicking actions Allow/Deny listed after tapping the push notification message in tray.
Problem - When Flutter-iOS app is closed/terminated state and user click Allow/Deny action on tapping the push notification message, then app gets openup up but not able to get hold of the action and related push notification payload. Following is AppDelegate.m and PushPlugin.m code that handles the push notification. Few observations

  1. When app is opening up didFinishLaunchingWithOptions method gets called but that does not contain any details about action and push notification payload.
  2. In non-flutter app, I noticed didFinishLaunchingWithOptions first gets called and after that didReceiveNotificationResponse gets called that has the action with payload details for processing.
    Can you please help me to understand
  3. Does flutter-ios app should call didReceiveNotificationResponse callback method to handle the push notification message?
  4. What are the changes we should make to handle action with push notification message

FYI, if I directly click on push notification instead of actions listed on the message then didFinishLaunchingWithOptions able to read the notification details.

AppDelegate.m

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    self.flutterEngineInitialized = NO;  // Initially, engine is not ready

    // Initialize the FlutterViewController to access the Flutter engine
    self.flutterViewController = (FlutterViewController *)self.window.rootViewController;
    [GeneratedPluginRegistrant registerWithRegistry:self];

    // Register the PushPlugin
    [PushPlugin registerWithRegistrar:[self registrarForPlugin:@"PushPlugin"]];

    // Call PushPlugin to request notification permissions
    [PushPlugin performTaskPostNotificationPermissionAllowed];

    if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
        NSDictionary *userInfo = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
        NSString *actionIdentifier = userInfo[@"actionIdentifier"];
        if (userInfo) {
            [self handleNotificationLaunchAfterFlutterInitialization:actionIdentifier userInfo:userInfo];
        } else {
            NSLog(@"Error: Notification data is missing.");
        }
    }

    // Watch for the Flutter engine initialization
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        self.flutterEngineInitialized = YES;  // Set the engine as initialized after a small delay
    });

    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (void)handleNotificationLaunchAfterFlutterInitialization:(NSString *)actionIdentifier userInfo:(NSDictionary *)userInfo {
    if (self.flutterViewController) {
        // Check if the Flutter engine is ready
        if (self.flutterEngineInitialized) {
            // Now that Flutter is ready, send the notification data to Flutter/dart code through method channel
            [[PushPlugin sharedInstance] handleNotificationLaunch:actionIdentifier userInfo:userInfo];
        } else {
            // If Flutter engine isn't ready yet, defer the method call
            NSLog(@"Flutter engine not ready, deferring notification handling.");
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self handleNotificationLaunchAfterFlutterInitialization:actionIdentifier userInfo:userInfo];
            });
        }
    }
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)(void))completionHandler {
    NSDictionary *userInfo = response.notification.request.content.userInfo;            
    // Pass notification data to PushPlugin instance when the user clicks a notification
    [[PushPlugin sharedInstance] userNotificationCenter:center
                                       didReceiveNotificationResponse:response
                                                withCompletionHandler:completionHandler];
}

================
PushPlugin.m

(void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
    FlutterMethodChannel *channel = [FlutterMethodChannel
        methodChannelWithName:OMACHANNEL
              binaryMessenger:[registrar messenger]];

    PushPlugin *instance = [self sharedInstance];
    instance.channel = channel;
    [registrar addMethodCallDelegate:instance channel:channel];
    
    // Request permission for notifications
    [PushPlugin performTaskPostNotificationPermissionAllowed ];
}

// Request notification permissions
- (void)requestNotificationPermissions {
    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
    center.delegate = self; // Set delegate for notification handling
    [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound + UNAuthorizationOptionBadge)
                          completionHandler:^(BOOL granted, NSError * _Nullable error) {
        if (granted) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [[UIApplication sharedApplication] registerForRemoteNotifications];
            });
        }
    }];
   //This configures UNNotificationAction Allow/Deny and UNNotificationCategory
    [PushPlugin registerForNotification];
}

Hi All, I am new to flutter, your inputs will help me to handle/implement the things correctly for push notification actions when app is closed/terminated.

Hi All,

In native ios app (non-flutter app), I noticed didFinishLaunchingWithOptions first gets invoked and after that didReceiveNotificationResponse invoked when clicked notification action defined on push notification in the banner.

Can you please tell me flutter iso app should have the same behaviour? or We have to handle it differently?

Appreciate any help on this