Handling notifications in the mobile app

This topic describes how to handle push notification in the app running on mobile devices.

Basics

In order to implement a push notification mechanism, you need to set up a server part (token maintainer and push notification server), in conjunction with a push notification framework such as Google Cloud Messaging (GCM) or Apple Push Notification service (APNs). In Addition, you need to handle notification events in your mobile app. This section describes how to implement push notification in the app with the push notification API available in Genero BDL.

The same code base can be used to handle push notifications for Android (using GCM) and iOS (using APNs) devices. Only the content of the notification message will have to be processed with specific code, as the structure of the message differs according to standards defined by the push notification framework.

Genero API for push notifications

Genero BDL provides an API to handle push notification on mobile apps. Dedicated front calls are available to register to a push server, fetch push notification data, and unregister:

To detect when a notification message arrives from the push server, a specific action called notificationpushed must be used by app code on a ON ACTION handler. This special action is referenced as a predefined action.

Android app permissions for GCM push notifications

Android apps using push notification services need specific permissions (Android manifest), such as:
  • android.permission.INTERNET
  • android.permission.GET_ACCOUNTS
  • android.permission.WAVE_LOCK
  • com.google.android.c2dm.permission.RECEIVE
  • application-package-name.permission.C2D_MESSAGE where application-package-name is the Android package name of your app (for example, com.mycompany.pushclient)

Permissions will be automatically set when building the Android APK packages with the GMA build tool, according to the package name specified with the --build-app-package-name option.

See the GCM documentation for more details about required permissions for push notifications.

iOS app certificates for APNS push notifications

iOS apps must be created with an Apple certificate for development or distribution, linked to an App ID (or Bundle ID) with push notification enabled. The provisioning profile used when building the IPA must be linked to the App ID with push enabled. Certificate, provisioning and bundle id must be specified to the GMI build tool.

Handling push notification in the app

To handle push notifications in your mobile app, perform the following steps:
  1. Register to the push service and get the registration token
  2. Send the push notification token to your token maintainer
  3. Handle notification events with the notificationpushed action
  4. Eventually un-register from the push servers

1 - Registering to the push service and to the push provider

Register the app to the push notification service with the "registerForRemoteNotifications" front call.
  • When using GCM, you must provide Sender ID to identify the GCM project.
  • When using APNs, you can leave the Sender ID to NULL.
Note: The app does not need to register for notification each time it is restarted: Even if the app is closed, the registration is still active until the unregisterFromRemoteNotifications front call is performed. At first execution, an app will typically ask if the user wants to get push notifications and register to the push service if needed. To disable push notification, apps usually implement an option that can be disabled (to unregister) and re-enabled (to register again) by the user. On Android, that the app must register for notification each time it is upgraded.
Important:

When an app restarts, if notifications are pending and the app has already registered for push notification in a previous execution, the notificationpushed action will be raised as soon as a dialog with the corresponding ON ACTION handler activates. The app should then perform a getRemoteNotifications front call as in the regular case, to get the pending notifications pushed to the device while the app was off.

However, special consideration needs to be given to iOS devices. When push notification arrives for an iOS app that has not started, there is no mechanism to wake up the app and get the push data. Therefore, when the user starts the app from the springboard, there will never have any push data available. Depending on the context, implement the following programming patterns to solve this problem:
  1. If the push notification contains a badge number, the app can verify if the badge is greater than 0 (with the getBadgeNumber front call) in order to perform a getRemoteNotifications front call. Even if there is no data available with the front call, the app should directly ask the server push provider to get last push data.
  2. If the push notification does not contain badge numbers, the app should always perform a getRemoteNotification front call when it starts. If there is no push data available from the front call, the app should ask the server push provider if there is push data available. This is by the way also recommended when receiving a notificationpushed action during application life time.
  3. If the user starts the app from the Notification Center, the app is launched with push data transmitted from the system, and the notificationpushed action is sent. The app should the perform, the getRemoteNotifications front call and get the push data.
