Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-17705] iOS8: Handle action types for Remote Notifications as well

GitHub Issuen/a
TypeImprovement
Priorityn/a
StatusClosed
ResolutionFixed
Resolution Date2014-09-19T21:25:11.000+0000
Affected Version/sn/a
Fix Version/sRelease 3.4.0, Release 3.5.0
ComponentsiOS
Labelsn/a
ReporterTiman Rebel
AssigneeJon Alter
Created2014-09-16T14:27:57.000+0000
Updated2014-11-21T20:22:45.000+0000

Description

Currently only actions on Local Notifications are handled. We, as Titanium Developers, can display Remote Notifications with Actions, but a click on one of the buttons is never received by the app. In TiApp.m the following is implemented for Local Notifications
- (void) application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:
See: https://github.com/appcelerator/titanium_mobile/blob/master/iphone/Classes/TiApp.m#L441 A similar AppDelegate function has to be implemented to handle Actions on Remote Notifications
- application:handleActionWithIdentifier:forRemoteNotification:completionHandler:
See: https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/#//apple_ref/occ/intfm/UIApplicationDelegate/application:handleActionWithIdentifier:forRemoteNotification:completionHandler: I reckon that the implementation can be similar to
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
See: https://github.com/appcelerator/titanium_mobile/blob/master/iphone/Classes/TiApp.m#L825 With in addition a key in the event details which action was triggered.

