Thursday Feb 12, 2015

Introduction to custom Cordova plugin development

Oracle Mobile Application Framework (MAF) v2.1.0 uses Cordova version 3.6.3 on Android and 3.7.0 on iOS to provide access to native device functionality.  The MAF 2.1.0 extension to JDeveloper 12.1.3 enables you to easily include any of the hundreds of published 3rd party Cordova plugins into your MAF app.

But what if you can’t find a suitable 3rd party plugin? You could elect to write your own, which, depending on the functionality required, may not be as difficult as you think.

In this post I’ll provide an introduction to developing a Cordova plugin by creating a very simple plugin for each of Android and iOS.

How does a Cordova plugin work?

In a nutshell, Cordova provides a bridge between JavaScript and native code, enabling you to write native code that gets exposed to your app via a common JavaScript interface.

Each method exposed by a plugin’s JavaScript interface is mapped to a method in the plugin’s native code via the Cordova bridge, which also enables you to pass parameters back and forth between the JavaScript and native methods.

What comprises a Cordova plugin?

A Cordova plugin typically consists of:

  • Native code for each supported platform
  • A common JavaScript interface
  • A manifest file called plugin.xml

The conventional structure for a Cordova plugin supporting both Android and iOS is:

  - Plugin top-level folder
     - plugin.xml
     - src/
        - android/
           - <Java source code>
        - ios/
           - <Objective-C source code>
     - www/
        - <JavaScript interface>

Ideally, community-published plugins also include release notes, author and license information, and a README file.  

A plugin may also include additional native resources and these are identified in the plugin.xml manifest file. This manifest file is read by the plugman command-line tool, which is used by the Cordova command-line interface and also by the MAF extension for JDeveloper.

In some rare cases, a plugin may be created that simply executes some native code on initialization and requires no JavaScript interface.

For more detailed information about the plugin.xml manifest file, refer to the Cordova Plugin Specification.

How do I create my own custom Cordova plugin?

To create your own custom Cordova plugin, you must write:

  • JavaScript that provides the interface for calling your plugin from within a Cordova-based app, such as a MAF app.
  • Native code that provides the functionality you need.  Since MAF supports both Android and iOS, you should write native code for both platforms.
  • A plugin.xml manifest file that defines your plugin and how plugman should incorporate it into a MAF app (or any Cordova-based app).

What tools do I need to create my own custom Cordova plugin?

You really only need a text editor to create your own custom Cordova plugin, which is all I’ve used to create the custom plugin described in this post.

For more complex plugins, you may wish to develop and test the plugin’s native code in each platform’s native IDE, which means downloading and installing the Android Studio and/or Apple’s Xcode.

You don’t need Cordova installed to develop a Cordova plugin.  Once you have developed your Cordova plugin, you can incorporate it directly into your MAF app for testing. However, if you wish to test your plugin within a Cordova app, you must download and install Cordova using the Cordova Command-Line Interface.

How do I write the plugin’s JavaScript interface?

We start with the JavaScript interface since it provides a common interface to both the Android and iOS native code.  This interface effectively defines what is required in the native code.

The JavaScript interface must call the cordova.exec method to invoke a native plugin method, as follows: 

cordova.exec(successCallback, failureCallback, service, action, [args]);

This call invokes the action method on the service class on the native side, passing the arguments in the optional args array.  If the native code completes successfully, the successCallback function is executed, along with any return parameters.  If the native code fails, the failureCallback function is executed, with an optional error parameter.  For more information on JavaScript callback functions, refer to this helpful blog post by Michael Vollmer.

Let’s create a very simple plugin that displays a native popup dialog.  Thus plugin shall present one method that takes 3 parameters – title, message and button label – and shall return the result in the ‘success’ callback. No ‘failure’ callback will be implemented. The service class shall be named “Alert” and we shall call the sole method “alert”. 

The following JavaScript defines the interface:

module.exports = {
  alert: function(title, message, buttonLabel, successCallback) {
    cordova.exec(successCallback,
                 null, // No failure callback
                 "Alert",
                 "alert",
                 [title, message, buttonLabel]);
  }
};

The call to module.exports exports the JavaScript function alert as part of the JavaScript module that will be defined in the plugin manifest file, plugin.xml

Your app will call this alert JavaScript function, which will invoke the alert method on the Alert class in the plugin’s native code.

Save this JavaScript into a file called alert.js, within a www subfolder of your plugin’s top-level folder.

How do I write the plugin’s native code?

Android

Based on the JavaScript interface, we must define a class called Alert in a Java source file called Alert.java.  Let’s take a look at the Alert.java source file:

package com.acme.plugin.alert;

