Mobile applications / Push notifications |
This topic describes how to handle push notification in the app running on mobile devices.
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 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.
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 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.
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.
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
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.
To get and handle notification events, the current active dialog must implement the notificationpushed special action.
... 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
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
... 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