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.

Wednesday Feb 04, 2015

Migrating MAF apps with 3rd party Cordova plugins

Cordova is used in Oracle Mobile Application Framework (MAF) to provide access to device features.   MAF 2.1.0 uses Cordova version 3.6.3 on Android and 3.7.0 on iOS, dramatically increasing the number of available plugins that developers can leverage.

MAF 2.1.0 greatly simplifies the process of adding 3rd party Cordova plugins into a MAF app by internally using the plugman command-line interface to build the plugin and incorporate all the required artifacts into your MAF app.

So how do you migrate an existing MAF app that uses a 3rd party Cordova plugin to MAF 2.1.0?

The following steps and screenshots show the process of migrating the BarcodeDemo sample app from MAF 2.0.1 to MAF 2.1.0 using JDeveloper.

Step 1 – Download JDK 8 and update MAF extension

MAF 2.1.0 requires JDK 8. Download JDK 8 before updating the MAF extension to 2.1.0 via Check for Updates in JDeveloper. Following a restart, you will be prompted to enter the JDK 8 location on your local machine and to reconfigure the Android and iOS Platform Preferences in JDeveloper. Please refer to the Installation Guide for more detailed information.

Step 2 – Automatic app migration

Open your MAF app within JDeveloper and you will be prompted to allow JDeveloper to automatically migrate your app. The Installation Guide describes this process in more detail. 



Step 3 – Remove the old plugin code

In a later step, you will point JDeveloper to the location of the plugin’s source code on your local machine and JDeveloper will use plugman to build the plugin and incorporate it into your MAF app.

Before doing that, you should remove the plugin artifacts that you had manually added to your MAF app previously.  This includes:

  • Android native code found in Projects > ApplicationController > Application Sources > plugins.BarcodeScanner.Android
  • iOS native code found in Projects > ApplicationController > ApplicationSources > plugins.BarcodeScanner.iOS
  • JavaScript files found in Projects > ViewController > Web Content > plugins



Step 4 – Remove unnecessary iOS app icons and launch images

This is an optional step to reduce the size of your app. You can remove the following iOS app icons and launch images for unsupported devices and operating system versions, found in Application Resources > Resources > ios:

  • Default-1135h@2x.png
  • Default-Land.png
  • Default-Landscape-Ipad.png
  • Default-Landscape.png
  • Default-Landscape@2x.png
  • Default-Landscape@2x~Ipad.png
  • Default-Portrait-Ipad.png
  • Default-Portrait.png
  • Default-Portrait @2x.png
  • Default-Portrait @2x~Ipad.png
  • Default.png
  • Default@2x-Landscape.png
  • Icon-50.png
  • Icon-50@2x.png
  • Icon-57.png
  • Icon-57@2x.png
  • Icon-72@2x.png
  • Icon-80.png
  • Icon-144.png
  • icon-72.png
  • icon.png
  • icon@2x.png


Step 5 – Remove old plugin details from the Plugins UI

To complete the removal of the old plugin, you should remove the references to it from within the maf-application.xml Plugins UI, by selecting each reference and clicking the red ‘x’ icon.


Step 6 – Save all, Clean all

At this point, save all modifications and then do a ‘clean all’.

Step 7 – Download a new version of the plugin

MAF 2.1.0 requires you to have the plugin source code residing on your build machine, located on the same drive as your app source code, in a path that contains no spaces.

It is likely that there is a later version of the plugin that you have used previously which is compatible with the Cordova versions used by MAF.  You may even find that more recently someone has written a plugin that provides a better match to your requirements.

Anyone can build a Cordova plugin and they can choose to keep it to themselves or to publish it for use by others. The most common way to publish a Cordova plugin is to store it in a Git repository and to register it with the Cordova plugin registry.  Launch your browser and open http://plugins.cordova.io.


To find a barcode scanner plugin, type in “barcode” and search.


Click on com.phonegap.plugins.barcodescanner to view the details of this plugin.


You can see that this plugin requires Cordova version 3.0.0 or greater, works on Android and iOS, and was last updated 3 months ago to version 2.0.1. This page also provides a description and API documentation.

To access this plugin, click on the link to its Git repository.


To download this plugin, click on the link to Download Zip.

Once the plugin has been downloaded, extract the zip file contents into a location relative to your app’s source code which does not contain spaces. For example, you wish to extract the plugin into your app’s src folder as follows:


Step 8 – Add the plugin via the Plugins UI

To import the downloaded version of the plugin into your MAF app, return to the maf-application.xml Plugin UI in JDeveloper and click the green ‘+’ icon. Navigate to the location of the plugin’s top-level folder and click the Select button.


The details of the plugin, which are read from the plugin.xml file, will be displayed in the Plugins UI.


Step 9 – Update the JS that calls the plugin API

In most cases, you will need to update the JavaScript that calls the plugin API. Firstly, you no longer need to include the plugin’s JavaScript file because plugman will include this into your application at deploy-time. Secondly, the manner in which you invoke the plugin’s JS API may have changed.

For the BarcodeDemo sample app, this means editing the scanner.amx file to remove the inclusion of barcodescanner.js and to change window.plugins.barcodeScanner.scan to cordova.plugins.barcodeScanner.scan as described in the plugin’s README.md file.


Step 10 – Save all, deploy and test

All that’s left now is to save all modifications, deploy your app (most likely to a device, since many plugins won’t work on a simulator or emulator) and test it.