The registerForRemoveNotifications front call will return a registration token for the app which will be used by the push server (a.k.a push provider).
  • When using GCM, the returned identifier is the GCM "registration token".
  • When using APNs, the returned identifier is the APNs "device token".
CONSTANT GCM_SENDER_ID = "<enter your GCM Sender ID ('' for APNs)>"

...

    LET rec.tm_host = "https://pushreg.example.orion"
    LET rec.tm__port = 4930
    LET rec.app_user = "mike"

    LET rec.registration_token = register(GCM_SENDER_ID, rec.app_user)

...

FUNCTION register(sender_id, app_user)
    DEFINE sender_id STRING,
           app_user STRING
    DEFINE registration_token STRING
    TRY
        CALL ui.Interface.frontCall(
                "mobile", "registerForRemoteNotifications",
                [ sender_id ], [ registration_token ] )
        IF tm_command( "register", sender_id, registration_token, app_user, 0 ) < 0 THEN
           RETURN NULL
        END IF
    CATCH
        MESSAGE "Registration failed."
        RETURN NULL
    END TRY
    MESSAGE SFMT("Registration succeeded (token=%1)", registration_token)
    RETURN registration_token
END FUNCTION

2 - Sending a push notification token to your token maintainer

Once registered to the GCM or APNs service, the app must also register to the push server or push provider by sending the token obtained in step 1.

This is typically done by using a RESTFul HTTP POST, sending the token (along with additional application user information) to a dedicated server program that maintains the list of registered devices/tokens.

The device token maintainer can be implemented in BDL as a Web Service program, as described in Implementing a token maintainer.

In this tutorial, the tm_command() function implements token registration (as well as badge number handling for APNS):

IMPORT com
IMPORT util

...
    LET rec.tm_host = "https://pushreg.example.orion"
    LET rec.tm__port = 4930
...

FUNCTION tm_command( command, sender_id, registration_token, app_user, badge_number )
    DEFINE command STRING,
           sender_id STRING,
           registration_token STRING,
           app_user STRING,
           badge_number INTEGER
    DEFINE url STRING,
           json_obj util.JSONObject,
           req com.HTTPRequest,
           resp com.HTTPResponse,
           json_result STRING,
           result_rec RECORD
                          status INTEGER,
                          message STRING
                      END RECORD
    TRY
        LET url = SFMT( "http://%1:%2/token_maintainer/%3",
                        rec.tm_host, rec.tm_port, command )
        LET req = com.HTTPRequest.create(url)
        CALL req.setHeader("Content-Type", "application/json")
        CALL req.setMethod("POST")
        CALL req.setConnectionTimeOut(5)
        CALL req.setTimeOut(5)
        LET json_obj = util.JSONObject.create()
        CALL json_obj.put("sender_id", sender_id)
        CALL json_obj.put("registration_token", registration_token)
        CALL json_obj.put("app_user", app_user)
        CALL json_obj.put("badge_number", badge_number)
        CALL req.doTextRequest(json_obj.toString())
        LET resp = req.getResponse()
        IF resp.getStatusCode() != 200 THEN
           MESSAGE SFMT("HTTP Error (%1) %2",
                      resp.getStatusCode(),
                      resp.getStatusDescription())
           RETURN -2
        ELSE
           LET json_result = resp.getTextResponse()
           CALL util.JSON.parse(json_result, result_rec)
           IF result_rec.status >= 0 THEN
              RETURN 0
           ELSE
              MESSAGE SFMT("Notification maintainer message:\n %1", result_rec.message)
              RETURN -3
           END IF
        END IF
    CATCH
        MESSAGE SFMT("Failed to post token registration command: %1", STATUS)
        RETURN -1
    END TRY
END FUNCTION

When the app is declared as push notification client to the push server, continue with the normal program flow.

3 - Handling push notification events

To get and handle notification events, the current active dialog must implement the notificationpushed special action.

