Implementing GPS Detection in MAF

Oracle MAF does not currently provide an out of the box solution for detecting whether GPS is enabled (at least as of version 2.1.2). This is problematic when using a function such as startLocationMonitor, as when there is no GPS the app will lock up for about 15 seconds and then display an ADF exception that cannot be caught.

This article will show one approach to solving this problem.

Step 1. Create the MAF app

We’re going to set up an app with a single welcome feature, containing a page which displays our gps status. We’ll also put a call to start Location Monitor which will trigger off of the gps status.

Step through the new MAF application wizard – I have called my app GPSTest and given it an application prefix of com.rubiconred.test.gpstest. Create a feature called welcome and set it to be an amx page. Create the page and call it welcome.amx.

We need to put the call to the Cordova plugin somewhere – so let’s embed that in a new Javascript page. Right click on the View Controller-> Web Content directory and choose to Create Javascript file. Call it gps.js and add this to the feature reference as shown below.

At this point we are missing the crucial piece – the Cordova plugin! The one used in this example can be found at https://github.com/fastrde/phonegap-checkGPS . Note that there seem to now be several different plugins available to do similar things. Extract the zip and then edit the maf-application.xml to point to the plugin directory. Also tick the geolocation plugin as this is a dependency (if this isn’t ticked then the Jdeveloper build will connect and download the plugin).

Update gps.js and paste 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=”Header” id=”ot1″/>
    </amx:facet>

    <amx:facet name=”primary”>
      <amx:commandButton actionListener=”#{bindings.resetFeature.execute}” text=”resetFeature” disabled=”#{!bindings.resetFeature.enabled}” id=”cb3″/>
    </amx:facet>

    <amx:facet name=”secondary”>
        <amx:commandButton id=”cb2″/>
    </amx:facet>

  </amx:panelPage>

</amx:view>  

Deploy the app, check that Location is on and launch the app.

Turn off location on the device, click resetFeature and the following should be displayed.

Step 2. Extend to a bean

It’ll be easier within the app if this data was available through a managed bean. Mainly as it is easier to embed into EL expressions and also abstracts us away from a having to invoke the javascript from various places. To do this create a new bean called GPSBean and paste the following:-

public class GPSBean {

    private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

    public GPSBean() {
        super();
    }

    private boolean status;

    public void setStatus(boolean status) {
        boolean oldStatus = this.status;
        this.status = status;
        propertyChangeSupport.firePropertyChange(“status”, oldStatus, status);
    }

    public boolean isStatus() {
             return status;
    }

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

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

This defines an object with a single field of status. Update the adfc-mobile-config.xml to add this object into the pageFlowScope as shown below.

The subtle change to make is to modify the getter for the status field, through adding a line to invoke a Javascript function that will be responsible for deriving the new value. Update the isStatus function as follows:-

    public boolean isStatus() {

        String result = (String)AdfmfContainerUtilities.invokeContainerJavaScriptFunction(“com.rubiconred.test.gpstest.welcome”,”application.checkGPSStatus”, new Object[] {} );

        return status;
    }

This makes a call out to a checkGPSStatus method which has to be added to gps.js. Paste the following over the existing javascript. Notice that the old direct call toCheckGPS.check has been removed and is now embedded in the checkGPSStatus function. This also stops it running each time the page is loaded.

(function() {

if (!window.application) window.application = {};

    application.checkGPSStatus = function() {

        CheckGPS.check(function(){

        //GPS is enabled!
        adf.mf.api.invokeMethod(“com.rubiconred.test.gpstest.mobile.GPSBean”, “setGPSStatus”, true , onInvokeSuccess, onFail);

    },

   function(){

       //GPS is disabled!
       adf.mf.api.invokeMethod(“com.rubiconred.test.gpstest.mobile.GPSBean”, “setGPSStatus”, false , onInvokeSuccess, onFail);

    });

    return true;
  };

  function onFail() {
     //   alert(“It failed”);
  };

  function onInvokeSuccess(param) {

  };

})();

This new Javascript function makes a call to setGPSStatus on the GPSBean. This will be used to trigger the setting of the status field. Copy the following method intoGPSBean

