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), based on a push notification framework such as Firebase Cloud Messaging (FCM) 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 FCM) 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 depending on standards defined by the push notification framework.
Genero API for push notifications
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 FCM push notifications
Android apps using push notification services need specific permissions (Android manifest), such
as android.permission.POST_NOTIFICATIONS
.
Permissions are automatically set for Android APK packages by the GMA buildtool.
See the FCM 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 buildtool.
Handling push notification in the app
1 - Registering to the push service and to the push provider
"registerForRemoteNotifications"
front call. - When using FCM, you must provide Sender ID to identify the FCM project.
- When using APNs, you can set the Sender ID to NULL.
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, the app must register for
notification each time it is upgraded.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
then performs a mobile.getRemoteNotifications front call as in
the usual way, to get the pending notifications pushed to the device while the app was off.
- 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 agetRemoteNotifications
front call. Even if there is no data available with the front call, it is recommended that the app sends a request directly to the server push provider to get last push data. - If the push notification does not contain badge numbers, it is still recommended that the app
performs a
getRemoteNotifications
front call when it starts. If there is no push data available from the front call, the recommendation is that the app sends a request to the server push provider to see if there is push data available. This is by the way also recommended when receiving anotificationpushed
action during application life time. - 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. It is recommended that the app perform agetRemoteNotifications
front call and get the push data.
registerForRemoteNotifications
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 FCM, the returned identifier is the FCM "registration token".
- When using APNs, the returned identifier is the APNs "device token".
...
LET rec.tm_host = "https://pushreg.example.orion"
LET rec.tm_port = 4930
LET rec.user_name = "mike"
LET rec.notification_type = "FCM"
...
DIALOG ATTRIBUTES(UNBUFFERED)
...
ON ACTION register
LET rec.registration_token = register(rec.notification_type, rec.user_name)
...
FUNCTION register(
notification_type STRING,
app_user STRING
) RETURNS STRING
DEFINE registration_token STRING
TRY
CALL ui.Interface.frontCall(
"mobile", "registerForRemoteNotifications",
[ ], [ registration_token ] )
IF tm_command( "register", notification_type,
registration_token, app_user, 0 ) < 0 THEN
RETURN NULL
END IF
CATCH
ERROR "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 FCM 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.
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 STRING,
notification_type STRING,
registration_token STRING,
app_user STRING,
badge_number INTEGER
) RETURNS 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("notification_type", notification_type)
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
ERROR 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
ERROR 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.
notificationpushed
action is used, consider setting action default attributes for
this action in your .4ad file as follows
(like done in FGLDIR/lib/default.4ad):<ActionDefaultList>
...
<ActionDefault name="notificationpushed" validate="no" defaultView="no" contextMenu="no"/>
...
</ActionDefaultList>
ON ACTION
handler:ON ACTION notificationpushed (VALIDATE=NO, DEFAULTVIEW=NO)
...
ON ACTION
block for this action, query for notification messages by using
the "getRemoteNotifications"
front call, (passing the Sender ID as parameter
when using FCM, for APNs the Sender ID must be NULL). This front call returns a JSON string
containing a list of notification messages to be processed:...
ON ACTION notificationpushed
CALL handle_notification()
...
FUNCTION handle_notification() RETURNS ()
DEFINE notif_list STRING,
notif_array util.JSONArray,
notif_item util.JSONObject,
notif_data util.JSONObject,
aps_record util.JSONObject,
id, info, other_info STRING,
i, x INTEGER
CALL ui.Interface.frontCall(
"mobile", "getRemoteNotifications",
[ ], [ notif_list ] )
TRY
IF notif_list.trimLeft() NOT LIKE "[%" THEN -- GBC-3043 Workaround
LET notif_list = "[ ", notif_list, " ]"
END IF
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 other_info = NULL
LET notif_item = notif_array.get(i)
-- Try APNs msg format
LET aps_record = notif_item.get("aps")
IF aps_record IS NOT NULL THEN
LET id = notif_item.get("id")
LET info = aps_record.get("alert")
LET notif_data = notif_item.get("custom_data")
IF notif_data IS NOT NULL THEN
LET other_info = notif_data.get("other_info")
END IF
ELSE
-- Try FCM msg format
LET notif_data = notif_item.get("data")
LET id = notif_data.get("id")
LET info = notif_data.get("content")
LET other_info = notif_data.get("other_info")
END IF
IF info IS NULL THEN
LET info = "Unexpected message format"
END IF
LET x = x + 1
LET rec.notifications = rec.notifications, "\n",
SFMT("%1(%2): Notifiation received: ID=%3\n %4[%5]",
x, CURRENT HOUR TO SECOND, id, info, other_info)
END FOR
CATCH
ERROR "Could not extract notification info"
END TRY
END FUNCTION
- Query the current badge number with the
getBadgeNumber
front call. - Compute the new badge number based on the number of notifications consumed.
- Reset the badge number with the setBadgeNumber front call.
- Inform the token maintainer to sync the badge number in the central database.
FUNCTION setup_badge_number(consumed INTEGER) RETURNS ()
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", "APNS", 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 - Handle notification interactions
notificationselected
action
and the getLastNotificationInterfactions
front call:...
ON ACTION notificationselected
CALL handle_notification_selection()
...
FUNCTION handle_notification_selection() RETURNS ()
DEFINE notif_array DYNAMIC ARRAY OF RECORD
id STRING,
type STRING
END RECORD,
x INTEGER
TRY
CALL ui.Interface.frontCall("mobile", "getLastNotificationInteractions",
[], [notif_array] )
FOR x=1 TO notif_array.getLength()
LET rec.notifications = rec.notifications, "\n",
SFMT("%1(%2): Notification selected: ID=%3 TYPE=%4",
x, CURRENT HOUR TO SECOND,
notif_array[x].id, notif_array[x].type)
END FOR
CATCH
ERROR "Could not get selected notifications info"
END TRY
END FUNCTION
5 - Clear notifications
clearNotifications
front call:...
ON ACTION clear
CALL clear_notifications()
...
FUNCTION clear_notifications() RETURNS ()
DEFINE res STRING
TRY
CALL ui.Interface.frontCall(
"mobile", "clearNotifications",
[ ], [ res ] )
LET rec.notifications = res
CATCH
ERROR "Clear notifications failed!"
END TRY
END FUNCTION
6 - Unregistering the app from push notification
regunreg_token()
function), and unregister from the push
service by using the "unregisterFromRemoteNotifications"
front call.- When using FCM, you must pass the FCM 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(rec.registration_token, rec.app_user)
...
FUNCTION unregister(
notification_type STRING,
registration_token STRING,
app_user STRING
) RETURNS ()
IF tm_command( "unregister", notification_type,
registration_token, app_user, 0 ) < 0 THEN
RETURN
END IF
TRY
CALL ui.Interface.frontCall(
"mobile", "unregisterFromRemoteNotifications",
[ ], [ ] )
CATCH
ERROR "Un-registration failed!"
RETURN
END TRY
MESSAGE "Un-registration succeeded!"
END FUNCTION