Tuesday Apr 07, 2015

How to implement iBeacon in MAF 2.1.1

Want to alert your customer to a special loyalty discount offer when they enter the menswear department in one of your retail stores?  Want to present information about a famous artist’s life when a visitor to your museum nears one of the artist’s paintings?  These are just two of the many real-world scenarios made possible by the use of iBeacon technology.

This blog post provides an introduction to iBeacon technology and a description of how to build two different MAF apps – one that pretends to be an iBeacon and another that detects iBeacons and uses the local notifications functionality provided in MAF 2.1.1 to inform the user, even when the app isn’t even running.

To deploy these apps to iOS devices you will need an iOS developer account.

What is a beacon?

A beacon is a device that is intentionally conspicuous to draw attention to a location, such as a lighthouse sitting on the edge of a cliff. 

In in the Internet of Things (or IoT), a beacon is a small electronic device that transmits a regular radio signal according to the Bluetooth v4 Low Energy spec (otherwise known as “BLE”).  A beacon typically does no more than advertise its existence by transmitting a unique identifier and can last for months on a single cell battery.

Any BLE-enabled device, such as a modern smartphone, can detect a beacon by listening for BLE-based transmissions.

Whilst the possibilities appear endless, typical applications for beacons currently include retail stores, exhibition halls, museums, places of employment and homes, where users can be alerted to information pertaining to their current location within a building.

What is (an) iBeacon?

iBeacon is a technology introduced by Apple in iOS 7 that defines a standard for how a beacon identifies itself (or “advertises”) in its BLE transmissions.  Any beacon that implements this standard can be called an iBeacon.

Most beacon manufacturers implement the iBeacon standard by default, whilst some can also be configured to use their own proprietary protocol.  It’s also possible to configure a post-2012 iOS device, or Mac running OS X Mavericks (not Yosemite), to act as an iBeacon.

Whilst the iBeacon technology is included in the iOS Core Location framework since iOS 7, any BLE-enabled device can detect iBeacons and various libraries exist for use on devices running Android 4.3 or above.

How does iBeacon work?

The iBeacon standard defines three properties that determine a beacon’s identity:

  • A proximity UUID (universally unique identifier), which is a 128-bit value that uniquely identifies one or more beacons as a certain type or from a certain organization.
  • A major value, which is a 16-bit unsigned integer that can be used to group related beacons that have the same proximity UUID.
  • A minor value, which is a 16-bit unsigned integer that differentiates beacons with the same proximity UUID and major value.

Every iBeacon must advertise a proximity UUID, whilst the advertisement of major and minor values is optional.  All beacon manufacturers allow customers to modify these values on their purchased beacons.

A typical iBeacon deployment (e.g. within a retail store chain) would see all beacons advertising the same proximity UUID, those in a particular location (e.g. a single store) advertising the same major value, and the minor values being used to uniquely identify each beacon.

When using iOS Location Services, an app wishing to detect iBeacons must start by monitoring for an iBeacon region.  A region is defined by the proximity UUID and optionally major and minor values, and can therefore represent one or more beacons.  Consider an app for a retail store chain that monitors for a region defined only by the proximity UUID.  This app will be notified when any of the retain chain’s beacons are detected.  Alternatively, if the region being monitored is defined by proximity UUID and major value, the app might only be notified when a beacon from a particular store (represented by the major value) is detected.

Once an app has been launched and the user has given permission for the app to monitor for beacons, the app will be notified when the device enters a beacon region that is being monitored, even if the app is not running and even if the device has been restarted.  If the app is not running, iOS launches the app for a short period (around 10 seconds), allowing the app to receive and react to the event.  Typically, the app fires an immediate local notification to notify the user.

When a user’s device enters a beacon region, the app can start ranging for individual beacons within the region to determine its relative proximity to each beacon.  This is used to determine when the user is in the immediate proximity of a particular beacon, so that information related to that beacon can be displayed to the user.  The relative proximity is an approximation that can be affected by physical objects including walls, water and the human body.  Most beacon manufacturers allow customers to modify the transmission power and advertising interval on their purchased beacons, so as to fine-tune the distance at which the relative proximity is considered ‘immediate’.

When iOS Location Services determines that it can no longer detect any beacons in the region, it notifies the app that the beacon region has been exited.  In practice, I’ve found that this takes around 30 seconds, but some bloggers have reported much longer times.

Libraries exist for Android that provide similar functionality to iOS Location Services, but I have not explored these (yet).

How to create an iBeacon client app

If you are an existing MAF app developer, you can follow these steps to create a MAF app that detects iBeacons.

Step 1 – Find a suitable Cordova plugin

The first thing required is a Cordova plugin to provide access to the native iBeacon functionality on iOS and mimic the same on Android.  Typing “beacon” into the Cordova plugin repository produces a few results, with the most downloaded being this plugin.  According to the readme, this plugin is suitable for both iOS and Android apps and provides the ability to fake an iBeacon advertisement on iOS devices.

Open its GitHub page and download it by clicking on the Download ZIP button.

Step 2 – Create a MAF app and add the plugin

Open JDeveloper with MAF 2.1.1 installed, create a new MAF app called BeaconDemo.

Open the maf-application.xml file located in Application Resources > Descriptors > ADF META-INF and select the Plugins tab.  Click on the Add Plugin button under Additional Plugins, browse to the location of the downloaded plugin and click on Select.  The plugin is now added to your app.

Save all your changes.

Step 3 – Create a JavaScript interface file

The Cordova plugin interface is written in JavaScript, so we need to write some JavaScript that calls this interface.  We will also include some of the business logic in JavaScript.

Create a new JavaScript file by selecting the ViewController node in the Projects window, right click, select New > JavaScript File and enter "beacon.js" as the filename.  Enter the following code into the empty JavaScript file:

(function() {

  // Our region identifier
  var uuid = "0AC59CA4-DFA6-442C-8C65-22247851344C";
  var beaconRegion;

  function initialise() {
    beaconRegion = createRegion();
    var delegate = new cordova.plugins.locationManager.Delegate();

    // Callback for enter/exit region whilst monitoring
    delegate.didDetermineStateForRegion = function (pluginResult) {
      var options;
      if (pluginResult.state == "CLRegionStateInside") {
        // Fire local notification and start ranging from there so that ranging only
        // starts if the app is in the foreground, or the user taps on the notification
        options = {"alert":"Welcome! Thank you for entering the " +
                     pluginResult.region.identifier + " region.",
                   "sound":"SYSTEM_DEFAULT",
                   "vibration":"SYSTEM_DEFAULT",
                   "payload":{"id":pluginResult.region.identifier, "inside":true}};
        adf.mf.api.localnotification.add(options,  function() {}, function() {});
      } else if (pluginResult.state == "CLRegionStateOutside") {
        // Stop ranging immediately
        stopRanging();

        // Fire local notification
        options = {"alert":"Goodbye. Thank you for visiting the " +
                     pluginResult.region.identifier + " region.",
                   "sound":"SYSTEM_DEFAULT",
                   "vibration":"SYSTEM_DEFAULT",
                   "payload":{"id":pluginResult.region.identifier, "inside":false}};
        adf.mf.api.localnotification.add(options,  function() {}, function() {});
      }
    };

    // Callback for ranging region
    delegate.didRangeBeaconsInRegion = function(pluginResult) {
      var beacons = pluginResult.beacons;
      for (var i = 0; i < beacons.length; i++) {
        // Add to list of beacons in BeaconManager
        adf.mf.api.invokeMethod("mobile.BeaconManager", "beaconRanged",
                                beacons[i].uuid, beacons[i].major,
                                beacons[i].minor, beacons[i].proximity,
                                function() {}, function() {});
      }
    };

    // Set delegate
    cordova.plugins.locationManager.setDelegate(delegate);

    // Required in iOS 8+
    cordova.plugins.locationManager.requestAlwaysAuthorization();

    // Start monitoring by default each time the app is launched
    startMonitoring();
  }

  function createRegion() {
    // Monitor any beacons in this UUID-based region
    return new cordova.plugins.locationManager.BeaconRegion("FakeBeacon", uuid);
  }

  // Callable externally
  startMonitoring = function() {
    cordova.plugins.locationManager.startMonitoringForRegion(
      beaconRegion).fail(console.error).done();
    adf.mf.api.setValue({"name": "#{applicationScope.region}",
                         "value": beaconRegion.uuid},
                        function() {}, function() {});
    adf.mf.api.setValue({"name": "#{applicationScope.monitoring}", "value": true},
                        function() {}, function() {});
  }

  // Callable externally
  stopMonitoring = function() {
    stopRanging(); // Must stop ranging first
    cordova.plugins.locationManager.stopMonitoringForRegion(
      beaconRegion).fail(console.error).done();
    adf.mf.api.setValue({"name": "#{applicationScope.monitoring}", "value": false},
                        function() {}, function() {});
  }

  // Callable externally
  startRanging = function() {
    cordova.plugins.locationManager.startRangingBeaconsInRegion(
      beaconRegion).fail(console.error).done();
    adf.mf.api.setValue({"name": "#{applicationScope.ranging}", "value": true},
                        function() {}, function() {});
  }

  // Callable externally
  stopRanging = function() {
    cordova.plugins.locationManager.stopRangingBeaconsInRegion(
      beaconRegion).fail(console.error).done();
    adf.mf.api.setValue({"name": "#{applicationScope.ranging}", "value": false},
                        function() {}, function() {});
    // Clear list of beacons
    adf.mf.api.invokeMethod("mobile.BeaconManager", "clearBeacons",
                            function() {}, function() {});
  }

  document.addEventListener("showpagecomplete", initialise, false);

}) ();

Let’s take a look at what this code does:

  • There are four externally callable functions – startMonitoring, stopMonitoring, startRanging and stopRanging, each of which invokes the corresponding plugin API.
  • There are two internal functions – initialize, which is invoked when the framework loads the feature, and createRegion, which creates a new beacon region based on the uuid variable defined at the top of the file. You should enter your iBeacons’ UUID here, or generate your own.  The beacon region identifier “FakeBeacon” can be any string you wish.
  • The initialize function creates and registers two callback functions via a delegate, requests authorization from the user to monitor for beacons at all times, and starts monitoring the defined beacon region.
  • The delegate.didDetermineStateForRegion callback function is invoked when the beacon region is entered or exited, even if the app was not running.  When this happens, it schedules an immediate local notification. To avoid unnecessarily ranging when the user is not actively using this app, ranging is not started here, but within the local notification handler.  For the same reason, ranging is stopped as soon as the device exits the beacon region.
  • The delegate.didRangeBeaconsInRegion callback is invoked when ranging has detected one or more iBeacons.  For each identified beacon, proximity, RSSI and accuracy values will be returned.  We will manage the list of beacons in a Java bean, so the bean’s beaconRanged method is invoked here.
  • Three application-scope variables are used to maintain the monitoring state, ranging state and the identity of the region being monitored or ranged.
  • The stopRanging function also clears the list of beacons by calling the clearBeacons method of the Java bean.

Save all your changes.

Step 4 – Create a local notification handler

There are a couple of steps required to ensure our app can receive local notifications.

Open the LifecycleListenerImpl.java file located in Projects > ApplicationController > application and modify the start method as follows to register an EventListener for local notifications:

public void start() {
  EventSource evtSource = EventSourceFactory.getEventSource(
    EventSourceFactory.NATIVE_LOCAL_NOTIFICATION_EVENT_SOURCE_NAME);
  evtSource.addListener(
new NativeLocalNotificationListener());
}

Create a new Java file by selecting the ApplicationController node in the Projects window, right click, select New > Java Class and enter "NativeLocalNotificationListener" as the name.  Enter the following code into the empty Java file:

package application;

import java.util.HashMap;
import oracle.adfmf.framework.api.AdfmfContainerUtilities;
import oracle.adfmf.framework.event.Event;
import oracle.adfmf.framework.event.EventListener;
import oracle.adfmf.framework.event.NativeLocalNotificationEvent;
import oracle.adfmf.framework.exception.AdfException;
import oracle.adfmf.json.JSONException;
import oracle.adfmf.json.JSONObject;

public class NativeLocalNotificationListener implements EventListener {

  public NativeLocalNotificationListener() {
  }

  public
void onMessage(Event event) {
    String payload = event.getPayload();

    if (!"{}".equals(payload)) {
      try {
        HashMap<String, Object> payloadMap =
          ((NativeLocalNotificationEvent)event).getPayloadObject();
        JSONObject jsonPayload = (JSONObject)payloadMap.get(
"payload");
        boolean inside = jsonPayload.getBoolean("inside");

        // If inside, start ranging (If outside, ranging already has been stopped)
        if (inside) {
          AdfmfContainerUtilities.invokeContainerJavaScriptFunction(
"Beacon",
                                                                    "startRanging",
                                                                    new Object[] {});
        }
      } 
catch (JSONException e) {
        System.out.println(
"Local Notification JSON error: " + e.getMessage());
      }
    }
  }

  public void onError(AdfException adfException) {
    System.out.println(
"Local Notification error: " + adfException.getMessage());
  }

  public void onOpen(String token) {
    System.out.println(
"Local Notification opened: " + token);
  }
}

Let’s take a look at what this code does:

  • The local notification will be fired by the JavaScript code when the device enters or exits a beacon region, no matter whether the app was running in the foreground or not.
  • The onMessage method will be invoked immediately if the app is running in the foreground, otherwise it will only be invoked when the user does one of the following:
    • Taps on the notification banner (iOS only)
    • Taps on the “View” button of the notification alert  (iOS only)
    • Taps on the notification in the Notification Center (iOS) or Notification Drawer (Android)
  • The notification triggered by the JavaScript code includes a custom JSON payload that holds a Boolean value for the attribute inside, which defines whether the device is inside or outside the beacon region.
  • If the device is inside the beacon region, the Javascript startRanging function is invoked to start ranging for beacons in this region.

Save all your changes.

Step 5 – Create a bean Data Control

We will model the list of detected beacons in two Java classes (beans) and provide access to certain methods and properties via a Data Control.

Create a new Java file by selecting the ViewController node in the Projects window, right click, select New > Java Class and enter "Beacon" as the name.  Enter the following code into the empty Java file:

package mobile;

import oracle.adfmf.java.beans.PropertyChangeListener;
import oracle.adfmf.java.beans.PropertyChangeSupport;

public class Beacon {

  private String uuid;
  private int major;
  private int minor;
  private String proximity;

  private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

  public Beacon(String uuid, int major, int minor, String proximity) {
    this.uuid = uuid;
    this.major = major;
    this.minor = minor;
    this.proximity = proximity;
  }