   public void setGPSStatus(boolean status) {

         ValueExpression ve = AdfmfJavaUtilities.getValueExpression(“#{pageFlowScope.gpsBean.status}”, Boolean.class);

         ve.setValue(AdfmfJavaUtilities.getAdfELContext(), status);

    }

Note: The Javascript function is calling the Class directly (not an Object instance) it is necessary to call out to the pageFlowScope object we are using.

Finally, the previous method of resetting the welcome page was a bit of a kludge. It was needed to force the page to refresh and for the Javascript to re-run on page load. Let’s do this a different way, by adding a button to the page to do the trigger of the status check. Aligned with this, we’ll add some output text fields that are based on the status of the field, via an EL expression. Copy the following below onto the welcome page.

<amx:outputText value=”GPS is ON” id=”ot2″ rendered=”#{pageFlowScope.gpsBean.status}”/>

<amx:outputText value=”GPS is OFF” id=”ot3″ rendered=”{pageFlowScope.gpsBean.status==false}”/>

<amx:commandButton text=”refresh status” id=”cb1″ actionListener=”{pageFlowScope.gpsBean.updateStatus}”/>  

Update GPSBean with a new function that the button will call.

    public void updateStatus(ActionEvent actionEvent) {

        // Trigger a check of the GPS

       this.isStatus();

    }

This is to simulate that calling GPSBean.status from anywhere within the application will trigger an update of the status.

Deploy and test with location on. GPS in ON will show as location is available. The EL expression #{pageFlowScope.gpsBean.status} is used to show this output text and so it will show when the status is true.

Clicking refresh status will trigger an update and as location is available, it remains as GPS is ON. Turn off Location on the device and click the refresh status button. As shown below the message becomes GPS is OFF. This is due to the evaluation of the status being false.

Step 3. Add call to startLocationMonitor

The original intent was to add a page that was able to call location monitoring without throwing an error. Now that the EL expression exists, it is relatively easy to add the call as required.

Drag the DeviceFeature->startLocationMonitor method from the Data Controls onto the welcome.amx page. Select the option to add as a button and then at the prompt enter true, 10000 and pageFlowScope.gpsBean.locationUpdated. This tells the in-built location monitoring control to use high accuracy on the results, to update every 10 seconds and the endpoint to send location details to.

This will add a button to the page, which will allow location monitoring to be triggered. However, our use case is to trigger automatically; there are several options at this point, but for our use case the easiest is to simply update the setGPSStatus method to execute a binding. Paste the code below over the existing method.

   public void setGPSStatus(boolean status) {

        ValueExpression ve = AdfmfJavaUtilities.getValueExpression(“#{pageFlowScope.gpsBean.status}”, Boolean.class);

        ve.setValue(AdfmfJavaUtilities.getAdfELContext(), status);

        // check whether locationmonitor should now be triggered
        if (status==true){
             AdfELContext adfELContext = AdfmfJavaUtilities.getAdfELContext();

             MethodExpression me = AdfmfJavaUtilities.getMethodExpression(“#{bindings.startLocationMonitor.execute}”, Object.class, new Class[]{});

             me.invoke(adfELContext, new Object[]{});
        }
    }

To summarize – these actions have added a call to startLocationMonitor which is controlled through the Managed Bean and the status of the GPS being available or not. The final step is to add in the method called when startLocationMonitor passes back an update. Edit the GPSBean and paste the following at the end of the class:

    private double longitude = 0;

    private double latitude = 0;

    private boolean locationDetermined = false;

    public void setLongitude(double longitude) {
        double oldLongitude = this.longitude;
        this.longitude = longitude;

        propertyChangeSupport.firePropertyChange(“longitude”, oldLongitude, longitude);
    }

    public double getLongitude() {
        return longitude;
    }

    public void setLatitude(double latitude) {
        double oldLatitude = this.latitude;
        this.latitude = latitude;

        propertyChangeSupport.firePropertyChange(“latitude”, oldLatitude, latitude);
    }

    public double getLatitude() {
        return latitude;
    }

    public void setLocationDetermined(boolean locationDetermined) {
        boolean oldLocationDetermined = this.locationDetermined;

        this.locationDetermined = locationDetermined;
        propertyChangeSupport.firePropertyChange(“locationDetermined”, oldLocationDetermined, locationDetermined);
    }

    public boolean isLocationDetermined() {
        return locationDetermined;
    }

    public void locationUpdated(Location currentLocation) {

        this.setLatitude(currentLocation.getLatitude());
        this.setLongitude(currentLocation.getLongitude());

        // track location has been calculated
        if (this.getLatitude()!=0 && this.getLongitude()!=0) {
                     this.setLocationDetermined(true);
        }
    }

The longitude and latitude fields are used to store location details, with locationDetermined being used to track that an actual reference has been found. This could be used later as a way to show or hide certain fields (e..g if you had a distance to nearest store displayed on the page).

Finally, the welcome page needs to be updated to show these details. Go back to welcome.amx and paste the following under the refresh button. The button forstartLocationMonitor can also be removed as we are executing this via the binding trigger.

<amx:panelGroupLayout id=”pgl1″ layout=”vertical” rendered=”{pageFlowScope.gpsBean.locationDetermined}”>  
      <amx:outputText value=”#{pageFlowScope.gpsBean.longitude}” id=”ot4″/>
      <amx:outputText value=”Latitude #{pageFlowScope.gpsBean.latitude}” id=”ot5″/>
</amx:panelGroupLayout>  

Deploy the app and launch with Location On.

The latitude and longitude will now trigger automatically.

Close the app, turn off Location on the device and launch again. There is no error displayed as location monitoring has not been triggered.

*

Turn on Location on the device , click refresh status and in a few seconds the location will be displayed.

Note: Testing with the location monitor service shows that the interval is largely ignored (at least on Android). Updates will only fire when it is determined the device has travelled a distance worth notifying. Equally, these updates can come every 0.5 of a second, rather than every 10 seconds. If you are struggling with getting the function working – go for a walk! You may need to go a couple of hundred metres depending on the network and whether GPS or Wi-Fi location is being used.

Step 4. Extend to ‘real-time’

There is one further extension that could be added to ensure a ‘real time’ GPS status update. If this is important to the app then the following change to the gps.jsfunctions will check every 5 seconds for the latest status.

function onInvokeSuccess(param) {  
    // set timeout to trigger in 5 seconds
    setTimeout(function(){application.checkGPSStatus()}, 5000);
};

Now when the location is turned on or off on the device the update will flow through automatically within 5 seconds. However, it would be worth assessing the need for this as this will impact on the battery and performance in general. It is likely that the app only needs to know when location is available (i.e. when it is possible to call location monitor without an error). In this instance it may be better to move the setTimeout call to only occur when location isn’t available. The Javascript for taking this approach is shown below and replaces the current gps.js :-

(function() {

if (!window.application) window.application = {};

   application.checkGPSStatus = function() {
     CheckGPS.check(function(){

      //GPS is enabled!
      adf.mf.api.invokeMethod(“com.rubiconred.test.gpstest.mobile.GPSBean”, “setGPSStatus”, true , onInvokeSuccess, onFail);
     },

   function(){
    //GPS is disabled!
    adf.mf.api.invokeMethod(“com.rubiconred.test.gpstest.mobile.GPSBean”, “setGPSStatus”, false , onInvokeSuccessDisabled, onFail);

  });
  return true;
};

    function onFail() {
        //   alert(“It failed”);

        // setTimeout to trigger in 5 seconds
        setTimeout(function(){application.checkGPSStatus()}, 5000);
    };

    function onInvokeSuccessDisabled(param) {
        // no location, so try again in 5 seconds
        setTimeout(function(){application.checkGPSStatus()}, 5000);  
    };

    function onInvokeSuccess(param) {
    };
})();

Summary

This article has shown how you can build a managed bean method that allows the evaluation of the GPS Status and subsequent triggering of location monitoring. It also shows a quick method for making this real-time (although care should be taken in doing this).

rubiconred

Read more posts by this author.

Subscribe to Oracle PaaS, Oracle Middleware and DevOps

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!