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
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 FCM push notifications
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 are automatically set for Android APK packages by the GMA
buildtool. As some permissions need to be prefixed with the package name, they are applied
with the --build-app-package-name
option.
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
- Register to the push service and get the registration token
- Send the push notification token to your token maintainer
- Handle notification events with the
notificationpushed
action - Eventually un-register from the push servers
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 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
getRemoteNotification
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.
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 FCM, the returned identifier is the FCM "registration token".
- When using APNs, the returned identifier is the APNs "device token".
DEFINE rec RECORD
tm_host STRING,
tm_port INTEGER,
notification_type STRING,
user_name STRING,
registration_token STRING
END RECORD
...
LET rec.tm_host = "https://pushreg.example.orion"
LET rec.tm_port = 4930
LET rec.app_user = "mike"
LET rec.notification_type = "FCM"
...
DIALOG ATTRIBUTES(UNBUFFERED)
INPUT BY NAME rec.tm_host,
rec.tm_port,
rec.notification_type,
rec.user_name,
rec.registration_token
ATTRIBUTES(WITHOUT DEFAULTS)
END INPUT
...
ON ACTION register
LET rec.registration_token = register(rec.notification_type, rec.user_name)
...
FUNCTION register(notification_type, app_user)
DEFINE notification_type STRING,
app_user 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
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 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.
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, notification_type, registration_token,
app_user, badge_number )
DEFINE command STRING,
notification_type 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("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
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.
notificationpushed
action is used, consider setting action default attributes for
this action in your .4ad file as
follows:<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:DEFINE notifs DYNAMIC ARRAY OF RECORD
info STRING,
ts DATETIME YEAR TO FRACTION(3)
END RECORD
...
DEFINE x INTEGER
DIALOG ...
DISPLAY ARRAY notifs TO sr.*
END DISPLAY
...
ON ACTION notificationpushed
LET x=handle_notification()
CALL DIALOG.setCurrentRow("sr",x)
...
END DIALOG
...
FUNCTION handle_notification()
DEFINE notif_list STRING,
notif_array util.JSONArray,
notif_item util.JSONObject,
notif_data util.JSONObject,
aps_record util.JSONObject,
gcm_data_s STRING,
gcm_genero_notification_s STRING,
gcm_genero_notification util.JSONObject,
info, other_info STRING,
i, x INTEGER
CALL ui.Interface.frontCall(
"mobile", "getRemoteNotifications",
[ ], [ 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 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 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 GCM msg format
LET gcm_data_s = notif_item.get("data")
IF gcm_data_s IS NOT NULL THEN
LET notif_data = util.JSONObject.parse(gcm_data_s)
IF notif_data IS NOT NULL THEN
LET gcm_genero_notification_s = notif_data.get("genero_notification")
LET gcm_genero_notification = util.JSONObject.parse(
gcm_genero_notification_s )
IF gcm_genero_notification IS NOT NULL THEN
LET info = gcm_genero_notification.get("content")
END IF
LET other_info = notif_data.get("other_info")
END IF
END IF
END IF
IF info IS NULL THEN
LET info = "Unexpected message format"
END IF
MESSAGE SFMT("Notification message:\n%1\n%2", info, other_info)
CALL notifs.appendElement()
LET x = notifs.getLength()
LET notifs[x].info = SFMT("%1 (%2)", info, other_info)
LET notifs[x].ts = CURRENT
END FOR
CATCH
MESSAGE "Could not extract notification info"
END TRY
RETURN IIF(x==0,1,x)
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)
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", "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 - 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, registration_token, app_user)
DEFINE notification_type STRING,
registration_token STRING,
app_user STRING
IF tm_command( "unregister", notification_type,
registration_token, app_user, 0 ) < 0 THEN
RETURN
END IF
TRY
CALL ui.Interface.frontCall(
"mobile", "unregisterFromRemoteNotifications",
[ ], [ ] )
CATCH
MESSAGE "Un-registration failed (broacast service)."
RETURN
END TRY
MESSAGE "Un-registration succeeded"
END FUNCTION