In the ON ACTION block for this action, query for notification messages by using the "getRemoteNotifications" front call, (passing the Sender ID as parameter when using GCM, for APNs the Sender ID must be NULL). This front call returns a JSON string containing a list of notification messages to be processed:
...
   DIALOG ...
       ...
       ON ACTION notificationpushed
          CALL handle_notification(sender_id)
       ...
   END DIALOG
...

FUNCTION handle_notification(sender_id)
    DEFINE sender_id STRING
    DEFINE notif_list STRING,
           notif_array util.JSONArray,
           notif_item util.JSONObject,
           notif_data util.JSONObject,
           notif_fld util.JSONObject,
           gcm_data, info STRING,
           i INTEGER
    CALL ui.Interface.frontCall(
              "mobile", "getRemoteNotifications",
              [ sender_id ], [ notif_list ] )
    TRY
        LET notif_array = util.JSONArray.parse(notif_list)
        IF notif_array.getLength() > 0 THEN
           CALL setup_badge_number(notif_array.getLength())
        END IF
        FOR i=1 TO notif_array.getLength()
            LET info = NULL
            LET notif_item = notif_array.get(i)
            -- Try APNs msg format
            LET notif_data = notif_item.get("custom_data")
            IF notif_data IS NULL THEN
               -- Try GCM msg format
               LET gcm_data = notif_item.get("data")
               IF gcm_data IS NOT NULL THEN
                  LET notif_data = util.JSONObject.parse(gcm_data)
               END IF
            END IF
            IF notif_data IS NOT NULL THEN
               LET info = notif_data.get("other_info")
            END IF
            IF info IS NULL THEN
               LET info = "Unexpected message format"
            END IF
            MESSAGE CURRENT HOUR TO SECOND, ": ", info
            SLEEP 1
        END FOR
    CATCH
        ERROR "Could not extract notification info"
    END TRY
END FUNCTION
When using APNS, the app must handle the badge numbers attached to the device token. The app must:
  1. Query the current badge number with the getBadgeNumber front call.
  2. Compute the new badge number according to the number of notifications consumed.
  3. Reset the badge number with the setBadgeNumber front call.
  4. Inform the token maintainer to sync the badge number in the central database.
The following function handles badge numbers for the app:
FUNCTION setup_badge_number(consumed)
    DEFINE consumed INTEGER
    DEFINE badge_number INTEGER
    TRY -- If the front call fails, we are not on iOS...
        CALL ui.Interface.frontCall("ios", "getBadgeNumber", [], [badge_number])
    CATCH
        RETURN
    END TRY
    IF badge_number>0 THEN
       LET badge_number = badge_number - consumed
    END IF
    CALL ui.Interface.frontCall("ios", "setBadgeNumber", [badge_number], [])
    IF tm_command( "badge_number",
                   rec.sender_id, rec.registration_token,
                   rec.user_name, badge_number) < 0 THEN
       ERROR "Could not send new badge number to token maintainer."
       RETURN
    END IF
END FUNCTION

4 - Unregistering the app from push notification

If the app no longer wants to get push notifications, unregister from the push provider (using a RESTful POST, in the regunreg_token() function), and unregister from the push service by using the "unregisterFromRemoteNotifications" front call.
  • When using GCM, you must pass the GCM Sender ID as parameter.
  • When using APNs, the parameter must be NULL.
...

    LET rec.tm_host = "https://pushreg.example.orion"
    LET rec.tm__port = 4930

    CALL unregister(GCM_SENDER_ID, rec.registration_token, rec.app_user)

...

FUNCTION unregister(sender_id, registration_token, app_user)
    DEFINE sender_id STRING,
           registration_token STRING,
           app_user STRING
    IF tm_command( "unregister", sender_id, registration_token, app_user, 0 ) < 0 THEN
       RETURN
    END IF
    TRY
        CALL ui.Interface.frontCall(
                "mobile", "unregisterFromRemoteNotifications",
                [ sender_id ], [ ] )
    CATCH
        MESSAGE "Un-registration failed (broacast service)."
        RETURN
    END TRY
    MESSAGE "Un-registration succeeded"
END FUNCTION