  public void setUuid(String uuid) {
    String oldUuid = 
this.uuid;
    this.uuid = uuid;
    propertyChangeSupport.firePropertyChange(
"uuid", oldUuid, uuid);
  }

  public String getUuid() {
    return uuid;
  }

  public void setMajor(int major) {
    int oldMajor = this.major;
    this.major = major;
    propertyChangeSupport.firePropertyChange(
"major", oldMajor, major);
  }

  public int getMajor() {
    return major;
  }

  public void setMinor(int minor) {
    int oldMinor = 
this.minor;
    this.minor = minor;
    propertyChangeSupport.firePropertyChange(
"minor", oldMinor, minor);
  }

  public int getMinor() {
    return minor;
  }

  public void setProximity(String proximity) {
    String oldProximity = 
this.proximity;
    this.proximity = proximity;
    propertyChangeSupport.firePropertyChange(
"proximity", oldProximity, proximity);
  }

  public String getProximity() {
    return proximity;
  }

  public String getIdentifier() {
    return 
"Major: " + getMajor() + " Minor: " + getMinor();
  }

  public void addPropertyChangeListener(PropertyChangeListener l) {
    propertyChangeSupport.addPropertyChangeListener(l);
  }

  public void removePropertyChangeListener(PropertyChangeListener l) {
    propertyChangeSupport.removePropertyChangeListener(l);
  }
}

Let’s take a look at what this code does:

  • Beacon has five properties – uuid, major, minor, proximity and identifier, with associated getter and setter methods.
  • Consuming classes can add and remove PropertyChangeListeners, as required, to be notified of changes to these properties.

Create a new Java file by selecting the ViewController node in the Projects window, right click, select New > Java Class and enter "BeaconManager" as the name.  Enter the following code into the empty Java file:

package mobile;

import java.util.ArrayList;
import java.util.List;

import oracle.adfmf.amx.event.ActionEvent;
import oracle.adfmf.framework.api.AdfmfContainerUtilities;
import oracle.adfmf.framework.api.AdfmfJavaUtilities;
import oracle.adfmf.java.beans.ProviderChangeListener;
import oracle.adfmf.java.beans.ProviderChangeSupport;

public class BeaconManager {

  private static List<Beacon> beacons = null;
  protected transient static ProviderChangeSupport providerChangeSupport;

  public BeaconManager() {
    if (beacons == null) {
      beacons = new ArrayList<Beacon>();
      providerChangeSupport = new ProviderChangeSupport(this);
    }
  }

  public void startMonitoring(ActionEvent actionEvent) {
    AdfmfContainerUtilities.invokeContainerJavaScriptFunction(
      AdfmfJavaUtilities.getFeatureId(), "startMonitoring", new Object[] {});
  }

  public void stopMonitoring(ActionEvent actionEvent) {
    AdfmfContainerUtilities.invokeContainerJavaScriptFunction(
      AdfmfJavaUtilities.getFeatureId(), "stopMonitoring", new Object[] {});
  }

  public List<Beacon> getBeacons() {
    return beacons;
  }

  public static void beaconRanged(String uuid, int major, int minor, String proximity) {
    for (Beacon beacon : beacons) {
      if (beacon.getMajor() == major && beacon.getMinor() == minor) {
        // Existing beacon, update proximity
        beacon.setProximity(proximity);
        return;
      }
    }

    // No existing beacon found, add new one
    beacons.add(new Beacon(uuid, major, minor, proximity));
    providerChangeSupport.fireProviderRefresh("beacons");
  }

  public static void clearBeacons() {
    beacons.clear();
    providerChangeSupport.fireProviderRefresh("beacons");
  }

  public void addProviderChangeListener(ProviderChangeListener l) {
    providerChangeSupport.addProviderChangeListener(l);
  }

  public void removeProviderChangeListener(ProviderChangeListener l) {
    providerChangeSupport.removeProviderChangeListener(l);
  }
}

Let’s take a look at what this code does:

  • The BeaconManager contains a list of Beacon objects, which can be accessed via a getter method.
  • Four methods are provided for managing the beacons:
    • startMonitoring calls the JavaScript startMonitoring function
    • stopMonitoring calls the JavaScript stopMonitoring function
    • beaconRanged is called by JavaScript to inform the BeaconManager that a beacon has been detected.  If newly found, it is added to the list, otherwise the proximity property is updated.
    • clearBeacons is called by JavaScript to clear the list of beacons
  • Consuming classes can add and remove ProviderChangeListeners, as required, to be notified of changes to the list.
  • The list of beacons and the methods accessing the list are static because each time these methods are invoked from JavaScript, a new BeaconManager object is instantiated.

Save all your changes.

To create the Data Control, select the BeaconManager.java file, which will be located in Projects > ViewController > Application Sources > mobile, right click, select Create Data Control and accept the defaults.  The newly created BeaconManager Data Control will appear in the Data Controls window (you may need to Refresh it).

Save all your changes.

Step 6 – Create the AMX feature

The final step prior to deployment is to create the AMX feature.

Return to the maf-feature.xml file located in Projects > ViewController > Application Sources > META-INF and click on the Insert Feature button to add a feature called Beacon.

Open the Content tab and click on the Insert Include button to include the JavaScript file we created earlier by entering resources/js/beacon.js as the file location.

Click on the Create button in the Content tab to create an AMX file called beacon.amx.  Using the BeaconManager Data Control found in the Data Controls window and the AMX components found in the Components palette, create an AMX page similar to the following:

<?xml version="1.0" encoding="UTF-8" ?>
<amx:view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:amx="http://xmlns.oracle.com/adf/mf/amx"
          xmlns:dvtm="http://xmlns.oracle.com/adf/mf/amx/dvt">
  <amx:panelPage id="pp1">
    <amx:facet name="header">
      <amx:outputText value="Beacon Demo" id="ot1"/>
    </amx:facet>
    <amx:facet name="secondary">
      <amx:commandButton actionListener="#{bindings.startMonitoring.execute}"
                         text="Start"
                         disabled="#{!bindings.startMonitoring.enabled}" id="cb1"
                         rendered="#{applicationScope.monitoring != true}"/>
      <amx:commandButton actionListener="#{bindings.stopMonitoring.execute}" text="Stop"
                         disabled="#{!bindings.stopMonitoring.enabled}" id="cb2"
                         rendered="#{applicationScope.monitoring == true}"/>
    </amx:facet>
    <amx:outputText value="Not monitoring a region." id="ot2"
                    rendered="#{applicationScope.monitoring != true}"/>
    <amx:outputText value="Monitoring region: #{applicationScope.region}." id="ot3"
                    rendered="#{applicationScope.monitoring == true &amp;&amp;
                                applicationScope.ranging != true}"/>
    <amx:outputText value="Ranging region: #{applicationScope.region}." id="ot4"
                    rendered="#{applicationScope.ranging == true}"/>
    <amx:outputText value="Beacons located:" id="ot5"
                    rendered="#{applicationScope.ranging == true}"/>
    <amx:listView var="row" value="#{bindings.beacons.collectionModel}"
                  fetchSize="#{bindings.beacons.rangeSize}"
                  styleClass="adfmf-listView-insetList"
                  showMoreStrategy="autoScroll" bufferStrategy="viewport"
                  id="lv1" rendered="#{applicationScope.ranging == true}">
      <amx:listItem showLinkIcon="false" id="li1">
        <amx:tableLayout width="100%" id="tl1">
          <amx:rowLayout id="rl2">
            <amx:cellFormat width="10px" rowSpan="2" id="cf2"/>
            <amx:cellFormat width="100%" id="cf3"
                            height="#{deviceScope.device.os=='Android'?'36':'32'}px">
              <amx:outputText value="#{row.identifier}" id="ot7"/>
            </amx:cellFormat>
          </amx:rowLayout>
          <amx:rowLayout id="rl1">
            <amx:cellFormat width="100%" id="cf1"
                            height="#{deviceScope.device.os=='Android'?'22':'19'}px">
              <amx:outputText value="#{row.proximity}"
                              styleClass="adfmf-listItem-captionText" id="ot6"/>
            </amx:cellFormat>
          </amx:rowLayout>
        </amx:tableLayout>
      </amx:listItem>
    </amx:listView>
  </amx:panelPage>