import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class Alert extends CordovaPlugin {
  protected void pluginInitialize() {
  }

  public boolean execute(String action, JSONArray args, CallbackContext callbackContext)
      throws JSONException {
    if (action.equals("alert")) {
      alert(args.getString(
0), args.getString(1), args.getString(2), callbackContext);
      return true;
    }
    return false;
  }

  private synchronized void alert(final String title,
                                  final String message,
                                  final String buttonLabel,
                                  final CallbackContext callbackContext) {
    new AlertDialog.Builder(cordova.getActivity())
    .setTitle(title)
    .setMessage(message)
    .setCancelable(
false)
    .setNeutralButton(buttonLabel,
new AlertDialog.OnClickListener() {
      public void onClick(DialogInterface dialogInterface, int which) {
        dialogInterface.dismiss();
        callbackContext.sendPluginResult(
new PluginResult(PluginResult.Status.OK, 0));
      }
    })
    .create()
    .show();
  }
}

The Alert class should be part of a package that will be referenced in the manifest file, plugin.xml.

The Alert class must extend the CordovaPlugin class, the definition of which you can find here.

The Alert class must override the execute method, since this will be called each time the JavaScript cordova.exec method is called, providing the name of the plugin method, the input parameters and a callback context.  The execute method should return true if a valid action was passed in, otherwise false.

Once the code has completed, it should return a result and optional return parameters to the calling JavaScript by invoking the sendPluginResult method on the callbackContext object.   Returning a result of PluginResult.Status.OK will cause the JavaScript ‘success’ callback to be executed.  Any other result (apart from PluginResult.Status.NO_RESULT) will cause the JavaScript ‘failure’ callback to be executed.

In our plugin, a ‘success’ result is returned when the user taps the button on the popup dialog and a value of 0 is returned.

The Alert class may override the pluginInitialize method if any initialization logic is required when the plugin is first constructed.

Save this code into a file called Alert.java, within a src/android subfolder of your plugin’s top-level folder.

For more detailed information on aspects such a threading and event handling, refer to the Cordova page Android Plugins.

iOS

For our example, we must define a class called Alert in an Objective-C source file called Alert.m and corresponding header file Alert.h

Let’s take a look at the Alert.h header file:

#import <Cordova/CDV.h>

@interface Alert : CDVPlugin <UIAlertViewDelegate> {}
- (void)alert:(CDVInvokedUrlCommand*)command;
@end

@interface
MyAlertView : UIAlertView {}
@property (nonatomic, copy) NSString* callbackId;
@end

The Alert class must be a sub-class of CDVPlugin, the definition of which you can find here.

The Alert class must provide an alert method, since this will be called each time the plugin’s JavaScript alert method executes the cordova.exec method.

Save this code into a file called Alert.h, within a src/ios subfolder of your plugin’s top-level folder.

Let’s take a look at the Alert.m source file:

#import "Alert.h"

@implementation Alert
- (void)pluginInitialize
{
}

- (void)alert:(CDVInvokedUrlCommand*)command
{
  NSString* callbackId = command.callbackId;
  NSString* title = [command argumentAtIndex:
0];
  NSString* message = [command argumentAtIndex:
1];
  NSString* button = [command argumentAtIndex:
2];

  MyAlertView *alert = [[MyAlertView alloc]
                        initWithTitle:title
                        message:message
                        delegate:
self
                        cancelButtonTitle:button
                        otherButtonTitles:
nil];
                        alert.callbackId = callbackId;
  [alert show];
}

- (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
  MyAlertView* myAlertView = (MyAlertView*)alertView;
  CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
                             messageAsInt:
0];
  [
self.commandDelegate sendPluginResult:result callbackId:myAlertView.callbackId];
}
@end

@implementation
MyAlertView
@synthesize callbackId;
@end

The alert method receives the input parameters and a callback id.

Once the code has completed, it should return a result and optional return parameters to the calling JavaScript by invoking the sendPluginResult method on the commandDelegate object. Returning a result of CDVCommandStatus_OK will cause the JavaScript ‘success’ callback to be executed. Any other result will cause the JavaScript ‘failure’ callback to be executed.

In our plugin, a ‘success’ result is returned when the user taps the button on the popup dialog and a value of 0 is returned.

The Alert class may implement the pluginInitialize method if any initialization logic is required when the plugin is first constructed.

Save this code into a file called Alert.m, within a src/ios subfolder of your plugin’s top-level folder.

For more detailed information on aspects such a threading and event handling, refer to the Cordova page iOS Plugins.

How do I write the plugin’s manifest file (plugin.xml)?

The manifest file, called plugin.xml, is an XML document that defines the plugin and tells plugman how to incorporate the plugin into your MAF app (or any Cordova-based app) for each platform it supports.

The Cordova Plugin Specification is comprehensive, but we will focus on the manifest file used for our “Alert” plugin:

<?xml version="1.0" encoding="UTF-8"?>
<plugin 
xmlns="http://apache.org/cordova/ns/plugins/1.0"
        id="com.acme.plugin.alert"
        version="0.0.1">

  <name>
Alert</name>
  <description>
A Cordova plugin that displays an alert popup dialog</description>

  <engines>
    <engine
name="cordova" version=">=3.6.0" />
  </engines>

  <js-module
src="www/alert.js" name="Alert">
    <clobbers
target="Alert" />
  </js-module>

  <!-- android -->
  <platform name="android">
    <config-file
target="res/xml/config.xml"
parent="/*">
      <feature name="Alert">
        <param name="android-package" value="com.acme.plugin.alert.Alert" />
      </feature>
    </config-file>
    <source-file src="src/android/Alert.java" target-dir="src/com/acme/plugin/alert" />
  </platform>

  <!-- ios -->
  <platform name="ios">
    <config-file target="config.xml" parent="/*">
      <feature name="Alert">
        <param name="ios-package" value="Alert" />
      </feature>
    </config-file>
    <header-file src="src/ios/Alert.h" />
    <source-file src="src/ios/Alert.m" />
  </platform>

</plugin>

The plugin element must contain the plugin’s XML namespace (xmlns), id and version.

The name and description elements should always be defined.  If you intend to publish your plugin for public use, you should include additional elements such as those identifying the author, license, keywords and repository.

The child elements of the engines element define which version(s) of the Cordova framework the plugin supports.  Since this plugin has been developed based on Cordova 3.6.0 documentation and will be tested within a MAF 2.1.0 app, we should set the minimum required Cordova version to 3.6.0.

The JavaScript interface is defined by including a js-module tag for each JavaScript file.  Any file defined here is automatically copied into your MAF app and injected into any HTML using a <script> tag so that you don’t have to specifically add this tag yourself, or include the JavaScript as content in your AMX features.  The clobbers tag indicates that the module shall be inserted into the window object as Alert, so your MAF app code should call the JavaScript method Alert.alert to execute the plugin’s alert method.

If your plugin has a dependency on another Cordova plugin, you can define this using a dependency tag.  Since our plugin does not have any dependencies, there is no such tag defined.

The platform tag is used to define the platforms that are supported by the plugin.  Within each platform tag, you specify the native source files, changes required to configuration files, any additional native resources or frameworks and any platform-specific JavaScript files.

For Android, it is important to specify the full package name of the plugin as the value for the android-package parameter, for example “com.acme.plugin.alert.Alert”, and to specify the target-dir for any source files (since this indicates the location to which the source file should be copied and it must match the package name), for example “src/com/acme/plugin/alert”.

Save this XML into a file called plugin.xml, within your plugin’s top-level folder.

Conclusion

We have now developed a custom Cordova plugin that can be incorporated into a MAF 2.1.0 app, or any app based on Cordova 3.6.0 or above.  In a follow-up post [Edit: here], I’ll describe how to integrate this plugin into a MAF app.

For developers looking for more detailed information about how to create a Cordova plugin, refer to the Cordova Plugin Development Guide, which contains specific guides for each native platform.

Monday Oct 13, 2014

Updated MAF Academy Course

Now available! The FREE online Oracle Developer Academy course, Developing Applications with Oracle Mobile Application Framework (MAF) has just been re-released to include all you need to know about securing a MAF application. Carve out two hours of time in your day* with this interactive, engaging online course and get a comprehensive education on Oracle MAF. You'll master the nuts and bolts of the architecture, be able to design and develop a MAF application with web services, integrate with native device capabilities, and secure the app and the services it accesses.

*or multiple days – the course player remembers where you were so you can easily pick up where you left off if necessary

http://bit.ly/MAFCourse

Note: The course requires the following:

Flash Player 10 or later, and one of the following browsers:

Internet Explorer 6 and later, Firefox 1.x and later, Google Chrome, Opera 9.5 and later, Safari 3 and later.

Wednesday Mar 19, 2014

Re-Enabling USB Debugging in Android 4.3

Having trouble deploying to your Android device after the recent upgrade to Android 4.3? I have the answer for you![Read More]

Thursday Jan 30, 2014

New Enhancement Released for iOS7+Android Native Look & Feel and Xcode 5 Support

Hi, everyone:

It has been a while since a blog article was published last.  While the bloggers took a little time off during the holidays, the product development team has been hard at work to release a new ADF Mobile "Patch" (Patch 5) that adds some significant new functionality, in addition to numerous bug fixes.  You can find the release note for the new patch here.

To download, you would simply need to start JDeveloper 11.1.2.4, and select menu item Help->Check For Updates->Official Oracle Extension and Updates, and select ADF Mobile.  It should have version number 11.1.2.4.39.64.62.

