Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS 8 push notification action buttons - code in handleActionWithIdentifier does not always run when app is in background

I am adding two action buttons to my push notifications on iOS 8: an Accept button and a Deny button. Neither button will open the app, but different server requests will be made depending on which button is pressed. Here's my setup:

+ (void)requestForPushNotificationToken {
    UIApplication *application = [UIApplication sharedApplication];
    // if ios 8 or greater
    if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {
        UIMutableUserNotificationAction *acceptAction = [[UIMutableUserNotificationAction alloc] init];
        [acceptAction setActivationMode:UIUserNotificationActivationModeBackground];
        [acceptAction setTitle:@"Accept"];
        [acceptAction setIdentifier:@"ACCEPT_ACTION"];
        [acceptAction setDestructive:NO];
        [acceptAction setAuthenticationRequired:NO];

        UIMutableUserNotificationAction *denyAction = [[UIMutableUserNotificationAction alloc] init];
        [denyAction setActivationMode:UIUserNotificationActivationModeBackground];
        [denyAction setTitle:@"Deny"];
        [denyAction setIdentifier:@"DENY_ACTION"];
        [denyAction setDestructive:NO];
        [denyAction setAuthenticationRequired:NO];

        UIMutableUserNotificationCategory *actionCategory = [[UIMutableUserNotificationCategory alloc] init];
        [actionCategory setIdentifier:@"ACTIONABLE"];
        [actionCategory setActions:@[acceptAction, denyAction]
                        forContext:UIUserNotificationActionContextDefault];

        NSSet *categories = [NSSet setWithObject:actionCategory];

        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert) categories:categories];
        [application registerUserNotificationSettings:settings];
    } else if ([application respondsToSelector:@selector(registerForRemoteNotificationTypes:)]) { // ios 7 or lesser
        UIRemoteNotificationType myTypes = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound;
        [application registerForRemoteNotificationTypes:myTypes];
    }
}

Then, in my delegate method, I am specifying actions to be taken when user pressed one of the action buttons:

- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler {
    if ([identifier isEqualToString:@"ACCEPT_ACTION"]) {
        // Sending a request to the server here
    }
    else if ([identifier isEqualToString:@"DENY_ACTION"]) {
        // Sending a request to the server here
    }

    if (completionHandler) {
        completionHandler();
    }
}

The ideal scenario is that the user does not need to launch the app in the whole process; pressing Accept or Deny will make different calls to the server. With the code above, I am seeing very unstable behaviors with the button actions:

  • A lot of times, the code in the action handler doesn't execute when app is in background and no server calls are made at all; when this happens, if I tap on my app icon and launch my app, the handler code will immediately be run upon launching the app.
  • Occasionally, the handler code gets triggered and everything works fine. Server requests are made as soon as I press one of the action buttons.
  • If I put breakpoints in my Xcode and step through the handler code, the success rate is also 100%. I do not need to launch my app, and handler code gets executed when button is pressed.

Could anyone please help me figure out what's causing such unstable behavior? Thanks in advance.

like image 448
SeaJelly Avatar asked Apr 25 '15 18:04

SeaJelly


People also ask

Do push notifications work when app is closed iOS?

Apple does not offer a way to handle a notification that arrives when your app is closed (i.e. when the user has fully quit the application or the OS had decided to kill it while it is in the background). If this happens, the only way to handle the notification is to wait until it is opened by the user.

Why push notification is not working for iOS?

You can fix an iPhone that's not getting notifications by restarting it or making sure notifications are turned on. You should also make sure your iPhone is connected to the internet so apps can receive notifications. If all else fails, you should try resetting the iPhone — just make sure to back it up first.

Does Apple block push notifications?

iOS enables you to both disable push notifications entirely, or turn them off for individual apps. To access iOS notifications settings, go into the Settings > Notifications menu. From that list, you can configure each app's notifications settings or disable them.


1 Answers

I have finally figured out the reason. The fact that it sometimes works and sometimes doesn't should have given me the hint much sooner.

According to the Apple documentation of application:handleActionWithIdentifier:forRemoteNotification:completionHandler::

Your implementation of this method should perform the action associated with the specified identifier and execute the block in the completionHandler parameter as soon as you are done. Failure to execute the completion handler block at the end of your implementation will cause your app to be terminated.

I am calling the completion handler at the end of the application:handleActionWithIdentifier:forRemoteNotification:completionHandler method. However, the fact that I am sending requests to the server in my handler code means that my end of implementation is not simply at the end of the method; my real end lies within the callback of my requests. The way I code it, completion handler and callback are on two different threads, and when completion handler runs before it reaches callback, it'll fail.

So the solution is to move the completion handler into the callback methods of the request, i.e., the real "end of the implementation". Something like this:

[MyClient sendRequest:userInfo withSuccessBlock:^(id responseObject){
    NSLog(@"Accept - Success");
    if (completionHandler) {
        completionHandler();
    }
} withFailureBlock:^(NSError *error, NSString *responseString) {
    NSLog(@"Accept - Failure: %@",[error description]);
    if (completionHandler) {
        completionHandler();
    }
}];
like image 98
SeaJelly Avatar answered Oct 06 '22 05:10

SeaJelly