</amx:view>

Let’s take a look at what this code does:

  • Start and Stop buttons enable you to start or stop monitoring for the beacon region, depending on whether monitoring is active, by calling the startMonitoring and stopMonitoring methods on the BeaconManager Data Control.
  • The page displays the current state of monitoring or ranging the beacon region, using the application-scope variables that are updated by the JavaScript code.
  • Whilst ranging, the page displays the list of detected beacons managed by the BeaconManager Data Control, along with their estimated proximity.

Save all your changes.

Step 7 – Deploy the app

The app must be deployed to a BLE-enabled device.  I have verified it on various iOS devices, but have not yet verified it on an Android device.

In order to deploy to an iOS device, you must configure a suitable provisioning profile and signing identity in JDeveloper  > Preferences > Mobile Application Framework > iOS Platform.

You must also configure an appropriate bundle ID in the maf-application.xml file.  Return to this file, which is located in Application Resources > Descriptors > ADF META-INF, select the Application tab and enter an Id that matches your provisioning profile.

Deselect Show Navigation Bar on Application Launch, save all your changes and deploy the app to a BLE-enabled device.

Before launching the app, ensure you have Bluetooth enabled, as well as Location Services if running on iOS.  When you launch the app on iOS, you will be prompted to allow the app to access your location even when you are not using the app, which you should allow.

The app will start monitoring for the defined region immediately.  If you have access to beacons (or a fake beacon) with the same UUID that you have configured in the beacon.js file, you will be notified when you enter a region containing those beacons.  If the app is running in the foreground when this occurs, ranging will commence and you will see a screen similar to the following:


How to create a fake iBeacon app

If you don’t have any beacons lying around for testing, you can create a MAF app that pretends to be an iBeacon by following these steps.

Step 1 – Find a suitable Cordova plugin

Already done!  Just reuse the plugin you have already downloaded.

Step 2 – Create a MAF app and add the plugin

Open JDeveloper with MAF 2.1.1 installed, create a new MAF app called FakeBeacon and add the plugin as you did for the BeaconDemo app.  Save all your changes.

Step 3 – Create a JavaScript interface file

Create a new JavaScript file called beacon.js as you did for the BeaconDemo app and enter the following code into the empty JavaScript file and save your changes:

(function() {

  // Our region identifiers
  var uuid = "0AC59CA4-DFA6-442C-8C65-22247851344C";
  var major = 1;
  var minor = 100;

  function initialise() {
    var delegate = new cordova.plugins.locationManager.Delegate();

    // Event when advertising starts (there may be a short delay after the request)
    delegate.peripheralManagerDidStartAdvertising = function(pluginResult) {
      console.log('StartAdvertising: ' + JSON.stringify(pluginResult.region));
    };

    // Event when bluetooth transmission state changes
    delegate.peripheralManagerDidUpdateState = function(pluginResult) {
      console.log('UpdateState: ' + JSON.stringify(pluginResult.region));
    };

    // Set delegate
    cordova.plugins.locationManager.setDelegate(delegate);

    // Set initial beacon identifiers in EL
    adf.mf.api.setValue({"name": "#{applicationScope.uuid}", "value": uuid},
                        function() {}, function() {});
    adf.mf.api.setValue({"name": "#{applicationScope.major}", "value": major},
                        function() {}, function() {});
    adf.mf.api.setValue({"name": "#{applicationScope.minor}", "value": minor},
                        function() {}, function() {});
  }

  function createRegion() {
    // Read the values input by the user
    adf.mf.el.getValue("#{applicationScope.major}",
                       function(req, res) { major = res[0]['value'] }, function() {});
    adf.mf.el.getValue("#{applicationScope.minor}",
                       function(req, res) { minor = res[0]['value'] }, function() {});
    return new cordova.plugins.locationManager.BeaconRegion("FakeBeacon", uuid,
                                                            major, minor);
  }

  // Callable externally
  startAdvertising = function() {
    // Verify the platform supports transmitting as a beacon
    cordova.plugins.locationManager.isAdvertisingAvailable().then(function(isSupported){
      if (isSupported) {
        cordova.plugins.locationManager.startAdvertising(
          createRegion()).fail(console.error).done();
      } else {
        alert("Advertising not supported");
      }
    }).fail(console.error).done();
  }

  // Callable externally
  stopAdvertising = function() {
    cordova.plugins.locationManager.stopAdvertising().fail(console.error).done();
  }

  document.addEventListener("showpagecomplete", initialise, false);

}) ();

Let’s take a look at what this code does:

  • There are two externally callable functions – startAdvertising and stopAdvertising, each of which invokes the corresponding plugin API.
  • There are two internal functions – initialize, which is invoked when the framework loads the feature, and createRegion, which creates a new beacon region based on the uuid, major and minor variables defined at the top of the file. The value for uuid should be the same as you specified in the BeaconDemo app.
  • The initialize function creates and registers a Delegate object with two callback functions (which are only included for debug logging) and initializes three application-scope variables.
  • The application-scope variables are used on the AMX page we will create.

Step 4 – Create a managed bean

We will use a managed bean to provide the interface between the AMX code and the JavaScript functions.

Create a new Java file called BeaconBean.java by selecting the ViewController node in the Projects window, right click, select New > Java Class and enter “BeaconBean” as the name.  Enter the following code into the empty Java file and save your changes:

package mobile;

import oracle.adfmf.amx.event.ActionEvent;
import oracle.adfmf.framework.api.AdfmfContainerUtilities;
import oracle.adfmf.framework.api.AdfmfJavaUtilities;
import oracle.adfmf.java.beans.PropertyChangeListener;
import oracle.adfmf.java.beans.PropertyChangeSupport;

public class BeaconBean {

  private boolean advertising;
  private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

  public BeaconBean() {
  }

  public void setAdvertising(boolean advertising) {
    boolean oldAdvertising = this.advertising;
    this.advertising = advertising;
    propertyChangeSupport.firePropertyChange("advertising",
                                             oldAdvertising,
                                             advertising);
  }

  public boolean isAdvertising() {
    return advertising;
  }

  public void startAdvertising(ActionEvent actionEvent) {
    AdfmfContainerUtilities.invokeContainerJavaScriptFunction(
      AdfmfJavaUtilities.getFeatureId(), "startAdvertising", new Object[] {});
    setAdvertising(true);
  }