For any customers who have received one off patch through the BLR process, please note that one-off patches obtained through the BLR process are not automatically rolled into this overall patch.  Therefore, please work with your Oracle Support representative to request a new BLR patch for this latest patch.

We will focus on two specific enhancements in this article:

  • New iOS7 and Android Native Look and Feel (mobileAlta Skin)
  • Xcode 5 support

mobileAlta Skin for Native iOS 7 and Android Look and Feel

With iOS7, Apple introduced a new look and feel that gives the iOS user interface a cleaner and more stream-lined look and feel  Gone are a lot of the three-dimensional and gradient effect in the user interface and icons, and replaced by simple and modern "skins" to complement the iOS device hardware.  With the latest ADF Mobile patch, we introduced a new mobileAlta skin that, when running on iOS devices, would closely match the standard look and feel of the iOS7 look and feel.

Here is what the HR application looks like with the old (mobileFusionFX) skin:

The updated skin on the iPhone now looks like:

As for Android, as many of you noticed, the look and feel of the ADF Mobile skin has always more resembled that of the iOS than Android.  Android did not introduce a more formal UI standard until Android 4.x, and is still evolving.  Nevertheless, it has evolved to a look and feel that's very distinctive from the iOS UI.  Many of you have reported this issue to us, and we listened.  When rendered on Android devices, mobileAlta skin would also introduce a more Android look and feel. 

Here is the new Android native look-and-feel:

How to Apply the New Look and Feel

To apply the new look and feel, you simply need to edit the adfmf-config.xml file, and change the skin family as follows:

<skin-family>mobileFusionFX</skin-family>

to 

<skin-family>mobileAlta</skin-family>

 That's it.  If you have over-written out of box ADF Mobile CSS styles or added your own CSS classes, please test the application thoroughly and ensure the new style is compatible with the customized classes.  Color, coordinates, and icon references may have changed, and thorough UI testing is needed to ensure there is no unexpected side-effects.


Xcode 5 support

With the latest patch, you will need to use Xcode5 to deploy the iOS version of the ADF Mobile application.  Only Xcode 5 is supported.  This complies with the latest Apple guideline due to take effect on February 1, 2014, where all apps submitted to the AppStore must be compiled with Xcode5 and optimized for iOS 7.

Update to the latest Xcode requires downloading and installing Xcode 5, and then configure the location of the Xcode 5 in JDeveloper - Tools - Preferences - ADF Mobile.  

While JDeveloper's integration with Xcode 5 did not change, Xcode 5 itself introduced a number of changes.  The most prominent change is around how Provisioning Profiles and Developer Accounts are managed.  Previously Provisioning Profiles and Developer Accounts are accessed and managed through the Organizer utility that's accessed through menu item Windows - Organizer.  Now it is managed through the Xcode Preferences dialog box accessed with menu item Xcode - Preferences, and then select the "Accounts" tab.  You will then see your Apple Developer account information displayed.  When you click on the "View Details..." button, you will then see the list of certificates and provisioning profiles assigned to you.  Therefore, when you need to verify the Provisioning Profiles, you will need to access this dialog box.  You can still double click on the .mobileprovision file to install the Provisioning Profiles, but must use this functionality to see it.

Please consult Apple documentation for details of how to use this new functionality.

There had been some blog article published that references the old "Organizer" functionality.  These blog articles would be updated over time.  

Thanks,

ADF Mobile Product Management Team 

Friday Sep 06, 2013

Modify Android Device Permissions and Form Factors Support for Google Play

Hi, everyone:

This is a re-post of a forum reply - I hope you will find it useful. 

Recently Google Play made a number of changes, so that it is much more strict in terms of Android device/form factor support.  For example, if you simply published an ADF Mobile application on Google Play and then try to download the app from an Android tablet, you may see "This Application is not compatible with your device".  This is true even if you have created tablet-optimized screen in your ADF Mobile application.  This is because Google Play now inspects the content of AndroidManifest.xml file for supported screen form factors. Google Play uses the "Supports Screen"  element in the AndroidManifest.xml file to determine what devices/form factors are supported.  Summary of the supported form factors and densities are described here: Supporting Multiple Screens | Android Developers.  ADF Mobile supports different screen density display already in the deployment profile, and you don't really have to do anything there other than specified the required icon resources.  As for screen size, you would need to add the "Supports Screen" element in the manifest xml file template for the screen size you want to support.  For example, if you want to add to support all devices:

<supports-screens android:smallScreens="true"

                  android:normalScreens="true"

                  android:largeScreens="true"

                  android:xlargeScreens="true"/>

