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.

Before adding the plugin to your app, you must extract the contents of the downloaded zip file into a location that is on the same drive as your MAF app and into a folder whose path contains no spaces.  I like to extract the plugin into the source tree of the app that's using it, for example within the top-level src folder of the app. 

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 extracted 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.

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.

Here's where I should point out, if you haven't noticed already, that the Java bean we created earlier isn't yet "managed" by anything.  To turn it into a 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.

Returning to beacon.amx, use the AMX components found in the Components palette to 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.

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

How-to Access Selected Values in a Multi Select List

MAF Version: 2.1

Problem Statement

Using multi-item-select components, like amx:selectManyCheckbox, in a MAF application does not directly write the values back to the model but saves an array of selected row indexes in the inputValue attribute of the list binding. In addition, when users start selecting items in a muli-select list, each time they click on an item, the component valueChangeListener fires notifying the event listener in the managed bean about the selection. Both leads to the question of how to access the underlying data object (the row) for each selected item to read its properties.

For example, the assumed requirement for the sample application you can download at the end of this blog entry is to display the name of selected customers in a text box below the list component on the AMX page.

The Solution

The best part of a trip often is the journey because its where you see things and learn new skills. The same is true for the solution to the above challenge, as you will see when going through the implementation. For this solution, the decision was made to use the valueChangeListener and - for each selection - print the actual list values.

As shown in the image below, each selection in the list of customers is immediately mirrored in the text field below the list.

Multi Select List Sample Screen Shot

Note: If you don't need this information on each selection you would read the selected index values from the attribute binding (list binding) inputValue property upon e.g. a press on a button. In this case the code shown in this blog will guide you the way.

The implementation in the sample performs the following steps

  1. A valueChangeListener tracks all user item selection and de-selection
  2. For the selected items, the managed bean code reads the indexes from the component event
  3. The indexes are then changed from an array of String to List of Integer
  4. The list binding in the pageDef file (the binding) is accessed to obtain access to the list iterator
  5. For each index in the list of selected items, the iterator current row is set and then read

Note: Keep in mind that multiple binding artifacts may point to the same iterator. If you change the current row of a shared iterator, make sure to first read the current row key or index so it can be set back at the end of your action. In the sample the iterator is not shared and therefore the current row doesn't need to be set back.

Implementation Steps

The implementation is based on a POJO model, as this model is the most common in MAF. POJO models are usually created for REST services (using the REST Service Adapter in MAF), or when accessing the SOAP DC from Java, or when exposing the local SQLite database to the UI. In each of the cases mentioned before, you create a Java bean that then is exposed as a Data Control for use in MAF. From the data control you then create the UI using drag and drop from the Data Controls panel in JDeveloper or Oracle Enterprise Package for Eclipse (OEPE).

Note: This sample has customer data contained in the POJO data control (so to not require any dependency to a service, database or SQLite in MAF)

AMX view

The sample uses a single AMX view in the feature (which is good for demos and sample. For real MAF applications my personal preference is to use bounded task flows as the feature content). The page source of the list and the item mirroring the selections is shown below

<amx:selectManyCheckbox value="#{bindings.customers.inputValue}" 
         label="#{bindings.customers.label}" id="smc1"
         valueChangeListener="#{viewScope.SelectHelper.onValueChange}">
    <amx:selectItems value="#{bindings.customers.items}" id="si1"/>
</amx:selectManyCheckbox>
<amx:inputText label="Selected Values" id="it1" rows="7" inputType="text"
                   value="#{viewScope.SelectHelper.selectedValues}"/> 

To explain the important bits:

#{viewScope.SelectHelper.onValueChange} 
" valign="top"> Managed bean reference to the value change listener.  The managed bean doesn't hold state and thus is in the smallest scope available for MAF, which is viewScope
#{bindings.customers.items}
The customers reference is the list binding in the AMX pageDef file. Items gives you access to a list of items that then are iterated over by the component. This part doesn't need to be manually created by you and is generated by JDevelper or OEPE
#{bindings.customers.inputValue}
The inputValue property exists on the list binding and holds the value of the selected items (for the use case in which you need to access the selected row indexes in response to a command button or link action. As you see, the value is EL accessible and as such can be referenced from Java too using the AdfmfJavaUtilities class in MAF
#{viewScope.SelectHelper.selectedValues}

This EL is added to the input text field that shows the selected customer name and references a set/get method pair (bean properties) in the managed bean. The value change listener associated with the select component calls the set-method to post changes to the text field. A property change event is used notify the UI field to refresh


Note: most of the AMX page configuration is created for you when dragging and dropping a collection from the data control panel to the page. The only manual step is the creation of the value change listener and the input text field referencing the managed bean properties.

Managed Bean

The value change listener method - onValueChange(...) - in the managed bean is doing the real work. This is where the journey starts becoming exciting. The important parts of the code - in addition to the code comments, are explained further at the end

public void onValueChange(ValueChangeEvent valueChangeEvent) {

 //the newValue property of the valueChangeEvent shows an array of Strings 
 //with the selected values. This array is updated with each new selection 
 //or de-select
 Object[] newVal = (Object[]) valueChangeEvent.getNewValue();

 //the oldValue property of the valueChangeEvent shows an array of Strings 
 //with the values of the previous selection. This property allows you to 
 //tell what exactly has been changed within a new selection
 Object[]  oldVal = (Object[]) valueChangeEvent.getOldValue();

 //converting array of Strings to list of Integer to read the real 
 //objects from the list binding iterator
 ArrayList<Integer> selectedCustomers = new ArrayList<Integer>();      
 for (int i = 0; i < newVal.length; i++) {
     selectedCustomers.add(new Integer((String)newVal[i]));
 }

 //lists are represented by the  AmxAttributeBinding
 AmxAttributeBinding customerList = (AmxAttributeBinding) AdfmfJavaUtilities
                               .evaluateELExpression("#{bindings.customers}");

 StringBuffer selectedCustomerNames = new StringBuffer();

 //access the list iterator to first set the current row by the indexes obtained 
 //from the value change event and then read the row object, which in this sample
 //represents a customer
 AmxIteratorBinding amxListIterator =  customerList.getIteratorBinding();
 
 //the basic iterator in the AmxIteratorBinding is what we need to work with
  BasicIterator      basicIterator = amxListIterator.getIterator();
        
  for (Integer customerIndx : selectedCustomers) {
     //set new current row in list iterator
     basicIterator.setCurrentIndex(customerIndx.intValue());
     //get current row. Note that for POJO models you call getDataProvider and cast to 
     //the entity. For SOAP models you call getCurrentRow and cast it to GenericType 
     Customer customer = (Customer) basicIterator.getDataProvider();
     //for this sample, print selected customers into a text field on the page
     selectedCustomerNames.append("Customer on index  " + (customerIndx.intValue() + 1) + ": " + customer.getName()+"\n");
  }
  
  //update the input text field with the selection. The set-method of the 
  //selectedValue property uses the PropertyChane listener in MAF to trigger
  //the field to refresh
  this.setSelectedValues(selectedCustomerNames.toString());

}

Following table contains further explanations for parts of the code

valueChangeEvent.getNewValue() The list component has a "ValueChange" property that can be linked to a managed bean that defines a public method with the ValueChangeEvent as the only argument. The event argument gives developers access to the current selected items as well as the previously selected items, allowing them to tell the change. In the sample, the selected list is used only to lookup the associated binding object (aka. model objects) for more details. The getNewValue() method returns an array of String, where each string represents the index of a selected item in the list. The index is the same as in the binding layer, which makes it easy to lookup the data object.
AmxAttributeBinding There is no list binding in MAF (and I used teh term for simplicity in the article). List binds are attribute bindings and allow access to the list of items and the iterator. The iterator is created from the collection that in this example displays customer information. The attribute binding however does not display the full customer object and only knows of a value property (usually you select this as the PK of a object) and one to any display properties.
AmxIteratorBinding The iterator is an instance of AmcIteratorBinding, which is a wrapper for BasicIterator, which is the object instance that gives access to the data provider, which in the POJO case represents the data object (entity) representing a row.
basicIterator.getDataProvider(); The getDataProvider() method works for POJO models (PojoDC) wheras getCurrentRow provides equivalent access to the current row object for the SOAP DC. The code in the sample sets the current row for each selected item (using the item index) before accessing the row data object (entity)

Summary

This blog article and its sample shows one possible solution to work with multi select  lists in MAF. The use case covered is where the application requirement is to respond to any selections users make in the list. The use case in which the selected list is read after all items are selected is not covered. The handling for this is to access the list binding (which now you know is an attribute binding) to access the inputValue property for the submitted values. You then iterate over the values and set the current row in the BasicIterator using the keys you find in the array you obtain from the inputValue. The rest then remains the same

Download the Sample

The sample for this blog entry has been written for MAF 2.1 using JDeveloper. The sample code can be download from http://www.oracle.com//technetwork/developer-tools/adf/learnmore/multiselectlist-2423622.zip . In JDeveloper, open the MAF workspace and run the AMX page contained within. When the application shows up in the simulator, emulator or on-device, select items in the list to see the customer name - which is not part of the list - displaying in the text field. 

--------------

Disclaimer

The sample explained in the blog and provided for this blog entry is provided as is. The sample will not be upgraded automatically to newer versions of MAF and is also not supported by Oracle world wide customer support. Samples are community efforts that we support in a community effort: If you experience problems or have questions, please post a note to the MAF community forum on OTN: https://community.oracle.com/community/oracle-mobile/oraclemaf/content?customTheme=otn


Thursday Feb 12, 2015

How-to open and close a popup dialog from Java in MAF

MAF Version: 2.1

Problem Statement

In MAF, application developers use two component behavior tags, amx:showPopupBehavior and amx:closePopupBehavior, to show or hide popup dialogs in response to user interaction, pressing a command button or link. What however if you need to show or hide a popup from Java in a value change listener or similar component event? And what if the dialog should close itself after a period of time? By the time of writing, no API for this exists in MAF.

The Solution

Since there is no framework API to close and open popup dialogs in MAF, a custom implementation must do. The custom implementation uses JavaScript, as well as the amx:showPopupBehavior and amx:closePopupBehavior tags on hidden buttons (visible buttons would be possible too).

To show or hide a popup dialog you use Java in a managed bean to call out to JavaScript,using the
AdfmfContainerUtilities.invokeContainerJavaScriptFunction() framework method.

Section Q&A:

Q1: Could you call the same from a POJO Data Control?
A1:Yes you can.

Q2: Does it make sense?
A2: No. The data control is part pf the MAF model and thus needs to be generic and not specifically written for a view in MAF. 

About the Example Code

The MAF 2.1 sample code shown in this blog entry (and available for download) keeps it simple. The AMX page displays single command button that, when pressed, triggers a component action event. This action event is calls a Java method in a managed bean that in turn invokes JavaScript to "virtually" press the hidden button with the amx:showPopupBehavior tag attached to it for you. A timer in the same managed bean then waits for 5 seconds before it closes the dialog, pressing the other hidden button (it actually raises a tap event on teh button). Its a simple example that however showcases nicely how you can open and clode dialogs in MAF.

The Sample Implementation

Though you can download the sample ZIP file from a link provided at the end of this article, its worth showing the code in the blog entry as well, so you you can quickly scan it. The JavaScript source is saved in a stand-alone JS file so it can be reused. In general I recommend using external JavaScript files and force yourself to write generic code than to stuff scripts into an AMX page directly.

Step 1) Creating the JavaScript file