  public void stopAdvertising(ActionEvent actionEvent) {
    AdfmfContainerUtilities.invokeContainerJavaScriptFunction(
      AdfmfJavaUtilities.getFeatureId(), "stopAdvertising", new Object[] {});
    setAdvertising(false);
  }

  public void addPropertyChangeListener(PropertyChangeListener l) {
    propertyChangeSupport.addPropertyChangeListener(l);
  }

  public void removePropertyChangeListener(PropertyChangeListener l) {
    propertyChangeSupport.removePropertyChangeListener(l);
  }
}

Let’s take a look at what this code does:

  • Two methods are provided for managing the advertising of iBeacon data:
    • startAdvertising calls the JavaScript startAdvertising function
    • stopAdvertising calls the JavaScript stopAdvertising function
  • The advertising attribute holds the advertising status and can be accessed by getter and setter methods.
  • Consuming classes can add and remove PropertyChangeListeners, as required, to be notified of changes to the advertising status.

To create the managed bean, open the adfc-mobile-config.xml file, which is located in Projects > ViewController > Web Content, select the Overview tab, select the Managed Beans tab and select Add.  Enter “BeaconBean” as the name, “mobile.BeaconBean” as the class and select application as the scope, then save all your changes.

Step 5 – Create the AMX feature

The final step prior to deployment is to create the AMX feature.

Return to the maf-feature.xml file and click on the Insert Feature button to add a feature called Beacon.

Open the Content tab and click on the Insert Include button to include the JavaScript file we created earlier by entering "resources/js/beacon.js" as the file location.

Click on the Create button in the Content tab to create an AMX file called beacon.amx.  Using the AMX components found in the Components palette, create an AMX page similar to the following and save all your changes:

<?xml version="1.0" encoding="UTF-8" ?>
<amx:view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:amx="http://xmlns.oracle.com/adf/mf/amx"
          xmlns:dvtm="http://xmlns.oracle.com/adf/mf/amx/dvt">
  <amx:panelPage id="pp1">
    <amx:facet name="header">
      <amx:outputText value="Fake Beacon" id="ot1"/>
    </amx:facet>
    <amx:inputText label="UUID" id="it1" readOnly="true"
                   value="#{applicationScope.uuid}"/>
    <amx:inputText label="Major" id="it2" readOnly="#{BeaconBean.advertising == true}"
                   value="#{applicationScope.major}" inputType="number"/>
    <amx:inputText label="Minor" id="it3" readOnly="#{BeaconBean.advertising == true}"
                   value="#{applicationScope.minor}" inputType="number"/>
    <amx:commandButton text="Start advertising" id="cb1"
                       actionListener="#{BeaconBean.startAdvertising}"
                       rendered="#{BeaconBean.advertising != true}"/>
    <amx:commandButton text="Stop advertising" id="cb2"
                       actionListener="#{BeaconBean.stopAdvertising}"
                       rendered="#{BeaconBean.advertising == true}"/>
  </amx:panelPage>
</amx:view>

Let’s take a look at what this code does:

  • The page displays the UUID, major value and minor value of the fake beacon, which are stored in application-scope variables. 
  • The major and minor values can be edited by the user when the app is not advertising.
  • Start Advertising and Stop Advertising buttons enable you to start or stop advertising as a fake beacon, depending on whether advertising is active, by calling the startAdvertising and stopAdvertising methods on the BeaconBean managed bean.

Step 7 – Deploy the app

The app must be deployed to a BLE-enabled iOS device.  Use the same iOS provisioning profile and signing identity as you did for the BeaconDemo app.

Configure an appropriate bundle ID in the maf-application.xml file as you did for the BeaconDemo app.  Deselect Show Navigation Bar on Application Launch, save all your changes and deploy the app to a BLE-enabled device.

Before launching the app, ensure you have Bluetooth enabled.  While the app is running, if it is not advertising, you can modify the major and minor values for your fake beacon.  The app looks similar to the following:


Conclusion

This post demonstrates how easily you can create a MAF app that detects iBeacons and notifies the user when a particular iBeacon region has been entered.

Detecting the beacons is the easy part, but how will you provide relevant information to your users in a real-world scenario, such as those mentioned?

When a beacon region is entered, or a user’s proximity to a particular beacon is closer than a defined limit, your MAF app could trigger a local notification, display a popup dialog or navigate to a page containing the relevant information.  MAF provides APIs for each of these scenarios and there are sample apps provided within each MAF release demonstrating how to use these APIs.

To keep your MAF app up-to-date, your app could connect to a REST-based service when launched, and download a current list of beacon identifiers and all relevant information.

For more information about these and other topics, please refer to the Oracle A-Team Chronicles, in particular this article on iBeacon and this article on the Mobile Persistence Accelerator (which includes REST-JSON and REST-XML wizards for MAF).

Tuesday Mar 31, 2015

How to implement push notifications in MAF 2.1.1

Push notifications are a fantastic way to notify your users of relevant information in a timely fashion, even when your mobile app is running in the background or not running at all.

The MAF 2.1.1 extension provides two sample applications – PushDemo, which demonstrates how to receive push notifications in a MAF app, and PushServer, which demonstrates how to send push notifications to a MAF app.

This blog post will provide an introduction to push notifications and a detailed description of how to configure the PushDemo and PushServer sample applications, as a vehicle for learning about push notifications. Readers can use the lessons learned to implement push notifications in their own applications.

How push notifications work

Although technically different, the logical push notifications architecture is similar for mobile apps running on both Android and iOS, with Google and Apple each providing a cloud-based Push Notifications service that acts as the go-between your server application and your mobile app.

The flow of push notifications is also similar: including a registration process followed by the sending of a notification to one or more devices, as follows:


Each time a push-enabled mobile app starts, it registers with the Apple Push Notifications service (APNs) or Google Cloud Messaging (GCM), based on the device type.  The registration request includes an identifier. Successful registration results in a registration token being returned that uniquely identifies this mobile app running on this device.

The push notification will originate from a server application, so the mobile app must register with the server application, providing enough information for the server application to be able to uniquely identify the user associated with the app on this device. This registration with the server application would typically be done following successful authentication of the user and must include the registration token.

Having completed the registrations, when an event occurs later on that requires a push notification to be sent to one or more users, the server application reviews all the registrations and sends a push notification to the user’s registered device(s) via APNs, GCM, or both. Each push notification message includes the unique registration token for the target device.

Required push notifications setup

APNs

Apple requires you to create an explicit App ID that enables push notifications and a corresponding client SSL certificate. 

The App ID is used to create a provisioning profile that is embedded into your mobile app.  This provisioning profile informs APNs that your mobile app is a trusted app.

The client SSL certificate is used by your server application when communicating with APNs.   It informs APNs that your server application is a trusted application and that it is permitted to send push notifications to your mobile app.

For more information refer to Apple Push Notification Service and Configuring Push Notifications.

GCM

Google requires you to create a Google API project and obtain an API Key.

The project number is used by your mobile app as the Sender ID when registering with GCM. It indicates to GCM which server application (or server applications, since more than one Sender ID can be specified) is permitted to send push notifications to your mobile app.

The API Key is used by your server application as the Sender Auth Token when communicating with GCM. It gives your server application access to Google services and ensures that push notifications originating from your server application are targeted to your mobile app.

For more information refer to Google Cloud Messaging Overview and Getting Started on Android.

About the PushDemo sample app