The Android site would give you definitions of what these different sizes mean. Basically, Normal = most of the smartphones, Large = small tablets with ~7 inch display, and xLarge = ~10 inch display.  

Furthermore, when you download the ADF Mobile application from Google Play and attempt to install it, you will also see the application requesting all sorts of device permissions, regardless of whether you are actually using that device service or not.  By default, ADF Mobile declares all the device permissions that may potentially be required by the framework.  Therefore, for example, even if the app does not access camera, the AndroidManifest.xml file will still contain references to camera permissions.  Ability to selectively choose the correct hardware permissions is planned for the next release of ADF Mobile.  In the meantime, you can modify the AndroidManifest.xml file template in the ADF Mobile extension as the workaround.

Here is what you will need to do to update the AndroidManifest.xml template:

  1. Navigate to file <JDev Install>/JDeveloper/JDev/Extensions/oracle.adf.mobile/Android/Oracle_ADFmf_Framework.zip
  2. Make a backup copy of the zip file
  3. Unzip the file, which would extract all files into a "framework" directory
  4. Navigate to framework/template/AndroidManifest.template.xml file
  5. Use a text editor to modify the device permissions, as well as adding the supports screen element as referenced above.  Check the list of device permissions and their definitions here for device permission modification, and please ensure you remove only the ones that you don't need.  Note that some permissions are required for framework to operate.  Please see the example below on what permissions are needed - the example below shows permissions required by the framework + GPS access.
  6. Zip up the framework directory and name zip file as Oracle_ADFmf_Framework.zip, replacing the old one.  Please make sure the zip file has the exact same directory structure as before.
  7. In JDeveloper, before deploying the application again, please do a "Clean All" to clean out all old deployment artifacts.
  8. Deploy the app - the app should now be visible for tablets, as well as has the appropriate permissions.
  9. Upload the app to Google Play - no additional actions are needed in Google Play around supported form factors and device permissions.

Now Android deployment process does not do any extra validation for what device permissions are actually needed, so it's up to you to ensure these are set correctly.  Also, you do not need to change the minimum and target API levels in the deployment profile - you would change it only if you want to restrict the application to support certain Android versions.  

Below is an example of the Android Manifest template file where device permissions have been striped down to only include GPS access and what the framework needs, but screen form factors have been specified to support all smartphone and tablet devices. 

Thanks,

Joe Huang 

 <?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

  package="$$app.package$$">

  <uses-sdk/>


  <application android:icon="$$app.icon$$"

    android:label="$$app.name$$" android:name="oracle.adfmf.Application">

    <activity android:name="oracle.adfmf.Container" android:label="$$app.label$$" android:theme="@android:style/Theme.NoTitleBar"

              android:configChanges="$$app.configChanges$$" android:windowSoftInputMode="adjustPan" >

      <intent-filter>

        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />

      </intent-filter>

    </activity>

    <activity android:name="oracle.adfmf.AMXActivity" android:theme="@android:style/Theme.NoTitleBar" android:windowSoftInputMode="adjustPan"></activity>

    <activity android:name="oracle.adfmf.RemoteURLActivity" android:theme="@android:style/Theme.NoTitleBar" android:windowSoftInputMode="adjustPan"></activity>

    <activity android:name="oracle.adfmf.LocalHTMLActivity" android:theme="@android:style/Theme.NoTitleBar" android:windowSoftInputMode="adjustPan"></activity>

    <activity android:name="oracle.adfmf.phonegap.AdfSpringboardPhoneGapActivity" android:theme="@android:style/Theme.NoTitleBar" android:windowSoftInputMode="adjustPan"></activity>

    <activity android:name="oracle.adfmf.SettingsActivity" android:theme="@android:style/Theme.NoTitleBar" android:windowSoftInputMode="adjustPan"></activity>

    <activity android:name="oracle.adfmf.navbar.MoreTabActivity" android:theme="@android:style/Theme.NoTitleBar" android:windowSoftInputMode="adjustPan"></activity> 

    <activity android:name="oracle.adfmf.BlankActivity" android:theme="@android:style/Theme.NoTitleBar" android:windowSoftInputMode="adjustPan"></activity>

  </application>

  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

  <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />

  <uses-permission android:name="android.permission.READ_PHONE_STATE" />

  <uses-permission android:name="android.permission.INTERNET" />

   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />   

  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />


<supports-screens android:smallScreens="true"

                  android:normalScreens="true"

                  android:largeScreens="true"

                  android:xlargeScreens="true"/>

</manifest>

Sunday Jun 09, 2013

Push Notification Support and (Provide Your Own?) Provider (Part 1)

Updated Aug 2, 2013 - see the paragraph below in red for the additions. 

Note: This is the first part of a multi-part series on Push Notification support.  First part focus on how Push Notification works, and discuss implementation options.  Future parts would include sample Push Notification applications and specific examples/use cases. 