Comments

  1. Jon Alter 2014-09-16

    Will fire event named 'remotenotificationaction'
  2. Timan Rebel 2014-09-17

    The local notifications trigger 'notification' or 'notificationbackground' (without the action part) and a remote notification triggers the callback registered when calling 'registerRemoteNotifications'. Can't you call the callback registered with registerRemoteNotifications or trigger 'remotenotification' and have every remote notification trigger that event as well? Now it seems inconsistent with how the rest of the notifications are handled.
  3. Timan Rebel 2014-09-18

    I just noticed that the event for local notifications is now called localnotificationaction, so please disregard my comment.
  4. Jon Alter 2014-09-18

    master PR: https://github.com/appcelerator/titanium_mobile/pull/6129 3_4_X PR: https://github.com/appcelerator/titanium_mobile/pull/6130

    Steps to test

    1. Setup push (have fun with that) 2. Open the ti.cloud example app and make a new file in the pushNotifications folder named test.js with the contents below. 2.5. Include the ti.cloud module 3. Update table.js to include the file you just added (see table.js line 7 and 18 below) 4. Run the app 5. Go to Push Notifications>Settings and click disabled (if registering for push is successful, you will get an alert with the device token) 6. Go back one window and click Subscribe Token > fill in any channel name you like and then subscribe. 7. If this was successful, go to the dashboard, and send a push with one of the categories defined below. 8. You should see the remotenotificationaction event fired when you click one of the buttons on the alert.

    test.js

       var WindowManager = require('helper/WindowManager');
       var Utils = require('helper/Utils');
       var Cloud = require('ti.cloud');
       exports['Test'] = function (evt) {
           var rows = [
               {
                   title: 'currentUserNotificationSettings',
                   onClick: function(){
                       // iOS 8 +
                       var settings =  Ti.App.iOS.currentUserNotificationSettings;
                       logUserNotificationSettings(settings);
                   }
               },
               {
                   title: 'registerUserNotificationSettings',
                   onClick: function(){
                       // iOS 8 +
                       // The following action launches the application in the foreground and requires the device to be unlocked
                       var acceptAction = Ti.App.iOS.createUserNotificationAction({
                           identifier: "ACCEPT_IDENTIFIER",
                           title: "Accept",
                           activationMode: Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_FOREGROUND,
                           destructive: false,
                           authenticationRequired: true
                       });
                       var testAction = Ti.App.iOS.createUserNotificationAction({
                           identifier: "TEST_IDENTIFIER",
                           title: "Test",
                           activationMode: Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_FOREGROUND,
                           destructive: false,
                           authenticationRequired: true
                       });
            
                       var foregroundCategory = Ti.App.iOS.createUserNotificationCategory({
                           identifier: "FOREGROUND_CATEGORY",
                           // The following actions will be displayed for an alert dialog
                           actionsForDefaultContext: [acceptAction, testAction],
                           // The following actions will be displayed for all other notifications
                           actionsForMinimalContext: [acceptAction, testAction]
                       });
       
       
       
                       // The following action launches the application in the foreground and requires the device to be unlocked
                       var acceptAction = Ti.App.iOS.createUserNotificationAction({
                           identifier: "ACCEPT_IDENTIFIER",
                           title: "Accept",
                           activationMode: Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_FOREGROUND,
                           destructive: false,
                           authenticationRequired: true
                       });
                        
                        
                       // The following action will only activate the application in the background, requires the device to be unlocked, and may have a red background. 
                       var rejectAction = Ti.App.iOS.createUserNotificationAction({
                           identifier: "REJECT_IDENTIFIER",
                           title: "Reject",
                           activationMode: Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_BACKGROUND,
                           destructive: true,
                           authenticationRequired: true
                       });
       
       
                       var invitationCategory = Ti.App.iOS.createUserNotificationCategory({
                           identifier: "INVITE_CATEGORY",
                           // The following actions will be displayed for an alert dialog
                           actionsForDefaultContext: [acceptAction, rejectAction],
                           // The following actions will be displayed for all other notifications
                           actionsForMinimalContext: [acceptAction, rejectAction]
                       });
       
       
            
                       Ti.App.iOS.registerUserNotificationSettings({
                           types: [Ti.App.iOS.USER_NOTIFICATION_TYPE_ALERT,
                                   Ti.App.iOS.USER_NOTIFICATION_TYPE_SOUND,
                                   Ti.App.iOS.USER_NOTIFICATION_TYPE_BADGE],
                           categories: [invitationCategory, foregroundCategory]
                       });
                   }
               },
               {
                   title: 'scheduleLocalNotification',
                   onClick: function(){
                       Ti.App.iOS.scheduleLocalNotification({
                           date: new Date(new Date().getTime() + 3000),
                           alertBody: "New content available! Download now?",
                           badge: 1,
                           userInfo: {"url": "http://www.download.com/resource/asset.json"},
                           category: "FOREGROUND_CATEGORY"
                           // category: "INVITE_CATEGORY"
                       });
                   }
               }
           ];
            
           function logInApp(text) {
               textLog.value = textLog.value + '\n' + text;
           }
            
           function logUserNotificationSettings(settings) {
               logInApp('UserNotificationSettings: ');
               logInApp('types: ' + JSON.stringify(settings.types));
               var categories = [];
               for (var i = 0, j = settings.categories.length; i < j; i++) {
                   categories.push(settings.categories[i].identifier);
               }
               logInApp('categories: ' + JSON.stringify(categories));
           }
            
           Ti.App.iOS.addEventListener('usernotificationsettings', function(e) {
               logInApp('Event: usernotificationsettings');
               logUserNotificationSettings(e);
           });
       
           Ti.App.iOS.addEventListener('localnotificationaction', function(e) {
               logInApp('#### localnotificationaction: ' + JSON.stringify(e));
               logInApp('#### category: ' + e.category + ' identifier: ' + e.identifier);
           });
       
           Ti.App.iOS.addEventListener('remotenotificationaction', function(e) {
               logInApp('#####---##### remotenotificationaction: ' + JSON.stringify(e));
               logInApp('#### category: ' + e.category + ' identifier: ' + e.identifier);
           }); 
       
           Ti.App.iOS.addEventListener('notification', function(e) {
               logInApp('#### notification');
           });
            
           ////////////////////////////////////////////////////////
           // UI
           ////////////////////////////////////////////////////////
           var win = Ti.UI.createWindow({
               backgroundColor: 'white'
           });
           // win.open();
            
           var textLog = Ti.UI.createTextArea({
               top: 0,
               height: '20%',
               width: '100%',
               borderWidth: '2',
               borderColor: '#000',
               value: 'AppLog: see device log for more info'
           });
           win.add(textLog);
            
           var tableView = Ti.UI.createTableView({
               top: '20%',
               data: rows
           });
           tableView.addEventListener('click', function(e){
               e.source.onClick && e.source.onClick();
           });
           win.add(tableView);
       
           return win;
       };
       

    table.js

       var WindowManager = require('helper/WindowManager');
       var Utils = require('helper/Utils');
       var Cloud = require('ti.cloud');
       var PushManager = require('windows/pushNotifications/pushManager');
       
       WindowManager.include(
           '/windows/pushNotifications/test',
           '/windows/pushNotifications/query',
           '/windows/pushNotifications/notify',
           '/windows/pushNotifications/settings',
           '/windows/pushNotifications/subscribe',
           '/windows/pushNotifications/unsubscribe',
           '/windows/pushNotifications/notifyTokens',
           '/windows/pushNotifications/subscribeToken',
           '/windows/pushNotifications/unsubscribeToken',
           '/windows/pushNotifications/updateSubscription',
           '/windows/pushNotifications/showChannels',
           '/windows/pushNotifications/queryChannels',
           '/windows/pushNotifications/setBadge',
           '/windows/pushNotifications/resetBadge'
       );
       exports['Push Notifications'] = function () {
           var win = WindowManager.createWindow({
               backgroundColor: 'white'
           });
       
           var rows = [
               'Test',
               'Notify',
               'Notify Tokens',
               'Query Subscriptions',
               'Show Channels',
               'Query Channels',
               'Set Badge',
               'Reset Badge'
           ];
           if (Ti.Platform.name === 'iPhone OS' || Ti.Platform.name === 'android') {
               rows.push('Settings for This Device');
               rows.push('Subscribe');
               rows.push('Unsubscribe');
               rows.push('Subscribe Token');
               rows.push('Unsubscribe Token');
               rows.push('Update Subscription');
           }
           else {
               // Our other platforms do not support push notifications yet.
           }
       
           var table = Ti.UI.createTableView({
               backgroundColor: '#fff',
               top: 0,
               data: Utils.createRows(rows)
           });
           table.addEventListener('click', WindowManager.handleOpenWindow);
           win.add(table);
           return win;
       };
       
       PushManager.checkPushNotifications();
       
  5. Wilson Luu 2014-09-19

    Closing ticket as fixed. Verified ACS iOS push (remote notification) can trigger the notification actions on device. Tested on: Appcelerator Studio, build: 3.4.0.201409161950 SDK build: 3.4.0.v20140919142515 ti.cloud: 3.2.4 Dashboard: preproduction (2.0.1) CLI: 3.4.0-rc4 Alloy: 1.5.0-rc3 Xcode: 6.0.1 Devices: iphone 5s (8.0)
  6. erez pilosof 2014-09-23

    please reopen ... doesn't work if the app is not running !!! or not in background !! serious issue
  7. Ingo Muschenetz 2014-09-23

    [~pilo] Please file a set of steps to reproduce your issue and we'd be happy to take a look.
  8. Steven van Loef 2014-11-01

    Like Perez Pilosof commented the action tapped by the user doesn't get fired when the app is not in memory (foreground or background). The steps described by Jon Alter all work except for step 8. The actions are shown on the notification center of course, but when tapping one of them it will just start the app but the 'remotenotificationaction' event is never fired. By adding some log statements to I found that in TiApp.m
       - (void) application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler {
       
    is called before app.js gets loaded. So that's too soon to even register for the 'remotenotificationaction' event in your app.
  9. Steven van Loef 2014-11-01

    A kind of dirty quick hack is to delay firing the event with this code (at about line 465 in TiApp.m of SDK 3.4.0):
       		double delayInSeconds = 5.0; // your mileage may vary here...
       		dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
       		dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
       		    //code to be executed on the main queue after delay
       		    [[NSNotificationCenter defaultCenter] postNotificationName:kTiRemoteNotificationAction object:event userInfo:nil];
       		    [event autorelease];
       		    completionHandler();
       		});
       

JSON Source