The PushDemo sample app is a MAF app that works in conjunction with the PushServer sample app to demonstrate registration for, sending and receipt of push notifications. (More details about the sample apps provided within each MAF release and where to find them are provided here.)

The PushDemo sample app has three functions:

  • Each time you launch the PushDemo sample app, it automatically registers with GCM or APNs, as appropriate for your device, and receives a registration token.
  • It provides a registration page for you to initiate registration with a running instance of the PushServer sample app.  In a real-world app, this step would normally be automatically handled by the client app following a user login, or retrieval of stored user details.
  • It provides a page for displaying the message received when a push notification is received by the PushDemo sample app.

About the PushServer sample app

The PushServer sample app is a server application that uses a GCM client library provided by Google (refer to the Android Developer site for details of how to install it) for connecting to the GCM server, and sample code for connecting to APNs as described on the Apple Developer site.

GCM and APNs have different policies regarding push notification error handling and de-registration of devices.  The PushServer sample app does not provide any special handling, but you should be aware of these policies when designing your server application if it will support push notifications. To review these policies, refer to Implementing GCM Server and Provider Communication with Apple Push Notification Service.

There are two components to the PushServer sample app:

  • A registration servlet that enables an instance of the PushDemo sample app running on a mobile device to register with the server application.  The registration request includes the registration token received from GCM or APNs, the mobile app’s Bundle ID, the user ID entered into the registration page of the mobile app and the device type.  These are stored in a database on the server for later sending of push notifications to the user’s mobile device.
  • A push notification page displays the registered devices and enables you to enter a message to push to a selected device.

How to configure and run the PushServer sample app

Apple and Google registration

To implement push notifications, the app must be registered with Apple and/or Google, depending on the device(s) you wish to use for testing.

If you are an Oracle employee, this has already been done for you and you can retrieve the necessary signing artifacts by clicking on the link “How to use the PushDemo sample app” found on the internal MAF uptake guide wiki.

Otherwise for customers, you will need to register your app with Apple and/or Google and retrieve the necessary signing artifacts, as described above under Required Push Notifications Setup.

Database setup

The PushServer sample app requires a database for storing the mobile device registration details. A dummy database connection called “pushServerDB” is provided in the connections.xml.

If you are an Oracle employee intending to run the server application on the Oracle intranet, you can configure the PushServer sample app to connect to a hosted database as described on the internal MAF uptake guide wiki.

Otherwise for customers, you must install a database, create the required schema using the pushschema.sql script found in the PushServer sample app’s Model/database folder and modify the properties of the “pushServerDB” database connection in the connections.xml file as follows.  If your database is not an Oracle database, you may also need to modify certain properties of the Application Modules that are configured in the bc4j.xcfg file.  The configuration steps are described below.

Server application configuration

Follow these steps to configure the server application:

  1. Open the PushServer sample app in JDeveloper 12.1.3 with MAF 2.1.1 extension.
  2. Modify the dummy database connection as follows:
    1. Expand Application Resources > Connections > Database to reveal pushServerDB.
    2. Right-click on pushServerDB and select Properties.
    3. Enter all the required properties for the database you intend to use.
    4. Verify the entered details by clicking on Test Connection.
    5. Save the entered details by clicking on OK.
  3. If you are using a non-Oracle database, you may need to update certain properties of the configured Application Modules as follows:
    1. Open Projects > Model > Application Sources > oracle.adfmf.demo > push > model > RegistrationModule > RegistrationModel.xml.
    2. Open the Configurations tab.
    3. Click on the link for bc4j.xcfg.
    4. For each listed Application Module, select it and click on the Edit icon, open on the Properties tab, modify the relevant properties and click OK.  For example, to connect to a MySQL database, change the jbo.SQLBuilder property to “SQL92”.
  4. Open the MessageBean.java file under Projects > ViewController > Application sources > oracle.adfmf.demo > push.
  5. Set the GOOGLE_APIKEY variable to your Google project’s API Key.
  6. Set the IOS_KEYFILE variable to the full path to your iOS keystore (.p12 file).
  7. Set the IOS_KEYPWD variable to the password you set on your iOS keystore.
  8. If you will run the server application on a corporate intranet, such as the Oracle network, set the PROXY_HOST and PROXY_PORT variables appropriately.
  9. Otherwise, leave both the PROXY_HOST and PROXY_PORT variables set to null.
  10. Save all your changes.

WebLogic Server configuration

The server application must be deployed to a running WebLogic Server (WLS) instance.  The most convenient option for developers is to configure and run the embedded WLS within JDeveloper as follows:

  1. If you are on a corporate intranet, such as the Oracle network, configure the JDeveloper proxy settings in Preferences > Web Browser and Proxy > Proxy Settings.
  2. Start an instance of the embedded WLS in JDeveloper via Run > Start Server Instance.
  3. You will be prompted to enter and confirm a password.  At this point, you may also change the listening address or port.  I recommend you keep the default settings, which should be to listen on All Available Addresses using port 7101, with port 7102 for SSL.
  4. Once the WLS has started, open http://localhost:7101/console in your preferred browser.  This is the WLS console.
  5. Login to the WLS console as user “weblogic” and the password you entered in step 3.
  6. Turn off hostname verification in the WLS console via Domain Configurations > Environment > Servers > Default Server > Configuration > SSL > Advanced. By setting “Hostname Verification” to None.
  7. Click on Save.

Deploy the server application

Deploy both components of the server application within JDeveloper as follows:

  1. Expand Projects > ViewController > Application Sources > oracle.adfmf.demo > Push to reveal RegistrationServlet.java.
  2. Right-click on RegistrationServlet.java and select Run.
  3. You can ignore the error reported by the servlet, since it was launched without any parameters.
  4. Expand Projects > ViewController > Web Content to reveal pushregistration.jspx.
  5. Right-click on pushregistration.jspx and select Run.

How to configure and run the PushDemo sample app

Client app configuration

Configure the PushDemo sample app as follows:

  1. Open the PushDemo sample app in JDeveloper 12.1.3 with MAF 2.1.1 extension.
  2. Open the adf-config.xml file found in Application Resources > Descriptors > ADF META-INF.
  3. Select the Source tab and enter your Google project number into the value for the gcmSenderId property.
  4. Open the connections.xml file also found in Application Resources > Descriptors > ADF META-INF.
  5. Modify the url for the PushServiceConn connection by changing “127.0.0.1” to the relevant value for your server application, based on the network that will be shared by your server application and your mobile client app.
  6. Save all your changes.

Deploy the client app

Follow these steps to deploy and run the PushDemo app:

  1. Configure JDeveloper with the name of your push-enabled iOS provisioning profile and corresponding signing identity in Preferences > Mobile Application Framework > iOS Platform.
  2. Deploy the PushDemo app to your Android or iOS device, or Android emulator (you must use a “Google APIs” target).  Push notifications do not work on the iOS simulator.
  3. Ensure that your mobile device can access the Internet via a mobile data plan or wifi network.  Note for Oracle employees that push notifications are blocked on the “clear” wifi network, even with VPN enabled.  If you have no access to a mobile data plan or corporate wifi connection, you can setup a local wifi on the machine that is running the server application and connect to that.
  4. Launch the app and it will automatically register with APNs or GCM, as appropriate. 
  5. Switch to the Register feature and it should advise that registration was successful.
  6. If you have not registered your device with the server application previously, you will need to do this now.  If you are running your server application on a corporate intranet, such as the Oracle network, you will need to establish a VPN connection on your mobile device before communicating with the server application. 
  7. To register your device, enter a unique User ID into the client app and tap on the Register button.  A success or error message will be displayed below the Register button. 
    1. Note: The server application does not handle duplicate entries for one device and APNs or GCM may return a different token, so you may need to delete the old entry for your device from the database and try again.
  8. If you are using an Android device and you have established a VPN connection, you must close this VPN connection now to ensure that you can receive push notifications.
  9. Switch to the Message feature, which will display any message received via push notifications.