A tip for anyone using plugins with dependencies

If a 3rd party Cordova plugin has a dependency on another plugin, this will be defined in the plugin.xml file. At deploy-time, JDeveloper calls plugman to build and incorporate the 3rd party plugins defined in the maf-application.xml Plugins UI. If one of these plugins has a dependency, plugman will automatically attempt to download the required plugin's source code so it can be built and incorporated into your MAF app.

If your build machine is behind a firewall, this attempted download may cause the JDeveloper deployment to hang for a while before reporting error messages similar to the following:

[10:21:35 AM] Fetching plugin "https://github.com/apache/cordova-plugin-device" via git clone

[10:21:35 AM] Deployment Successful

[10:21:35 AM] Failed to install 'de.appplant.cordova.plugin.local-notification':Error: Command failed: Cloning into '/var/folders/h9/84x8qcsj391c355v3v137n580000gn/T/plugman/git/1423102819927'...

[10:21:35 AM] fatal: unable to access 'https://github.com/apache/cordova-plugin-device/': Failed to connect to github.com port 443: Operation timed out

[10:21:35 AM] at ChildProcess.exithandler (child_process.js:648:15)

[10:21:35 AM] at ChildProcess.emit (events.js:98:17)

[10:21:35 AM] at maybeClose (child_process.js:756:16)

[10:21:35 AM] at Socket.<anonymous> (child_process.js:969:11)

[10:21:35 AM] at Socket.emit (events.js:95:17)

[10:21:35 AM] at Pipe.close (net.js:465:12)

[10:21:35 AM] Command failed: Cloning into '/var/folders/h9/84x8qcsj391c355v3v137n580000gn/T/plugman/git/1423102819927'...

[10:21:35 AM] fatal: unable to access 'https://github.com/apache/cordova-plugin-device/': Failed to connect to github.com port 443: Operation timed out

The solution for this is:

  • Download and extract the required plugin as per Step 7 above.
  • Remove the dependent plugin from the Plugins UI.
  • Add the required plugin to the Plugins UI.
  • Add the dependent plugin to the Plugins UI. 

This will ensure that each plugin is built and incorporated into your MAF app in the correct order.

How to manage iOS log files in MAF 2.1

Where do I typically find the MAF log file?

MAF logs to a file named application.log.  On both the simulator this file is created in the application’s documents directory under a subdirectory named “logs”. Locating the actual log file for an application, particularly since iOS 8 and Xcode 6, is a bit tricky.  To find the file we have to understand the file system of the iOS simulator (and underlying iOS).  

The iOS simulator stores all it’s content in the CoreSimulator directory located in a path like:

/Users/mvakoc/Library/Developer/CoreSimulator

The simulator can have any number of devices and each device is uniquely identified by a UUID, so the contents of an actual device will be under a subdirectory with that UUID, like:

/Users/mvakoc/Library/Developer/CoreSimulator/Devices/00DEC99D-0651-43A7-9AC8-319680B85C10

The file structure isolates applications and their data into various containers.  Containers are a way to separate an application’s private resources (the application itself) from a location that may be shared with others (e.g. a directory shared between an application and an extension it bundles with it) and the data containers.  The application, when installed, will get put in the Bundle container location, something like

/Users/mvakoc/Library/Developer/CoreSimulator/Devices/00DEC99D-0651-43A7-9AC8-319680B85C10/data/Containers/Bundle/Application/66D7BCF4-BDF2-4334-85AE-21DD7E3636C3/Oracle_ADFmc_Container_Template.app

The data container is the location for the documents (user files), library (caches, preferences), and temporary files for a given application.  An iCloud enabled application can have multiple data containers in a given application.  MAF doesn’t use this so there will only be one data container containing the Documents location.  It will again be stored under a UDID in the sandbox area, like:

 /Users/mvakoc/Library/Developer/CoreSimulator/Devices/00DEC99D-0651-43A7-9AC8-319680B85C10/data/Containers/Data/Application/21BAF3C1-4244-4383-8584-9713B6979A01

The actual log file will be contained in the documents directory contained inside, like:

/Users/mvakoc/Library/Developer/CoreSimulator/Devices/00DEC99D-0651-43A7-9AC8-319680B85C10/data/Containers/Data/Application/21BAF3C1-4244-4383-8584-9713B6979A01/Documents/logs/application.log

In short, it is very difficult to an outside observer (outside the application itself) to locate these resources.

On a device you have much less insight into the directory structure.  If the application has been installed to the device using a developer provisioning profile you can use the Xcode devices window to view and download the installed applications container, which will contain the log file

If you are using an App Store or distribution installed application it will be very difficult to obtain the log from the device.  You really need to add the ability to get the log file from the application itself (e.g. use email to send the log file.)


What a pain.  Can I put the log file somewhere easier to get?

Yes.  When running on the iOS simulator you can use the -consoleRedirect=/path/to/log/file.txt to send the log file to a more desirable and easier to find location.  You can specify these options directly in Xcode (if you are launching from the Xcode project) or directly in the Run/Debug configuration in JDeveloper.


There are some limitations to this approach.  You must launch the application with this argument.  Directly launching the application already installed on a device will cause it to launch without this argument so the contents will go back to the regular location.   Use the JDeveloper run configuration (the green triangle) to launch and run the app and this should make life easier.

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