First you need to create JavaScript file - or extend an existing file - with the JavaScript code shown below. In my sample (MAF 2.1) this the JS file is named "PopupUtils.js". The two functions in the file popupUtilsShowPopup and popupUtilsHidePopup become globally available for the feature they are configured for (explained next). The two functions require a singel argument to be passed, which is the id of the popup dialog to close

(function () {
  //register 
  popupUtilsShowPopup = function() {
        //the argument is required and cannot be missing
        if (arguments.length > 0) {
            var popupOpener = document.getElementById(arguments[0]);
            if (popupOpener != null && popupOpener != undefined) {
                adf.mf.api.amx.triggerBubbleEventListener(popupOpener,"tap");
            }
            else {
                adf.mf.log.Application.logp(adf.mf.log.level.WARNING,
                  "PopupUtils", "showPopup", "hidden button to launch
                   popup not found");
            }
        }
        else {
            adf.mf.log.Application.logp(adf.mf.log.level.WARNING,
              "PopupUtils", "showPopup", "Missing input argument");
        }
    }

    popupUtilsHidePopup = function() {
        //the argument is required and cannot be missing
        if (arguments.length > 0) {
            var popupCloser = document.getElementById(arguments[0]);

            if (popupCloser != null && popupCloser != undefined) {
                adf.mf.api.amx.triggerBubbleEventListener(popupCloser,"tap");
            }
            else {
                adf.mf.log.Application.logp(adf.mf.log.level.WARNING,
                "PopupUtils","showPopup", "hidden button to close popup
                 not found");
            }
        }
        else {
            adf.mf.log.Application.logp(adf.mf.log.level.WARNING,
             "PopupUtils", "hidePopup", "Missing input argument");
        }
    }

})();

Step 2) Registering the JS file with a MAF feature

To register the JavaScript file with a MAF feature, open the maf-feature.xml file (the file resides in the View Controller| Application Sources || META-INF directory). In the opened maf-feature.xml file, select the feature that needs programmatic access to a popup and select the "Content" tab. Click the green plus icon next to the "Includes" label and search for the JavaScript file in the project. The file type should be set to "JavaScript".

With this configuration, the custom JavaScript functions become available so they can be called from AdfmfContainerUtilities.invokeContainerJavaScriptFunction()

Step 3) About the Managed Bean (Java)