How to send a push notification to the PushDemo sample app

When you deployed the server application's Push Registration page, it should have been displayed in your default browser.  You can send a push notification to the PushDemo sample app running on your mobile device from this page as follows:

  1. Select the entry for your mobile device.
  2. Enter a message into the Message field.
  3. Click the Push Message button.

Your mobile device should receive the push notification as follows:

  • If the PushDemo sample app is running in the foreground, the message you entered should be displayed on the Message feature’s main page.
  • Otherwise, your mobile device’s operating system should display the push notification in the standard manner. On Android, a notification banner is presented for a short time and the notification can be viewed in the Notification Drawer. On iOS, depending on your notification settings, a banner or alert may be displayed and the notification can be viewed in the Notification Center.
  • If you tap on a notification displayed by your device’s operating system, the PushDemo sample app should be launched and/or moved to the foreground and the message should be displayed on the Message feature’s main page.

Conclusion

The PushDemo sample app demonstrates how to register for and receive push notifications using MAF 2.1.1. It works in conjunction with the PushServer sample app that demonstrates techniques for registering with GCM and APNs and sending push notifications using these services.

By following the steps described above, you should be able to send push notifications from a deployed instance of the PushServer sample app to your mobile device with the PushDemo sample app installed.

Based on the information and links provided in this blog post and the hands-on experience gained from configuring and using the provided sample apps, you should be able to implement push notifications in your own server application and MAF-based mobile app.

Monday Mar 30, 2015

Oracle MAF 2.1.1 Released

We are happy to announce the release of Oracle MAF 2.1.1.

This release adds a couple of new features and includes bug fixes - for the full details see the Oracle MAF documentation page.

One key new feature in this release is support for local notifications.

Oracle MAF local notifications originate within the MAF application and are received by the same application. The notifications are delivered to the end user through standard mechanisms supported by the mobile device platform (for example, banner, sound) and can work when the application is either in the foreground, background or not running at all. 

Focusing on simplifying development, you can schedule, receive and cancel local notification through the declarative device feature data control palette, as well as through Java and JavaScript APIs. More here.

You can get the new Oracle MAF 2.1.1 extension through the help->check for updates in JDeveloper. 

Monday Mar 16, 2015

Oracle University Now Offering - Oracle MAF 2.1 Courses

We are happy to announce that you can now get official Oracle University training on Oracle MAF

Check out the new course - Oracle Mobile Application Framework 2.1: Develop Mobile Apps 

Available both as an in class training and a virtual class training - so you can take it from anywhere.

logo

This is a great first step on your way to become a certified Oracle Mobile Developer.

The course covers the following topics:

  • Design, build and deploy mobile applications
  • Provide and persist data for mobile applications
  • Leverage on-device features in a mobile application
  • Add access control and authentication functionality to mobile applications
  • Test, debug and determine the type of deployment before releasing your application 

This new course joins the many other ways to learn Oracle MAF - so no more excuses ;-)

Wednesday Mar 04, 2015

Oracle MAF 2.1 Now in Eclipse Too

We are happy to announce the release of a new version of Oracle Enterprise Pack for Eclipse (OEPE)- the Oracle Eclipse tooling offering - with support for the latest Oracle MAF 2.1.

Download the new OEPE 12.1.3.4 with MAF 2.1 Support Here.

This new version of OEPE is based on the latest Eclipse Luna SR2, and adds support for the full set of MAF 2.1 features including support for Java 8 compact profile 2, the new Xcode  and Android SDKs, and Cordova 3 plugin integration.

One unique feature added to the OEPE design time experience is a simplified way to interact with REST/JSON services. REST/JSON services are the preferred way to exchange information between your mobile application and remote server. A new wizard based interface in OEPE allows you to easily test REST URLs and then generate a simple set of POJO Java classes that access those services and working with the data types they use. You can then expose those classes as data controls and simply drag and drop them to the AMX pages to create the mobile UI.

Here is a 5 minutes demo that shows you the new REST features in action:

For more about this new version see the OEPE blog entry.

The OEPE Blog also has new entries describing some of the new features including:

 

Thursday Feb 19, 2015

Integrating a custom Cordova plugin into a MAF app

In this earlier post, I demonstrated how to create a simple Cordova plugin for each of Android and iOS.  In this post, I’ll describe how to include that plugin into a MAF 2.1.0 app and how to invoke it from Java, from AMX and from local HTML.

You can follow these same instructions to integrate any 3rd party or custom Cordova plugin into your MAF app.

How do I include a custom Cordova plugin into my MAF app?

To include a custom Cordova plugin into a MAF app, you specify the location of the plugin’s top-level folder (on your local hard drive) in the app’s maf-application.xml > Plugins UI in JDeveloper, as described in the MAF 2.1.0 Documentation.

The path to the plugin will be stored in maf-plugins.xml and will be relative to that file’s location. To avoid any deploy-time issues, make sure the plugin’s top-level folder is on the same drive as your app and that there are no spaces in its path. If your plugin is specific to your MAF app, you probably want to include it within the app’s source tree.

The plugin will appear in the maf-application.xml > Plugins UI as follows:

Note: If you want to use a 3rd party plugin, you must first download it (as a zip file) and extract it to your local hard drive as described in Step 7 of this blog post.

How do I invoke the plugin’s methods?

As described in the earlier post, a Cordova plugin includes a JavaScript interface that defines its available functions. You must write your own JavaScript in your MAF application to call the plugin’s JavaScript interface so your MAF application can interact with the plugin.

In the example “Alert” plugin created in the earlier post, we defined the following JavaScript interface in the alert.js file:

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

Our plugin’s manifest file, plugin.xml, defined the JavaScript module as follows:

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

This means that the alert function will be exported as part of the Alert JavaScript module within the consuming MAF application, which enables you to call this function from JavaScript as follows:

  Alert.alert(“My title”, “My message”, “My button label”, myCallbackFunction);

As per the plugin design, this code would cause a native popup dialog to be displayed with “My title” as the title, “My message” as the message and a single button with the label “My button label”. When the user taps the button to dismiss the dialog, the JavaScript callback function myCallbackFunction will be executed with an input parameter of 0.

For more information on using JavaScript callback functions, refer to this helpful blog post by Michael Vollmer.

Note: If you want to use a 3rd party plugin, the JavaScript interface and how to call it should be described in the plugin’s README.md file, typically found in the plugin’s top-level folder.

How do I invoke the plugin from a Local HTML page?

You could invoke the plugin’s JavaScript interface methods directly from your HTML code, but it’s more common to provide a wrapper function that simplifies your code and abstracts you from possible changes to the plugin’s interface.

Let’s take a look at a local HTML page that calls our “Alert” plugin:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport"

          content="width=device-width, initial-scale=1, maximum-scale=1"/>
    <title>Plugin Demo</title>
    <script type="text/javascript">if (!window.adf) window.adf = {};
                                   adf.wwwPath = 