ADF Mobile 11.1.2.4 adds Push Notification Support to ADF Mobile.  Push Notification I am sure is a familiar concept for everyone - at one time or another we have all received Push Notification from popular mobile apps such as Facebook, Email, Twitter, etc.  Implementing Push Notification has been made much easier with ADF Mobile 11.1.2.4, especially since it provides cross-platform support for notifications.  However, client-side code is only half of the story.  Below are some details around how Push Notification works, how you would implement client side code, and what you should implement on the server side.

What Is Push Notification?

Generally Push Notification mechanisms requires mobile platform vendors to host some server-side services that can deliver notifications to devices, as well as some services in the mobile OS to process the notifications.  ADF Mobile integrates with device-native push notification mechanisms, and they are:

For details of how these mechanisms work, as well as re-requisite such as setting up SSL certificates or accounts, please click on the link above and navigate to the respective websites.

How Does It Work?

There are four primary components in delivering Push Notifications to end users:

  • ADF Mobile Application: The framework has added hooks that would allow an ADF Mobile app to register with the Push Notification Services, launched from device's push notifications and messages, and start special application logic to process the Push Notification and its message content.
  • Mobile Device Operating System: Mobile Device Operating Systems brokers push notification registration and requests between the server-side services and mobile applications, as well as providing the critical visual cue for inbound messages.  For a demo of what push notification looks like, please follow this link to view the 11.1.2.4 New Feature video.
    • The iOS operating system allows end users to configure notification alerts as banner on top of the screen, an alert popup message, or turn off visual notification altogether.  All messages would be queued and listed in the Notification Center.  User would be able to launch the application from any of these mechanisms.
    • The Android operating system simply displays the push notification in the device's Notification Manager, and user can launch the ADF Mobile application from the notification entry.
  • Push Notification Services: Apple Push Notification Services and Google Cloud Messaging service hands device/application registration requests from the devices, as well as acts as a gateway for the Provider to send the notification to.
  • Provider (Application Server): Provider is a set of server-side services that developer would create to store device token and user information, as well as initiating the push notification request.  At this time, ADF Mobile does not provide out of box server-side Provider services, but this article does provide a very simple sample of the Provider service.

The following is an event diagram of the entire Push Notification lifecycle:


There are a few steps during the development of the ADF Mobile application that would enable the app to leverage the Push Notification services.  These steps are described in the next section.  For now, assuming that the application has been developed to enable push notifications: 

  1. When an ADF Mobile application starts up, it would initiate a registration request with push notification services.
  2. The registration request goes to the mobile device operating system.
  3. And the operating system pass the request to the Apple Push Notification or Google Cloud Messaging Services.
  4. The Push Notification service would pass a token (a string consisted of alpha-numeric characters) back that would uniquely identify the ADF Mobile application and the device.
  5. This token is then received by the ADF Mobile application.
  6. When the token is received, the onOpen method in the Application Lifecycle event listener is invoked.
  7. In the onOpen Method, you would typically register the device with your Provider application.  In order to properly support Push Notification functionality, the Provider application would typically need the following information from the ADF Mobile application:
    • The Token that uniquely identifies a device and the ADF Mobile application
    • Mobile operating system type if the application supports multiple operating systems
    • User ID if Provider needs to push notifications to specific user(s)
At this point, the Provider application has enough information to send notifications as needed.  You the developer of course would decide when you want the Provider application to push notifications, which typically correspond to some business events that you want to push to the end users.  When the Provider server application needs to push notifications to the ADF Mobile application running on users' devices, the following occurs:
  1. The Provider send the notification payload, Push Notification certificate, and the token to the push notification service.
  2. If the SSL Certificate is valid, the request would be accepted by the Push Notification service.  The Push Notification service would then verify the identity of the device and validity of the application with the token.
  3. If Push Notification service is able to verify the device and application, the Push Notification with its payload is sent to the device.  At this point, an alert or a message in the notification center of the device is displayed if the ADF Mobile application is running in the background or not running.
  4. When user clicks on the alert or message, the ADF Mobile application is then brought to the foreground if it is running in the background, or launched if it is not running.  
  5. At this point, the onMessage method in the application lifecycle event listener is called.  The onMessage method is where you would add the logic in the ADF Mobile application to handle the notification.  For details of the Push Notification behavior under different application state, please refer to the Push Notification chapter in the ADF Mobile Developer Guide.

How Do I Implement Push Notification?

The following are the key steps to follow and components to implement

  • Register with iOS and Android Developer Programs.  
  • Set up developer account for Push Notification development
    • For iOS, there are Application IDs, Provisioning Profiles, and SSL Certificates that need to be set up first.  Please consult Apple Developer Portal for details.
    • For Android, you would need to create a Project and obtain an API key from Google.  Instructions are listed here.  You will need these information when you create the request for Google Cloud Messaging service.
  • Implement client side (in ADF Mobile app) and server side (Provider) code.  