A managed bean is useed to hold the Java code that either launches or closes the popup. The managed bean doesn't hold state and therefore can be defined in any scope (shorter scopes are preferred). The sample actually shows and hides the popup in response to an action performed on another button. However, you can call the same code from any other component event, e.g. from a value change listener.

public class PopupHelper {

    boolean isPopupOpen = false;

    public PopupHelper() {

    }

    public void onPopupShowHideAction(ActionEvent actionEvent) {

        if (!isPopupOpen) {

         //the error object is filled with the reason for a JS exception.
         //If the variable is empty, no exception has occured. Note that
         //usually you use the return object to pass information from the
         //JS function back to MAF. In this case, the function doesn't
         //return a value and so its only used here to allow debugging
         //of exceptions

            Object errorMsg =
                AdfmfContainerUtilities.invokeContainerJavaScriptFunction
                (FeatureContext.getCurrentFeatureId(),"popupUtilsShowPopup",
                new Object[] {"_popShowId" });


            isPopupOpen = true;

            //wait a few seconds and then close the popup
            Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                  closePopup();
                  this.cancel();
                }

            },5000);

        }
            }


    private void closePopup() {

        if (isPopupOpen) {

            Object errorMsg =
               AdfmfContainerUtilities.invokeContainerJavaScriptFunction(
                FeatureContext.getCurrentFeatureId(),"popupUtilsHidePopup",
                new Object[] {"_popCloseId" });
            isPopupOpen = false;

        }

    }
}

Step 4) Configuring the hidden buttons

The hidden buttons (in this example they are hidden, but can be visible too) have the two behavior tags, amx:showPopupBehavior and amx:closePopupBehavior defined as shown in the AMX page source shown below

<amx:panelPage>
 ...
  <amx:commandButton text="_hiddenShow" id="_popShowId"
                     inlineStyle="visibility: hidden;">
      <amx:showPopupBehavior id="spb1" popupId="p1"
                        type="action" decoration="simple" alignId="pp1"
                        align="overlapMiddleCenter"/>
  </amx:commandButton>
  <amx:commandButton text="_hiddenClose" id="_popCloseId"
                      inlineStyle="visibility: hidden;">
      <amx:closePopupBehavior id="cpb1" popupId="p1"
                                     type="action"/>
  </amx:commandButton>



  <amx:commandButton text="Show / Hide Popup" id="cb3"
                     actionListener="#{PopupHelper.onPopupShowHideAction}"/>

</amx:panelPage>
  <amx:popup id="p1" autoDismiss="false"
          inlineStyle="font-size:large;">
          <amx:outputText value="This Popup is launched from Java in a
                         managed bean. Wait 5 seconds for Java to close
                          this dialog." id="ot2"/>
</amx:popup> 

Download Sample

The sample files can be downloaded in a MAF 2.1 workspace from here: http://www.oracle.com/technetwork/developer-tools/adf/learnmore/programmaticcloseofpopup-2420222.zip Run the AMX page within the sample workspace and press the command button in the opened mobile application. A dialog shows and stay up for 5 seconds before it closes down again.

Note:  If you experience problems, or like to comment on the solution, please post to the MAF forum on OTN: https://community.oracle.com/community/oracle-mobile/oraclemaf/content?customTheme=otn

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.

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