"../../../../www/";</script>
    <script type="text/javascript" src="../../../../www/js/base.js"></script>
    <script type="text/javascript">
      function showAlert(frm) {
        Alert.alert(frm.title.value,
                    frm.message.value,
                    frm.buttonLabel.value,
                    function(button) {
                      console.log("User tapped button:" + button);
                    });
      }
    </script>
  </head>
  <body>
    <form>
      <br>Title: <input type="text" name="title" value="Title">
      <br>Message: <input type="text" name="message" value="Message">
      <br>Button Label: <input type="text" name="buttonLabel" value="Button">
      <br><input type="button" value="Alert" onclick="showAlert(this.form);">
    </form>
  </body>
</html>

The <meta> tag is included here to avoid the user seeing the components on the page increase or decrease in size as she interacts with it.

You must include the two lines of JavaScript that define the window.adf object and include base.js. There is no need to explicitly include the plugin’s JavaScript file here, since at deploy-time it will be injected into base.js.

A simple showAlert function has been defined to wrap the call to Alert.alert, which in turn simplifies the calling code within the HTML form.

The showAlert function is called when the user taps on the “Alert” button. The “title”, “message” and “button label” input text values are read from the form and passed to Alert.alert, along with an anonymous callback function that will be called when the user taps on the dialog’s button. This callback function will be passed a value for the input parameter button, which we know will be 0, since that was hard-coded in our plugin’s native code.

How do I invoke the plugin from an AMX page?

AMX components cannot call JavaScript methods directly, so you must implement a Java method that is called from an AMX component, which in turn executes some JavaScript that calls the plugin’s JavaScript interface.

Let’s take a look at an AMX page that is similar to the local HTML page described above:

<?xml version="1.0" encoding="UTF-8" ?>
<amx:view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:amx="http://xmlns.oracle.com/adf/mf/amx"
          xmlns:dvtm="http://xmlns.oracle.com/adf/mf/amx/dvt">
  <amx:panelPage id="pp1">
    <amx:facet name="header">
      <amx:outputText value="Plugin Demo" id="ot1"/>
    </amx:facet>
    <amx:inputText label="Title" id="it1"
                   value="#{applicationScope.PluginBean.alertTitle}"/>
    <amx:inputText label="Message" id="it2"
                   value="#{applicationScope.PluginBean.alertMessage}"/>
    <amx:inputText label="Button Label" id="it3"
                   value="#{applicationScope.PluginBean.alertButtonLabel}"/>
    <amx:commandButton text="Alert" id="cb1"
                       actionListener="#{PluginBean.popupAlertDialog}"/>
  </amx:panelPage>
</amx:view>

The three inputText components store their values in three attributes of an bean called PluginBean. When the user taps the commandButton component, it calls the popupAlertDialog method of PluginBean.

Let’s take a look at the PluginBean:

package mobile;

import oracle.adfmf.amx.event.ActionEvent;
import oracle.adfmf.framework.api.AdfmfContainerUtilities;
import oracle.adfmf.framework.api.AdfmfJavaUtilities;

public class PluginBean {
  private String alertTitle"Title";
  private String alertMessage"Message";
  private String alertButtonLabel"Button";

  public PluginBean() {
  }

  public void popupAlertDialog(ActionEvent actionEvent) {
    AdfContainerUtilities.invokeContainerJavaScriptFunction(
      AdfmfJavaUtilities.getFeatureId(),
      "showAlert",
      new Object[] {
        alertTitle,
        alertMessage,
        alertButtonLabel
      });
  }

  public void setAlertTitle(String alertTitle) {
    this.alertTitle = alertTitle;
  }

  public String getAlertTitle() {
    return alertTitle;
  }

  public void setAlertMessage(String alertMessage) {
    this.alertMessage = alertMessage;
  }

  public String getAlertMessage() {
    return alertMessage;
  }

  public void setAlertButtonLabel(String alertButtonLabel) {
    this.alertButtonLabel = alertButtonLabel;
  }

  public String getAlertButtonLabel() {
    return alertButtonLabel;
  }
}

The popupAlertDialog method calls AdfmfContainerUtilities.invokeContainerJavaScriptFunction to invoke a JavaScript function called showAlert, passing the alertTitle, alertMessage and alertButtonLabel attributes as input parameters to the JavaScript function.

You may wonder why the plugin’s alert JavaScript method is not invoked directly and we call the showAlert function instead. The reason is that the plugin’s alert method requires a JavaScript function callback to be passed as an input parameter and there is no way to do this from Java. Passing a null value may have unwanted side effects, so that is not recommended.

For this reason, we need to write a JavaScript function that wraps the call to the plugin’s alert method. There are two options for this.

One option is to create a JavaScript file and include it into each feature that wishes to invoke the plugin via the app’s maf-feature.xml UI as follows:


The JavaScript file would look similar to the following:

showAlert = function(title, message, buttonLabel) {
  Alert.alert(title,
              message,
              buttonLabel,
              function(button) {
                console.log("User tapped button: " + button);
              });
};

This JavaScript file defines a global function variable called showAlert, which accepts 3 input parameters and calls the plugin’s alert method with those 3 parameters, along with an anonymous callback function that will be executed when the user taps on the dialog’s button. The native code will pass a return value of 0 to the anonymous callback function as the input parameter button.

The other option, if the plugin is to be invoked from a single AMX page, is to include the wrapper function as verbatim JavaScript within the AMX page, similar to the following:

<?xml version="1.0" encoding="UTF-8" ?>
<amx:view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:amx="http://xmlns.oracle.com/adf/mf/amx"
          xmlns:dvtm="http://xmlns.oracle.com/adf/mf/amx/dvt">
  <amx:panelPage id="pp1">
    <amx:verbatim id="v1">
      <![CDATA[
        <script type="text/javascript">
          function showAlert(title, message, buttonLabel) {
            Alert.alert(title,
                        message,
                        buttonLabel,
                        function(button) {
                          console.log("User tapped button: " + button);
                        });
          }
        </script>
      ]]>
    </amx:verbatim>
    <amx:facet name="header">
      <amx:outputText value="Plugin Demo" id="ot1"/>
    </amx:facet>
    <amx:inputText label="Title" id="it1"
                   value="#{applicationScope.PluginBean.alertTitle}"/>
    <amx:inputText label="Message" id="it2"
                   value="#{applicationScope.PluginBean.alertMessage}"/>
    <amx:inputText label="Button Label" id="it3"
                   value="#{applicationScope.PluginBean.alertButtonLabel}"/>
    <amx:commandButton text="Alert" id="cb1"
                       actionListener="#{PluginBean.popupAlertDialog}"/>
  </amx:panelPage>
</amx:view>

In either case, there is no need to include the plugin’s JavaScript file in the calling feature(s), since at deploy-time it will be injected into base.js.

Conclusion

You now have the knowledge required to create your own custom Cordova plugin and integrate it into your MAF 2.1.0 app, invoking it from local HTML, an AMX page, or from Java code.

Once you’ve tested your code successfully, you might consider publishing the plugin to make it available to the greater Cordova community. This can be achieved by calling plugman as follows:

$ plugman publish path-to-plugin-top-level-folder

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 2015
SunMonTueWedThuFriSat
   
1
2
3
4
5
6
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  
       
Today