Client Side Code

In the ADF Mobile application, you will need:

  • Specify the Application Lifecycle Listener Implementation class in the adfmf-applications.xml file.  This allows you to invoke custom Java code during different phases of the application lifecycle.  
  • Add the PushNotificationConfig interface to the Application Lifecycle Listener class declaration:

    public class LifeCycleListenerImpl implements LifeCycleListener, PushNotificationConfig

  • In the start method of the Application Lifecycle Listener class, you would need to instantiate an eventSource object for the application, and add a listener to listen on native push notification events:

    EventSource evtSource = EventSourceFactory.getEventSource(NativePushNotificationEventSource.NATIVE_PUSH_NOTIFICATION_REMOTE_EVENT_SOURCE_NAME);
    evtSource.addListener(new PushNotificationListener());

    where PushNotificationListener is a class you would implement separately that handles Push Notification. 

  • Override and implement the getNotificationStyle and getSourceAuthorizationID methods of the PushNotificationConfig interface.  getNotificationStyle allows you to set the alert styles for an iOS application, and getSourceAuthorizationID allows you to enter the Google Project ID of the account where you would want to allow to send push notifications to ADF Mobile apps on Android.  Like this:
  public long getNotificationStyle () {

// Enable all possible alert styles

       return PushNotificationConfig.NOTIFICATION_STYLE_ALERT | PushNotificationConfig.NOTIFICATION_STYLE_BADGE | 
PushNotificationConfig.NOTIFICATION_STYLE_BADGE;
    }

 You will need to declare a Public String variable in your Application LifeCycleListenerImpl class and set it to the Google Project ID, and add this method below as well:

  public String getSourceAuthorizationId() {
          return SENDERID;
    }
  • Next, you would need to implement the listener class.  In this example, it's called PushNotificationListener but of course you can give it any name.  However, it must reside in the same project as the Application Lifecycle Event Listener class, and implements the EventListener interface.  Furthermore, the class would implement three methods invoked during the Push Notification event lifecycle - onOpen, onMessage, and onError.  As explained previously, onOpen is called during the Push Notification registration process, and onMessage is invoked when the ADF Mobile application is invoked and brought to the foreground.  onError is called when any error is encountered in the Push Notification lifecycle.  However, at the time of this blog, there is a bug where Push Notification errors would not properly invoke the onError method.  This fix is targeted for 11.1.2.4.2 (11.1.2.4 Patch 2).  Below is a skeleton of this class:
import oracle.adfmf.framework.api.AdfmfJavaUtilities;
import oracle.adfmf.framework.event.Event;
import oracle.adfmf.framework.event.EventListener;
import oracle.adfmf.framework.exception.AdfException;

public class PushNotificationListener implements EventListener {
    public PushNotificationListener() {
        super();
    }

    public void onMessage(Event event) {

//Invoked when a Push Notification arrives.

    }

    public void onError(AdfException adfException) {

//Invoked when any error is encountered in the Push Notification Lifecycle (not working at the time of blog publication

    }

    public void onOpen(String token) {

//Invoked during the Push Notification registration process. The parameter "token" contains the token received from APNs or GCMs that uniquely identifies a specific device-application combination.

    }
}

                                              Now let's look at what you need to implement in the onOpen and onMessage methods more closely.

                                              onOpen Method

                                              onOpen method is invoked when the ADF Mobile application successfully registered the device/application with the Push Notification service (APNs or GCMs), and the parameter "token" contains a string from these services.  The length of the token string is different between APNs and GCMs, but in general it uniquely identifies a device and application combination.  This is essential for you to be able to push message to a specific ADF Mobile app running on a particular device.  At this point, you can either send the token to the server-side Provider service, or simply save the token in an applicationScope variable for later use.  

                                              The server-side Provider service would typically need the following information:

                                              • Token, in order to send messages to a particular ADF Mobile app running on a specific device.
                                              • Mobile OS, in order to target messages to APNs or GCMs for this device.
                                              • User ID, as needed by the mobile application logic.  For example, push a notification to user X when a business event relevant to user X occurs.

                                              Of course you are free to choose any of then supported interfaces to send these information to the server-side Provider - via SOAP or REST services.   

                                              One key item to note - Mobile OS (part of Device Info Data Control) and User ID are not available inside the Application Lifecycle Event Listener, before the Listener is executed before Cordova and Security modules are loaded.  Therefore, you will need to send these information inside a feature - a logical place would be in the activate method of the Feature Lifecycle Event Listener class for the default and secured feature.  You can pass the token string into the Feature Lifecycle Event Listener via an applicationScope variable.

                                              Furthermore, if you choose to secure the Provider's device registration service, you must implement the registration logic within a secured feature that authenticates against the Provider's authentication services.  

                                              onMessage Method

                                              onMessage method is always invoked - either when ADF Mobile application is started/brought from the background when user clicks on the notification message/item, or automatically if the application is already running in the foreground as the active application.  The method is invoked with the payload of the notification - an Java object class called Event.  Java Doc for the Event class is missing from the initial version of 11.1.2.4 of ADF Mobile, and is still incomplete.  The most recent version of the Java Doc can be found here.  The Java Doc is missing explanation for the following two methods for the Event object:

                                              • getPayload: as the name implies, this returns the payload portion of the Push Notification message as a string.  
                                              • isStartTriggered: this returns a boolean indicating whether or not the ADF Mobile application is launched as the result of user acting on the Push Notification popup or an item in the notification center.  This is useful when you need to know whether the start (and therefore onOpen) methods of the Application Lifecycle Event Listener was executed prior to onMessage getting called.

                                              In the Application Lifecycle Listener - onMessage method, you can parse the event payload and invoke additional application logic accordingly.  One of the most common visual cue for Push Notification on iOS devices is through "badging", which adds a red circle containing numbers next to the application icon on the device.   For details of the new badging API, please consult this section in the ADF Mobile Developer Guide.  Another common scenario here is you may want to navigate to a particular AMX page in a particular feature, retrieve data from the server, and display a particular record based on the notification payload.  The high level logic to do this is:

                                              • First, ensure the Provider passes data object type and some unique Row ID in the message payload
                                              • In the onMessage method of the Application Lifecycle Event listener, parse the notification payload, and save Data Object type and Row ID into applicationScope variables.
                                              • Navigate to a particular feature based on data type
                                              • In the default task in the feature's AMX task flow, determine if the feature is invoked as the result of a push notification using, for example, a Router.
                                              • Router can then navigate to an AMX page that can retrieve the data from the server and perform a setCurrentRow based on the unique RowID from the applicationScope variable

                                              Server-side Code

                                              The server-side code, or commonly referenced as the "Provider", will need to support at least these two services:

                                              • Token and User Registration Service: this service is invoked when the ADF Mobile application sends the token and any other relevant information to the Provider.  You may use expose any of the supported networking interface (SOAP or REST), as well as secure this registration service.  You will typically want to store the device token and other relevant information in a data store so they can be used later.
                                              • Push Notification Delivery Service: this service is invoked by your server-side logic when Push Notification needs to be sent to one or more devices.  This service needs to send the token, SSL Certificate, and the Notification payload to APNs or GCMs.  The format of the Push Notification call can be found in Apple and Google developer sites.

                                              Push Notification provides a channel to push notifications to clients, and the notification payload can contain some limited set of parameter-value pairs that can be parsed by the ADF Mobile app.  However, it is not intended to send the business data nor used to "synchronize" data between the client and the server.  It is intended to pass certain key values that the ADF Mobile application would need to, say, display a particular piece of data for which the notification is intended for. 

                                              Furthermore, neither Apple nor Google guarantee the delivery or the order of the Push Notification messages.  Therefore, treat each of the push messages as a self-contained set of operations, rather than relying on multiple messages to perform some business logic. 

                                              Future Blog Entries on Push Notification

                                              Additional blog entries on Push Notification will be published to cover additional details around Provider implementation, sample client-side implementations, as well as specific integrations with Fusion Middleware and Application backends.  Please return to the ADF Mobile team blog and other related blog entires/ADF Insider sites for future articles.  If you have further questions on this topic, please contact us through the ADF/JDeveloper Forum, or one of the PMs for ADF.

                                              Thanks,

                                              Joe Huang 

                                              About

                                              This blog is is dedicated to announcements,tips and tricks and other items related to developing, integrating, securing, and managing mobile applications using Oracle's Mobile Platform. It is created and maintained by the Oracle Mobile product development team.

                                              Archive of past entries

                                              Even More Mobile Development Blogs

                                              Oracle A-Team Site - Mobile Related Entries

                                              Code samples from the Community

                                              Fusion Middleware Blogs

                                              Search

                                              Archives
                                              « April 2016
                                              SunMonTueWedThuFriSat
                                                   
                                              1
                                              2
                                              3
                                              4
                                              5
                                              6
                                              8
                                              9
                                              10
                                              12
                                              13
                                              14
                                              15
                                              16
                                              17
                                              18
                                              20
                                              21
                                              22
                                              23
                                              24
                                              25
                                              26
                                              27
                                              28
                                              29
                                              30
                                                     
                                              Today