9 Using Bindings and Creating Data Controls

This chapter describes how to use data bindings, data controls, and the ADF data binding expression language (EL) in ADF Mobile applications. This chapter also covers validation and data change events.

This chapter includes the following sections:

9.1 Introduction to Binding Layer Components and Data Controls

ADF Model implements two concepts that enable the decoupling of the user interface technology from the business service implementation: data controls and declarative bindings. Data controls abstract the implementation technology of a business service by using standard metadata interfaces to describe the service's operations and data collections, including information about the properties, methods, and types involved. Using JDeveloper, you can view that information as icons that you can drag and drop onto a page. Declarative bindings abstract the details of accessing data from data collections in a data control and invoking its operations. At runtime, the ADF Model layer reads the information describing the data controls and bindings from the appropriate XML files and then implements the two-way connection between the user interface and the business service.

The group of bindings supporting the UI components on a page are described in a page-specific XML file called the page definition file. The ADF Model layer uses this file at runtime to instantiate the page's bindings. These bindings are held in a request-scoped map called the binding container, accessible during each page request using the EL expression #{bindings}. This expression always evaluates to the binding container for the current page. You can design a databound user interface by dragging an item from the Data Controls panel and dropping it on a page as a specific UI component. When you use data controls to create a UI component, JDeveloper automatically creates the code and objects needed to bind the component to the data control you selected.

In ADF Mobile, data controls behave similarly to the way they work in Oracle ADF. The DeviceFeatures data control appears within the Data Controls panel in JDeveloper, allowing you to drag and drop the primary data attributes of data controls to your application as (text) fields, and the operations of data controls as command objects (buttons). These drag and drop actions will generate EL bindings in your application in the appropriate properties for the controls that are created. The normal ADF bindings for those actions (represented by a general DataControls.dcx file, to point at the data control source, and the page bindings, to link the specific page's reference to the data control) will be present, allowing the runtime to process the bindings when your application executes.

For more information on data binding and data controls, see the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework and the Oracle Fusion Middleware Java EE Developer's Guide for Oracle Application Development Framework.

9.2 Understanding EL Support

ADF Mobile provides support for the use of the Expression Language (EL) in its ADF Mobile AMX application feature.

You use the EL to enable data binding. For an overview of the use of EL with Oracle ADF, see the following:

ADF objects exist within different scopes, such as the application scope, session scope, page flow scope, and so on (see "About Object Scope Lifecycles" in Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework). ADF Mobile, on the other hand, supports the following scopes:

  • Application scope

  • Page flow scope

  • Preference scope

  • View scope

  • Device scope

EL expressions defined in the application scope namespace are available for the life of the application. With ADF Mobile, you can define an application scope in one view of an application, and then reference it in another.

9.2.1 ADF Mobile AMX EL Implementation

The ADF Mobile AMX EL implementation is based on the Java Unified Expression Language (JUEL) project and follows Expression Language Specification Version 2.1 (available from the JUEL project page at http://juel.sourceforge.net/, and referred to hereinafter as “the specification”), with the following exceptions:

9.2.1.1 Immediate and Deferred Evaluation

As described in "1.2.1: Eval-expression" in the specification, expressions may be evaluated immediately or deferred. In the ADF Mobile AMX EL implementation, expressions are parsed when the page metadata is loaded, at which point the owning component holds on to a reference to the parsed object. The expression is not actually evaluated until the component needs it for rendering a value. Because ADF Mobile AMX supports only the deferred semantics, an expression using the immediate construction expression ("${}") still parses, but behaves the same as a deferred expression ("#{}").

9.2.1.2 Enumerated Types

As described in "1.17: Enums" in the specification, using a literal string to coerce to the value of an enum type is not supported, because the required underlying enum operations are not supported on J2ME.

9.2.2 How to Reference Binding Containers

The active screen's binding container can be referenced by the root EL expression "#{bindings}". Another screen's binding container can be referenced through the expression "#{data.PageDefName}". The ADF Mobile AMX binding objects are referenced by name from the binding container "#{bindings.Name}".

Table 9-1 lists the properties that you can use in EL expressions to access values of the ADF Mobile AMX binding objects at run time. The table lists these properties in alphabetical order.

Table 9-1 Runtime Properties

Runtime Property Description Iterator Action Attribute Tree

class

Returns the Java class object for the runtime binding.

Yes

Yes

Yes

Yes

collectionModel

Exposes a collection of data. EL expressions used within a component that is bound to a collectionModel can be referenced with a row variable Foot 1 , which will resolve the expression for each element in the collection.

No

No

No

Yes

currentRow

Provides access to the current row pointed to by an iterator.

Yes

No

No

No

dataControl

Returns the iterator's associated data provider.

Yes

No

No

No

dataProvider

Available as a child of currentRow. Provides access to the data structure used in the iterator.

Yes

No

No

No

enabled

Returns true or false, depending on the state of the action binding. For example, the action binding may be enabled (true) or disabled (false) based on the currency (as determined, for example, when the user clicks the First, Next, Previous, Last navigation buttons).

No

Yes

No

No

execute

Invokes the named action or methodAction binding when resolved.

No

Yes

No

No

hints

Returns a list of name-value pairs for UI hints for all display attributes to which the binding is associated. The following named values are supported:

  • format: The format to be used for the current attribute.

  • label: The label to display for the current attribute.

  • updateable: Returns true if the current attribute can be written to.

No

No

Yes

Yes

inputValue

Returns or sets the value of the current attribute.

No

No

Yes

No

items

Returns the list of values associated with the current list enabled attribute.

Yes

No

No

No

label

Available as a child of hints or direct child of an attribute. Returns the label (if supplied by control hints) for the first attribute of the binding.

No

No

Yes

Yes

rangeSize

Returns the range size of the iterator binding's row set.

Yes

No

No

Yes

rangeStart

Returns the absolute index in a collection of the first row in range.

Yes

No

No

No

rowCount

Returns the total number of rows in the collection.

Yes

No

No

No

updateable

Available as a child of hints or direct child of an attribute. Returns true if the current attribute is updateable.

No

No

Yes

Yes

viewable

Available as a child of Tree. Resolves at runtime whether this binding and the associated component should be rendered or not.

No

No

No

Yes


Footnote 1  The EL term row is used within the context of a collection component; row simply acts as an iteration variable over each element in the collection whose attributes can be accessed by an ADF Mobile AMX binding object when the collection is rendered. Attribute and list bindings can be accessed through the row variable. The syntax for such expressions will be the same as those used for accessing binding objects outside of a collection, with the row variable prepended as the first term: #{row.bindings.Name.property}.

9.2.3 EL Events

EL events play a significant role in the functioning of the ADF Mobile AMX UI, as it enables expressions with common terms to update in sync with each other.

EL expressions can refer to values in various contexts. Example 9-1 shows the creation of two Input Number Slider components, with each component tied to an applicationScope value. The output text then uses EL to display a simple addition equation along with the calculated results. When the framework parses the EL expression in the output text labels, it determines that the expression contains references to two values and creates event listeners (see Section 8.10, "Using Event Listeners") for the output text on those two values. When the value of the underlying expression changes, an event is generated to all listeners for that value.

Note:

If you are referencing properties on a managed bean (as opposed to scope objects) you have to add the listeners. For more information, see Section 9.2.4.2, "ADF Managed Beans."

Example 9-1 Generating EL Events with Two Components

<amx:inputNumberSlider id="slider1" label="X" value="#{applicationScope.X}"/>
<amx:inputNumberSlider id="slider2" label="Y" value="#{applicationScope.Y}"/>
<amx:outputText id="ot1" value="#{applicationScope.X} + 
       #{applicationScope.Y} = #{applicationScope.X + applicationScope.Y}"/>

In Example 9-1 two components are updating one value each, and one component is consuming both values. Example 9-2 shows that the behavior would be identical if a third Input Number Slider component is added that references one of the existing values.

Example 9-2 Generating EL Events with Three Components

<amx:inputNumberSlider id="slider1" label="X" value="#{applicationScope.X}"/>
<amx:inputNumberSlider id="slider2" label="Y" value="#{applicationScope.Y}"/>
<amx:outputText id="ot1" value="#{applicationScope.X} + 
       #{applicationScope.Y} = #{applicationScope.X + applicationScope.Y}"/>
<amx:inputNumberSlider id="slider3" label="X" value="#{applicationScope.X}"/>

In Example 9-2, when either Input Number Slider component updates #{applicationScope.X}, the other is automatically updated along with the Output Text.

9.2.3.1 Configuration Properties

Any <adf-property> elements in the application's adf-config.xml file will be exposed through EL as children of the #{applicationScope.configuration} node. So, for example, if adf-config.xml looks like the following example, evaluating #{applicationScope.configuration.key1} will yield value1, and evaluating #{applicationScope.configuration.key2} will yield vaue2.

<?xml version="1.0" encoding="windows-1252" ?>
<adf-config xmlns="http://xmlns.oracle.com/adf/config" xmlns:config="http://xmlns.oracle.com/bc4j/configuration"
            xmlns:sec="http://xmlns.oracle.com/adf/security/config">
  <adf-properties-child xmlns="http://xmlns.oracle.com/adf/config/properties">
    <adf-property name="key1" value="value1"/>
    <adf-property name="key2" value="value2"/>
  </adf-properties-child>
</adf-config>

In addition, #{applicationScope.configuration.accessibilityEnabled} evaluates to true if accessibility mode is enabled on the device. On iOS devices, this means that VoiceOver is activated. On Android devices, this means TalkBack is activated.

9.2.4 EL Expression Builder

You can use the JDeveloper Expression Builder to create EL expressions by selecting values from variables and operators. The Expression Builder can be invoked from the Property Inspector for any EL-enabled property. For more information about using the Expression Builder, see "How to Create an ADF Data Binding EL Expression" in Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

There are two EL types in Oracle ADF:

  • Dynamic EL: ${EL expression}

  • Deferred EL: #{EL expression}

Since ADF Mobile does not support dynamic EL type and treats ${EL expression} as #{EL expression}, you should use the hash sign ( # ) prefix when defining expressions. For more information, see Section 9.2.1.1, "Immediate and Deferred Evaluation."

The following categories are available in the Expression Builder for the ADF Mobile AMX pages:

9.2.4.1 ADF Bindings

This section lists the options available under the ADF Bindings category. The bindings and data nodes display the same set of supported bindings and properties. Table 9-2 lists available binding types along with the properties that are supported for each binding type.

  • bindings

    Table 9-2 lists the available binding types along with the properties that are supported for each binding type.

  • data

    Table 9-2 lists the available binding types along with the properties that are supported for each binding type.

  • securityContext

    Supported properties:

    • authenticated

    • userGrantedPrivilege

    • userInRole

    • userName

Table 9-2 Supported Binding Types

Binding Type Properties

accessorIterator

class

currentRow: dataProvider

name

rangeSize

action

classenabled

execute

name

attributeValues

class

format

hints: format, label, updateable

inputValue

items

iteratorBinding

label

name

updateable

button

class

format

hints: format, label, updateable

inputValue

items

label

name

updateable

invokeAction

always

deferred

iterator

class

currentRow: dataProvider

name

rangeSize

list

class

format

hints: format, label, updateable

inputValue

items

label

name

updateable

methodAction

class

enabled

execute

name

operationInfo

paramsMap

result

methodIterator

class

currentRow: dataProvider

name

rangeSize

tree

class

collectionModel: <AttrName>

hints: format, label, updateable, <AttrName>

iteratorBinding

name

rangeSize

viewable

variableIterator

class

currentRow: dataProvider

name


9.2.4.2 ADF Managed Beans

You can create and use managed beans in an ADF Mobile application to store additional data or to execute custom code. Adding a managed bean to an ADF Mobile application is done the same way as adding one to a Fusion Web Application.

For more information, see the following:

Note:

Carefully consider the binding styles you use when configuring components. More specifically, combining standard bindings with managed bean bindings will frequently result in misunderstood behaviors because the class instances are unlikely to be the same between the binding infrastructure and the managed bean infrastructure. If you mix bindings, you may end up calling behavior on an instance that isn't directly linked to the UI.

Supported ADF Mobile managed beans scopes are as follows:

  • applicationScope: ADF Managed Beans > applicationScope node contains everything that is defined at the application level (for example, application-scoped managed beans).

  • pageFlowScope: ADF Managed Beans > pageFlowScope node contains everything that is defined at the page flow level (for example, page flow-scoped managed beans).

  • viewScope: ADF Managed Beans > viewScope node contains everything that is defined at the view level (for example, view-scoped managed beans).

When the ADF Managed Beans category is selected in the Expression Builder, the Create Managed Bean button appears. Click this button to create Managed Beans directly from the Expression Builder.

Figure 9-1 shows an example of the ADF Mobile Managed Beans contents and the Create Managed Bean button.

Figure 9-1 ADF Managed Bean Contents

ADF Mobile Managed Bean contents

The ADF Mobile runtime will register itself as a listener on managed bean property change notifications so that EL expressions bound to UI components that reference bean properties will update automatically if the value of the property changes. Sourcing these notifications requires some additional code in the beans' property accessors. To automatically generate the necessary code to source notifications from your beans' property accessors, select the Notify listeners when property changes checkbox in the Generate Accessors dialog (see Figure 9-2).

Figure 9-2 Notify Listeners When Property Changes

Generate Accessors dialog

It is not necessary to add this code to simply reference bean methods or properties through EL, but it is necessary to keep the rendering of any EL expressions in the active form that depend on values stored in the bean current if those values change, especially if the change is indirect, such as a side effect of executing a bean method that changes one or more property values. For information about property changes and the PropertyChangeSupport class, see Section 9.7, "Data Change Events."

Note:

If you declare a managed bean within the applicationScope of a feature but then try to reference that bean through EL in another feature at design time, you will see a warning in the design time about invalid EL. This warning is due to the fact that the design time cannot find a reference in the current project for that bean. You can reference that bean at runtime only if you first visit the initial feature where you declared the bean and the bean is instantiated before you access it through EL in another feature. This is not the case for the PreferenceValue element as it uses the Name attribute value as the node label.

9.2.4.3 ADF Mobile Objects

The following options are available under the ADF Mobile Objects category:

  • applicationScope: The applicationScope node contains everything that is defined at the application level (for example, application-scoped managed beans).

  • deviceScope: The deviceScope node contains everything that is defined at the device level (for example, device-scoped managed beans). ADF Mobile supports the following deviceScope properties:

    • device

      • model

      • name

      • os

      • phonegap

      • platform

      • version

    • hardware

      • hasAccelerometer

      • hasCamera

      • hasCompass

      • hasContacts

      • hasFileAccess

      • hasGeolocation

      • hasLocalStorage

      • hasMediaPlayer

      • hasMediaRecorder

      • hasTouchScreen

      • networkStatus

      • screen (availableHeight, availableWidth, height, width)

  • pageFlowScope: The pageFlowScope node contains everything that is defined at the page flow level (for example, page flow-scoped managed beans).

  • preferenceScope: The preferenceScope node contains all the application and feature preferences.

    • Application preferences are available under ADF Mobile Objects > preferenceScope > application.

    • Feature preferences are available under ADF Mobile Objects > preferenceScope > feature > featureId

    Figure 9-3 shows an example of preference elements under the preferenceScope node.

    Figure 9-3 Preference Elements Under the preferenceScope Node

    Preference elements under the preferenceScope node

    Preference elements use the Id attribute value as the node label in the Expression Builder, except for the PreferenceValue element. The PreferenceValue element uses the Name attribute value as the node label in the Expression Builder.

    Note:

    Where string tokens in EL expressions contain a dot (".") or any special character, or a reserved word like default, the Expression Builder surrounds such string tokens with a single quote and bracket. When the feature ID or preference component ID contains a dot, the Expression Builder displays each part of the ID that is separated by a dot as a separate property in the preferenceScope hierarchy. The expression generated also takes each part of the ID separated by a dot as a separate property.

    Following are some sample preferenceScope EL expressions:

    Example 9-3 Feature ID Containing "."

    "#{preferenceScope.feature.oracle.hello.SampleGroup1.label}"
    

    Example 9-4 Attribute Name Is a Reserved Word

    "#{preferenceScope.application.OracleMobileApp.Edition['default']}"
    
  • viewScope: This node contains everything that is defined at the view level (for example, view-scoped managed beans).

  • row: The row variable node is a shortcut to the collectionModel. Its name is the value of the var attribute of the parent Iterator, List View, or Carousel component. This node supports the following directory structure:

    • bindings

      • AttrName

      • class

      • format

      • hints

      • inputValue

      • items

      • iteratorBinding

      • label

      • name

      • updateable

    • AttrName

      • class

      • format

      • hints

      • inputValue

      • items

      • iteratorBinding

      • label

      • name

      • updateable

    • rowKey

    Figure 9-4 shows an example of the row variable node contents.

    Figure 9-4 Row Variable Contents

    Example of the row variable contents
  • viewControllerBundle

    This is the name of the resource bundle variable that points to a resource bundle defined at the project level.This node is shown only after the amx:loadBundle element has been dropped and a resource bundle has been created. The name of this node will vary as it depends on the variable name of amx:loadBundle. This node will display all strings declared in the bundle.

    Figure 9-5 shows an example of the contents of the viewControllerBundle node. Example 9-5 shows an example of AMX code for viewControllerBundle.

    Figure 9-5 Contents of the viewControllerBundle Node

    Contents of the viewControllerBundle Node

    Example 9-5 AMX Code Sample of the loadBundle Element

    <amx:loadBundle basename="mobile.ViewControllerBundle" var="viewcontrollerBundle"/>
    

9.2.4.4 Method Expression Builder

Table 9-3 shows properties that have the Method Expression Builder option available in the Property Inspector instead of the Expression Builder option. The only difference between them is that the Method Expression Builder filters out the managed beans depending on the selected property.

Table 9-3 Editable Attributes

Property Element

binding

actionListener

action

commandButton

actionListener

commandButton

action

commandLink

actionListener

commandLink

action

listItem

actionListener

listItem

valueChangeListener

inputDate

valueChangeListener

inputNumberSlider

valueChangeListener

inputText

valueChangeListener

selectBooleanCheckbox

valueChangeListener

selectBooleanSwitch

valueChangeListener

selectManyCheckbox

valueChangeListener

selectManyChoice

valueChangeListener

selectOneButton

valueChangeListener

selectOneChoice

valueChangeListener

selectOneRadio

moveListener

listView


9.2.4.5 Non EL-Properties

Table 9-4 shows the properties that do not have the EL Expression Builder option available in the Property Inspector, because they are not EL-enabled.

Table 9-4 Non EL-Properties

Property Element

name

facet

var

iterator

var

listView

var

loadBundle

group

validationBehavior


9.3 Understanding Binding Layer Components

The ADF Mobile runtime uses the default behavior (illustrated in Example 9-6) to invoke the action for a particular operation, because it must be called on repeat showings of the page after it has been loaded (not only for the initial load).

Example 9-6 invokeAction in ADF Mobile AMX

<invokeAction id="callSetCurrentRowWithKeyValue" 
              Binds="setCurrentRowWithKeyValue" 
              Refresh="default" 
              RefreshCondition="#{requestScope.partyId != null}"/>

For information on the recognized values of the Refresh attribute and their meaning, see Table 9-5, "Refresh Values and the Corresponding Conditions to Invoke".

The ADF Mobile runtime can detect the following conditions for issuing notifications to invoke executables:

  • A: Each time a page is shown.

  • B: On any action that changes the state of the model (like setting an attribute value or navigating to a new row).

The runtime executes an invokeAction on the above conditions according to values of its Refresh and RefreshCondition attributes, as listed in Table 9-5, "Refresh Values and the Corresponding Conditions to Invoke". For more information, see "What You May Need to Know About Using the Refresh Property Correctly" in Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

Table 9-5 Refresh Values and the Corresponding Conditions to Invoke

Refresh Value Condition to Invoke

always - Fire the invokeAction every time a page is navigated to regardless of the state of the binding container. In terms of the runtime lifecycle, this will be on every setContext message.

A and B

deferred - Fire the invokeAction when a page is navigated to and its bindings are loaded and initialized. In terms of the runtime lifecycle, this will be on every setContext message that initializes a binding container. If subsequent navigations to the page do not reload the bindings, then the action will not fire. This is the default behavior.

A

default - Always set to deferred by default.

A


If a RefreshCondition expression is supplied, it will be evaluated at each potential execution of the invokeAction. If it evaluates to false, execution is skipped on that occurrence.

Note:

For iterator executables, deferred is the standard behavior: the iterator does not perform its initial Refresh until an EL expression that is dependent on it is evaluated. Any invokeAction executable in a page definition file must have a value other than the default (deferred) for its refresh attribute, or it will not be refreshed and invoked.

For more information on the use of bindings in ADF Mobile, see the following:

9.3.1 Sequencing

By default, invokeActions declared with the same Refresh value execute in the order they are declared in the pagedef.

Note:

The predecessor still executes or not based on its own Refresh and RefreshCondition values.

9.3.2 Validation of EL Bindings for ADF Mobile AMX Pages

In ADF Mobile, validation of EL bindings for the ADF Mobile AMX pages is done using the same mechanism for validating EL for JSF pages, behaving similarly to Oracle ADF. The EL constructs are validated against the page bindings and method references are validated against the managed beans.

For more information on validation, see Section 9.6, "Performing Validation."

9.4 Creating and Using the Bean Data Control

Java bean data controls obtain their data structure from POJOs (plain old Java objects). To create a Java bean data control, right-click a Java class file (in the Applications window), and choose Create Data Control. Creating bean data controls is very similar to creating EJB data controls.

Note:

If the Java bean is using a background thread to update data in the UI, you need to manually call oracle.adfmf.framework.api.AdfmfJavaUtilities.flushDataChangeEvent. For information about the flushDataChangeEvent method, see Section 9.7, "Data Change Events."

For more information, see the "Data Controls in Fusion Web Applications" section in the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework and the "Exposing Business Services with ADF Data Controls" section in the Oracle Fusion Middleware Java EE Developer's Guide for Oracle Application Development Framework.

9.4.1 What You May Need to Know About Serialization of Bean Class Variables

ADF Mobile does not serialize to JavaScript Object Notation (JSON) data bean class variables that are declared as transient. If you want to avoid serialization of a chain of nested objects, you should define them as transient. This strategy also helps to prevent the creation of cyclic objects due to object nesting.

Consider the following scenario: you have an Employee object that, in turn, has a child Employee object representing the employee's manager. If you do not declare the child object transient, a chain of serialized nested objects will be created when you attempt to calculate the child Employee object at runtime.

ADF Mobile does not support serializing objects of the GregorianCalendar class. The JSONBeanSerializationHelper class cannot serialize objects of the GregorianCalendar class because the GregorianCalendar class has cyclical references in it. Instead, use java.util.Date or java.sql.Date for date manipulation. The following example shows how to convert a GregorianCalendar object using java.util.Date:

Calendar calDate = new GregorianCalendar();
calDate.set(1985, 12, 1); // "January 1, 1986"
Date date = calDate.getTime();

9.5 Using the DeviceFeatures Data Control

ADF Mobile exposes device-specific features that you can use in your application through the DeviceFeaturers data control, a component that appears in the Data Controls panel when you create a new ADF Mobile application. The PhoneGap Java API is abstracted through this data control, enabling the application features implemented as ADF Mobile AMX to access various services embedded on the device. By dragging and dropping the operations provided by the DeviceFeatures data control into an ADF Mobile AMX page, you can add functions to manage the user contacts stored on the device, create and send both email and SMS text messages, ascertain the location of the device, use the device's camera, and retrieve images stored in the device's file system. The following sections describe each of these operations in detail, including how to use them declaratively and how to implement them with Java code and JavaScript.

Figure 9-6 ADF Mobile DeviceFeatures Data Control in the Overview Editor

ADF Mobile DeviceFeatures Data Control in JDeveloper

The DeviceFeatures data control appears in the Data Controls panel automatically when you create an application using the ADF Mobile application template. Figure 9-6 shows the DeviceFeatures data control appears in the overview editor. The following methods are available:

  • createContact

  • findContacts

  • getPicture

  • removeContact

  • sendEmail

  • sendSMS

  • startLocationMonitor

  • updateContact

After you create a page, you can drag DeviceFeatures data control methods (or other objects nested within those methods) from the Data Controls panel to an ADF Mobile AMX view to create command buttons and other components that are bound to the associated functionality. You can accept the default bindings or modify the bindings using EL. You can also use JavaScript or Java to implement or configure functionality.

The DeviceManager is the object that enables you to access device functionality. You can get a handle on this object by calling DeviceManagerFactory.getDeviceManager. The following sections describe how you can invoke methods like getPicture or createContact using the DeviceManager object.

For information on how to include data controls in your ADF Mobile application, see Section 7.3.2.4, "Adding Data Controls to the View."

9.5.1 How to Use the getPicture Method

The DeviceFeatures data control includes the getPicture method, which enables ADF Mobile applications to leverage the device's camera and photo library so end users can take a photo or retrieve an existing image. Example 9-7 shows JavaScript code that allows an end user to take a picture with the device's camera. Example 9-8 and Example 9-9 show Java code that will allow an end user to take a picture or retrieve a saved image. For information about the getPicture method, see the DeviceDataControl class in the ADF Mobile Javadoc and refer to the PhoneGap documentation (see http://www.phonegap.com/home).

The following parameters control where the image is taken from and how it is returned:

  • quality: Set the quality of the saved image. Range is 0 to 100, inclusive. A higher number indicates higher quality, but also increases the file size. Only applicable to JPEG images (specified by encodingType).

  • destinationType: Choose the format of the return value:

    • DeviceManager.CAMERA_DESTINATIONTYPE_DATA_URL (0)—Returns the image as a Base64-encoded string. This value is also specified as an enum using DeviceManager.CAMERA_DESTINATION_DATA_URL when used programmatically.

    • DeviceManager.CAMERA_DESTINATIONTYPE_FILE_URI (1)—Returns the image file path. This value is also specified as an enum using DeviceManager.CAMERA_DESTINATION_FILE_URI when used programmatically.

  • sourceType: Set the source of the picture:

    • DeviceManager.CAMERA_SOURCETYPE_PHOTOLIBRARY (0)—Allows the user to choose from a previously saved image. This value is also specified as an enum using DeviceManager.CAMERA_SOURCETYPE_PHOTOLIBRARY when used programmatically.

    • DeviceManager.CAMERA_SOURCETYPE_CAMERA (1)—Allows the user to take a picture with device's camera. This value is also specified as an enum using DeviceManager.CAMERA_SOURCETYPE_CAMERA when used programmatically.

    • DeviceManager.CAMERA_SOURCETYPE_SAVEDPHOTOALBUM (2)—Allows the user to choose from an existing photo album. This value is also specified as an enum using DeviceManager.CAMERA_SOURCETYPE_SAVEDPHOTOALBUM when used programmatically.

  • allowEdit: Choose whether to allow simple editing of the image before selection (boolean).

  • encodingType: Choose the encoding of the returned image file:

    • DeviceManager.CAMERA_ENCODINGTYPE_JPEG (0)—Encodes the returned image as a JPEG file. This value is also specified as an enum using DeviceManager.CAMERA_ENCODINGTYPE_JPEG when used programmatically.

    • DeviceManager.CAMERA_ENCODINGTYPE_PNG (1)—Encodes the returned image as a PNG file. This value is also specified as an enum using DeviceManager.CAMERA_ENCODINGTYPE_PNG when used programmatically.

  • targetWidth: Set the width in pixels to scale the image. Aspect ratio is maintained. A negative or zero value indicates that the original dimensions of the image will be used.

  • targetHeight: Set the height in pixels to scale the image. Aspect ratio is maintained. A negative or zero value indicates that the original dimensions of the image will be used.

To customize a getPicture operation using the DeviceFeatures data control:

  1. Drag the getPicture operation from the DeviceFeatures data control in the Data Controls panel and drop it on the page as an ADF Mobile Button.

    If you want to provide more control to the user, drop the getPicture operation as an ADF Mobile Parameter Form. This allows the end user to specify settings before taking a picture or choosing an existing image.

  2. In the Edit Action dialog, set the values for all parameters described above. Be sure to specify destinationType = 1 so that the image is returned as a filename.

  3. Drag the return value of getPicture and drop it on the page as an Output Text.

  4. From the Common Components panel, drag an Image from the Component Palette and drop it on the page.

  5. Set the source attribute of the Image to the return value of the getPicture operation. The bindings expression should be: #{bindings.Return.inputValue}.

Figure 9-7 shows the bindings for displaying an image from the end user's photo library:

Figure 9-7 Bindings for Displaying an Image from the Photo Library at Design Time

Bindings for the getPicture method

When this application is run, the image chooser will automatically be displayed and the end user can select an image to display. The image chooser is displayed automatically because the Image control is bound to the return value of the getPicture operation, which in turn causes the getPicture operation to be invoked.

Keep in mind the following platform-specific issues:

  • iOS

    • Set quality below 50 to avoid memory error on some devices.

    • When destinationType FILE_URI is used, photos are saved in the application's temporary directory.

    • The contents of the application's temporary directory are deleted when the application ends. You may also delete the contents of this directory using the navigator.fileMgr APIs if storage space is a concern.

    • targetWidth and targetHeight must both be specified to be used. If one or both parameters have a negative or zero value, the original dimensions of the image will be used.

  • Android

    • Ignores the allowEdit parameter.

    • Camera.PictureSourceType.PHOTOLIBRARY and Camera.PictureSourceType.SAVEDPHOTOALBUM both display the same photo album.

    • Camera.EncodingType is not supported. The parameter is ignored, and will always produce JPEG images.

    • targetWidth and targetHeight can be specified independently. If one parameter has a positive value and the other uses a negative or zero value to represent the original size, the positive value will be used for that dimension, and the other dimension will be scaled to maintain the original aspect ratio.

    • When destinationType DATA_URL is used, large images can exhaust available memory, producing an out-of-memory error, and will typically do so if the default image size is used. Set the targetWidth and targetHeight to constrain the image size.

Example 9-7 shows JavaScript code that allows the user to take a picture with the device's camera. The result will be the full path to the saved image.

Example 9-7 JavaScript Code Example for getPicture

// The camera, like many other device-specific features, is accessed 
// from the global 'navigator' object in JavaScript.
// Note that in the PhoneGap JavaScript APIs, the parameters are passed
// in as a dictionary, so it is only necessary to provide key-value pairs
// for the parameters you want to specify.

navigator.camera.getPicture(onSuccess, onFail, { quality: 50, 

function onSuccess(imageURI) { 
    var image = document.getElementById('myImage');
    image.src = imageURI;
}
function onFail(message) {
    alert('Failed because: ' + message);
}

Example 9-8 shows Java code that allows the user to take a picture with the device's camera. The result will be the full path to the saved image.

Example 9-8 Java Code Example for Taking a Picture with getPicture

import oracle.adf.model.datacontrols.device; 

// Access device features in Java code by acquiring an instance of the  
// DeviceManager from the DeviceManagerFactory.
// Take a picture with the device's camera.  
// The result will be the full path to the saved PNG image.
String imageFilename = DeviceManagerFactory.getDeviceManager().getPicture(100,
    DeviceManager.CAMERA_DESTINATIONTYPE_FILE_URI,
    DeviceManager.CAMERA_SOURCETYPE_CAMERA, false,
    DeviceManager.CAMERA_ENCODINGTYPE_PNG, 0, 0);

Example 9-9 shows Java code that allows the user to retrieve a previously-saved image. The result will be a base64-encoded JPEG.

Example 9-9 Java Code Example for Retrieving an Image with getPicture

import oracle.adf.model.datacontrols.device; 

// Retrieve a previously-saved image. The result will be a base64-encoded JPEG.
String imageData = DeviceManagerFactory.getDeviceManager().getPicture(100, 
  DeviceManager.CAMERA_DESTINATIONTYPE_FILE_URL,
  DeviceManager.CAMERA_SOURCETYPE__PHOTOLIBRARY, false,
  DeviceManager.CAMERA_ENCODINGTYPE_JPEG, 0, 0);

9.5.2 How to Use the SendSMS Method

The DeviceFeatures data control includes the sendSMS method, which enables ADF Mobile applications to leverage the device's Short Message Service (SMS) text messaging interface so end users can send and receive SMS messages. ADF Mobile enables you to display the device's SMS interface and optionally pre-populate the following fields:

  • to: List recipients (comma-separated).

  • body: Add message body.

After the SMS text messaging interface is displayed, the end user can choose to either send the SMS or discard it. It is not possible to automatically send the SMS due to device and carrier restrictions; only the end user can actually send the SMS.

To customize a sendSMS operation using the DeviceFeatures data control:

To display an interactive form on the page for sending SMS, drag the sendSMS operation from the DeviceFeatures data control in the Data Controls panel and drop it on the page designer as an ADF Mobile Parameter Form. You can then customize the form in the Edit Form Fields dialog. At runtime, an editable form will be displayed on the page, which enables the application user to enter values for the various fields described above. Below this form will be a button to display the device's SMS interface, which will display an SMS that is ready to send with all of the specified fields pre-populated.

Figure 9-8 shows the bindings for sending an SMS using an editable form on the page.

Figure 9-8 Bindings for Sending an SMS Using an Editable Form at Design Time

Bindings for the sendSMS method

Example 9-10 and Example 9-11 show code examples that allow the end user to send an SMS message with the device's text messaging interface.

For information about the sendSMS method, see the DeviceDataControl class in the ADF Mobile Javadoc and refer to the PhoneGap documentation (see http://www.phonegap.com/home).

Example 9-10 JavaScript Code Example for sendSMS

adf.mf.api.sendSMS({to: "5551234567", body: "This is a test message"}); 

Example 9-11 Java Code Example for sendSMS

import oracle.adf.model.datacontrols.device.DeviceManagerFactory;

// Access device features in Java code by acquiring an instance of the  
// DeviceManager from the DeviceManagerFactory.
// Send an SMS to the phone number "5551234567"
DeviceManagerFactory.getDeviceManager().sendSMS("5551234567", "This is a test message");

9.5.3 How to Use the sendEmail Method

The DeviceFeatures data control includes the sendEmail method, which enables ADF Mobile applications to leverage the device's email messaging interface so end users can send and receive email messages. ADF Mobile enables you to display the device's email interface and optionally pre-populate the following fields:

  • to: List recipients (comma-separated).

  • cc: List CC recipients (comma-separated).

  • subject: Add message subject.

  • body: Add message body.

  • bcc: List BCC recipients (comma-separated).

  • attachments: List file names to attach to the email (comma-separated).

  • mimeTypes: List MIME types to use for the attachments (comma-separated). Specify null to let ADF Mobile automatically determine the MIME types. It is also possible to specify only the MIME types for selected attachments as shown in Example 9-12 and Example 9-13.

After the device's email interface is displayed, the user can choose to either send the email or discard it. It is not possible to automatically send the email due to device and carrier restrictions; only the end user can actually send the email. The device must also have at least one email account configured to send email; otherwise, an error will be displayed indicating that no email accounts could be found.

To customize a sendEmail operation using the DeviceFeatures data control:

In JDeveloper, drag the sendEmail operation from the DeviceFeatures data control in the Data Controls panel to the page designer and drop it as an ADF Mobile Parameter Form. You can then customize the form in the Edit Form Fields dialog. At runtime, an editable form will be displayed on the page, which enables the application user to enter values for the various fields described above. Below this form will be a button to display the device's email interface, which will display an email ready to send with all of the specified fields pre-populated.

Figure 9-9 shows the bindings for sending an email using an editable form on the page.

Figure 9-9 Bindings for Sending an Email Using an Editable Form at Design Time

Bindings for the sendEmail method

Example 9-12 and Example 9-13 show code examples that allow the end user to send an email message with the device's email interface.

For information about the sendEmail method, see the DeviceDataControl class in the ADF Mobile Javadoc and refer to the PhoneGap documentation (see http://www.phonegap.com/home).

Example 9-12 JavaScript Code Example for sendEmail

// Populate an email to 'ann.li@corp.example.com',  
// copy 'joe.jones@corp.example.com', with the 
// subject 'Test message', and the body 'This is a test message'
// No BCC recipients or attachments
adf.mf.api.sendEmail({to: "ann.li@corp.example.com",
                           cc: "joe.jones@corp.example.com",
                           subject: "Test message",
                           body: "This is a test message"});

// Populate the same email as before, but this time, also BCC 
// 'john.smith@corp.example.com' & 'jane.smith@corp.example.com' and attach two files.
// By not specifying a value for the mimeTypes parameter, you are telling 
// ADFMobile to automatically determine the MIME type for each of the attachments.
adf.mf.api.sendEmail({to: "ann.li@corp.example.com",
                           cc: "joe.jones@corp.example.com",
                           subject: "Test message",
                           body: "This is a test message"});
                           bcc: "john.smith@corp.example.com,jane.smith@corp.example.com",
                           attachments: "path/to/file1.txt,path/to/file2.png"});

// For iOS only: Same as previous email, but this time, explicitly specify
// all the MIME types.
adf.mf.api.sendEmail({to: "ann.li@corp.example.com",
                           cc: "joe.jones@corp.example.com",
                           subject: "Test message",
                           body: "This is a test message"});
                           bcc: "john.smith@corp.example.com,jane.smith@corp.example.com",
                           attachments: "path/to/file1.txt,path/to/file2.png"});
                           mimeTypes: "text/plain,image/png"});

// For iOS only: Same as previous email, but this time, only specify  
// the MIME type for the second attachment and let the system determine
// the MIME type for the first one.
adf.mf.api.sendEmail({to: "ann.li@corp.example.com",
                           cc: "joe.jones@corp.example.com",
                           subject: "Test message",
                           body: "This is a test message"});
                           bcc: "john.smith@corp.example.com,jane.smith@corp.example.com",
                           attachments: "path/to/file1.txt,path/to/file2.png"});
                           mimeTypes: ",image/png"});

// For Android only: Same as previous e-mail, but this time, explicitly specify 
// the MIME type.
adf.mf.api.sendEmail({to: "ann.li@corp.example.com",
                           cc: "joe.jones@corp.example.com",
                           subject: "Test message",
                           body: "This is a test message"});
                           bcc: "john.smith@corp.example.com,jane.smith@corp.example.com",
                           attachments: "path/to/file1.txt,path/to/file2.png"});
                           mimeTypes: "image/*"}); 
// You can also use "plain/text" as the MIME type as it just determines the type
// of applications to be filtered in the application chooser dialog.

Example 9-13 Java Code Example for sendEmail

import oracle.adf.model.datacontrols.device.DeviceManagerFactory;

// Access device features in Java code by acquiring an instance of the  
// DeviceManager from the DeviceManagerFactory.
// Populate an email to 'ann.li@corp.example.com', copy 'joe.jones@corp.example.com', with the 
// subject 'Test message', and the body 'This is a test message'.
// No BCC recipients or attachments.
DeviceManagerFactory.getDeviceManager().sendEmail(
                                        "ann.li@corp.example.com",
                                        "joe.jones@corp.example.com",
                                        "Test message",
                                        "This is a test message",
                                        null,
                                        null,
                                        null);

// Populate the same email as before, but this time, also BCC 
// 'john.smith@corp.example.com' & 'jane.smith@corp.example.com' and attach two files.
// By specifying null for the mimeTypes parameter, you are telling 
// ADFMobile to automatically determine the MIME type for each of the attachments.
DeviceManagerFactory.getDeviceManager().sendEmail(
                                        "ann.li@corp.example.com",
                                        "joe.jones@corp.example.com",
                                        "Test message",
                                        "This is a test message",
                                        "john.smith@corp.example.com,jane.smith@corp.example.com",
                                        "path/to/file1.txt,path/to/file2.png",
                                        null);

// Same as previous email, but this time, explicitly specify all the MIME types.
DeviceManagerFactory.getDeviceManager().sendEmail(
                                        "ann.li@corp.example.com",
                                        "joe.jones@corp.example.com",
                                        "Test message",
                                        "This is a test message",
                                        "john.smith@corp.example.com,jane.smith@corp.example.com",
                                        "path/to/file1.txt,path/to/file2.png",
                                        "text/plain,image/png");


// Same as previous email, but this time, only specify the MIME type for the 
// second attachment and let the system determine the MIME type for the first one.
DeviceManagerFactory.getDeviceManager().sendEmail(
                                        "ann.li@corp.example.com",
                                        "joe.jones@corp.example.com",
                                        "Test message",
                                        "This is a test message",
                                        "john.smith@corp.example.com,jane.smith@corp.example.com",
                                        "path/to/file1.txt,path/to/file2.png",
                                        ",image/png");

9.5.4 How to Use the createContact Method

The DeviceFeatures data control includes the createContact method, which enables ADF Mobile applications to leverage the device's interface and file system for managing contacts so end users can create new contacts to save in the device's address book. ADF Mobile enables you to display the device's interface and optionally pre-populate the Contact fields. The createContact method takes in a Contact object as a parameter and returns the created Contact object, as shown in Example 9-14. For information about the createContact method and the Contact object, see the DeviceDataControl class in the ADF Mobile Javadoc and refer to the PhoneGap documentation (see http://www.phonegap.com/home). Also see Section 9.5.5, "How to Use the findContacts Method" for a description of Contact fields.

To customize a createContact operation using the DeviceFeatures data control:

  1. In JDeveloper, drag the createContact operation from the DeviceFeatures data control in the Data Controls panel and drop it on the page designer as an ADF Mobile Link, Button, or Parameter Form.

    Link or Button: You will be prompted with the Edit Action Binding dialog to enter values for arguments to the createContact operation. At runtime, a button or link will be displayed on the page, which will use the entered values to perform a createContact operation when pressed.

    Parameter Form: Customize the form in the Edit Form Fields dialog. At runtime, an editable form will be displayed on the page, which enables the application user to enter values for the various Contact fields. Below this form will be a button, which will use the entered values to perform a createContact operation when pressed.

  2. You can also drag a Contact return object from under the createContact operation in the Data Controls panel and drop it on to the page as an ADF Form. You can then customize the form in the Edit Form Fields dialog. When the createContact operation is performed, the results will be displayed in this form.

Example 9-14 and Example 9-15 show code examples that allow the end user to create contacts on devices.

Example 9-14 JavaScript Code Example for createContact

// Contacts, like many other device-specific features, are accessed from the global 'navigator' object in JavaScript.
var contact = navigator.contacts.create();
 
var name = new ContactName();
name.givenName = "GivenName";
name.familyName = "FamilyName";
 
contact.name = name;
 
// Store contact phone numbers in ContactField[]
var phoneNumbers = [1];
phoneNumbers[0] = new ContactField('home', '650-111-2222', true);
 
contact.phoneNumbers = phoneNumbers;
 
// Store contact phone numbers in ContactField[]
var emails = [1];
emails[0] = new ContactField('work', 'first.given@oracle.com');
 
contact.emails = emails;
 
// Save
contact.save(onSuccess, onFailure);
 
function onSuccess()
{
  alert("Create Contact successful.");
}
 
function onFailure(Error)
{
  alert("Create Contact failed: " + Error.code);
}
     

Example 9-15 Java Code Example for createContact

import oracle.adf.model.datacontrols.device.DeviceManagerFactory; 

import oracle.adfmf.framework.contract.adf.ContactAddresses;
import oracle.adfmf.framework.contract.adf.ContactField;
import oracle.adfmf.framework.contract.adf.ContactName;

String givenName = "GivenName";
String familyName = "FamilyName";
String note = "Just a Note";
String phoneNumberType = "mobile";
String phoneNumberValue = "650-000-1234";
String phoneNumberNewValue = "650-111-1234";
String emailType = "home";
String emailTypeNew = "work";
String emailValue = "first.given@oracle.com";
String addressType = "home";
String addressStreet = "500 Oracle Pkwy";
String addressLocality = "Redwood Shores";
String addressCountry = "USA";
String addressPostalCode = "94065";
ContactField[] phoneNumbers = null;
ContactField[] emails = null;
ContactAddresses[] addresses = null;
ContactField[] emails = null;
    
/*
* Create contact
*/
Contact aContact = new Contact();
     
ContactName name = new ContactName();
name.setFamilyName(familyName);
name.setGivenName(givenName);
aContact.setName(name);
     
ContactField phoneNumber = new ContactField();
phoneNumber.setType(phoneNumberType);
phoneNumber.setValue(phoneNumberValue);
     
phoneNumbers = new ContactField[] { phoneNumber };
     
ContactField email = new ContactField();
email.setType(emailType);
email.setValue(emailValue);
     
emails = new ContactField[] { email };
     
ContactAddresses address = new ContactAddresses();
address.setType(addressType);
address.setStreetAddress(addressStreet);
address.setLocality(addressLocality);
address.setCountry(addressCountry);
     
addresses = new ContactAddresses[] { address };
     
aContact.setNote(note);
aContact.setPhoneNumbers(phoneNumbers);
aContact.setEmails(emails);
aContact.setAddresses(addresses);
     
// Access device features in Java code by acquiring an instance of the  
// DeviceManager from the DeviceManagerFactory.
// Invoking the createContact method, using the DeviceDataControl object.
Contact createdContact = DeviceManagerFactory.getDeviceManager()
    .findContacts.createContact(aContact);
     

9.5.5 How to Use the findContacts Method

The DeviceFeatures data control includes the findContacts method, which enables ADF Mobile applications to leverage the device's interface and file system for managing contacts so end users can find one or more contacts from the device's address book. ADF Mobile enables you to display the device's interface and optionally pre-populate the findContacts fields. The findContacts method takes in a filter string and a list of field names to look through (and return as part of the found contacts). The filter string can be anything to look for in the contacts. For more information about the findContacts method, see the DeviceDataControl class in the ADF Mobile Javadoc and refer to the PhoneGap documentation (see http://www.phonegap.com/home).

The findContacts operation takes the following arguments:

  • contactFields: Required parameter. Use this parameter to specify which fields should be included in the Contact objects resulting from a findContacts operation. Separate fields with a comma (spacing does not matter).

  • filter: The search string used to filter contacts. (String) (Default: "")

  • multiple: Determines if the findContacts operation should return multiple contacts. (Boolean) (Default: false)

Note:

Passing in a field name that is not in the following list may result in a null return value for the findContacts operation. Also, only the fields specified in the Contact fields argument will be returned as part of the Contact object.

The following list shows the possible Contact fields that can be passed in to look through and be returned as part of the found contacts:

  • id: A globally unique identifier

  • displayName: The name of this contact, suitable for display to end-users

  • name: An object containing all components of a person's name

  • nickname: A casual name for the contact

  • phoneNumbers: An array of all the contact's phone numbers

  • emails: An array of all the contact's email addresses

  • addresses: An array of all the contact's addresses

  • ims: An array of all the contact's Instant messaging (IM) addresses

  • organizations: An array of all the contact's organizations

  • revision: The last date the contact was revised

  • birthday: The birthday of the contact

  • gender: The gender of the contact

  • note: A note about the contact

  • photos: An array of the contact's photos

  • urls: An array of web pages associated to the contact

To customize a findContacts operation using the DeviceFeatures data control:

  1. In JDeveloper, drag the findContacts operation from the DeviceFeatures data control in the Data Controls panel and drop it on the page designer as an ADF Mobile Link, Button, or Parameter Form.

    Link or Button: You will be prompted with the Edit Action Binding dialog to enter values for arguments to the findContacts operation. At runtime, a button or link will be displayed on the page, which will use the entered values to perform a findContacts operation when pressed.

    Parameter Form: Customize the form in the Edit Form Fields dialog. At runtime, an editable form will be displayed on the page, which enables the application user to enter values for the various Contact fields described above. Below this form will be a button, which will use the entered values to perform a findContacts operation when pressed.

  2. You can also drag a Contact return object from under the findContacts operation in the Data Controls panel and drop it on to the page as an ADF Form. You can then customize the form in the Edit Form Fields dialog. When the findContacts operation is performed, the results will be displayed in this form.

Example 9-16 shows possible argument values for the findContacts method. Example 9-17 and Example 9-18 show how to find a contact by family name and get the contact's name, phone numbers, email, addresses, and note.

Example 9-16 Possible Argument Values for findContacts

// This will return just one contact with only the ID field:
Contact[] foundContacts = DeviceManagerFactory.getDeviceManager().findContacts("", "", false);

// This will return all contacts with only ID fields:
Contact[] foundContacts = DeviceManagerFactory.getDeviceManager().findContacts("", "", true);

// This will return just one contact with all fields:
Contact[] foundContacts = DeviceManagerFactory.getDeviceManager().findContacts("*", "", false);

// This will return all contacts with all fields:
Contact[] foundContacts = DeviceManagerFactory.getDeviceManager().findContacts("*", "", true);

// These will throw an exception as contactFields is a required argument and cannot be null:
DeviceManagerFactory.getDeviceManager().findContacts(null, "", false);
DeviceManagerFactory.getDeviceManager().findContacts(null, "", true);

// These will throw an exception as the filter argument cannot be null:
DeviceManagerFactory.getDeviceManager().findContacts("", null, false);
DeviceManagerFactory.getDeviceManager().findContacts("", null, true);

Note:

The Contact fields passed are strings (containing the comma-delimited fields). If any arguments are passed as null to the method, an ADF exception is thrown.

Example 9-17 JavaScript Code Example for findContacts

var filter = ["name", "phoneNumbers", "emails", "addresses", "note"];
 
var options = new ContactFindOptions();
options.filter="FamilyName";
 
// Contacts, like many other device-specific features, are accessed from 
// the global 'navigator' object in JavaScript.
navigator.contacts.find(filter, onSuccess, onFail, options);
 
function onSuccess(contacts)
{
  alert ("Find Contact call succeeded! Number of contacts found = " + contacts.length);
}
 
function onFail(Error)
{
  alert("Find Contact failed: " + Error.code);
}

Example 9-18 Java Code Example for findContacts

import oracle.adf.model.datacontrols.device.DeviceManagerFactory;

/*
 * Find Contact - Find contact by family name.
 *
 * See if we can find the contact that we just created.
 */

String familyName = "FamilyName"

// Access device features in Java code by acquiring an instance of the  
// DeviceManager from the DeviceManagerFactory.
Contact[] foundContacts = DeviceManagerFactory.getDeviceManager().findContacts(
    "name,phoneNumbers,emails,addresses,note", familyName, true); 

9.5.6 How to Use the updateContact Method

The DeviceFeatures data control includes the updateContact method, which enables ADF Mobile applications to leverage the device's interface and file system for managing contacts so end users can update contacts in the device's address book. ADF Mobile enables you to display the device's interface and optionally pre-populate the updateContact fields. The updateContact method takes in a Contact object as a parameter and returns the updated Contact object, as shown in Example 9-19.

Note:

The Contact object that is needed as the input parameter can be found using the findContacts method as described in Section 9.5.5, "How to Use the findContacts Method." If a null Contact object is passed in to the method, an ADF exception is thrown.

To customize an updateContact operation using the DeviceFeatures data control:

  1. In JDeveloper, drag the updateContact operation from the DeviceFeatures data control in the Data Controls panel and drop it on the page designer as an ADF Mobile Link, Button, or Parameter Form.

    Link or Button: You will be prompted with the Edit Action Binding dialog to enter values for arguments to the updateContact operation. At runtime, a button or link will be displayed on the page, which will use the entered values to perform an updateContact operation when pressed.

    Parameter Form: Customize the form in the Edit Form Fields dialog. At runtime, an editable form will be displayed on the page, which enables the application user to enter values for the various Contact fields. Below this form will be a button, which will use the entered values to perform an updateContact operation when pressed.

  2. You can also drag a Contact return object from under the updateContact operation in the Data Controls panel and drop it on to the page as an ADF Form. You can then customize the form in the Edit Form Fields dialog. When the updateContact operation is performed, the results will be displayed in this form.

Example 9-19 and Example 9-21 show how to update a contact's phone number. Example 9-20 and Example 9-22 show how to add another phone number to a contact.

For information about the updateContact method and the Contact object, see the DeviceDataControl class in the ADF Mobile Javadoc and refer to the PhoneGap documentation (see http://www.phonegap.com/home). Also see Section 9.5.5, "How to Use the findContacts Method" for a description of Contact fields.

Example 9-19 JavaScript Code Example for updateContact

function updateContact(contact)
{
  try
  {
    if (null != contact.phoneNumbers)
    {
      alert("Number of phone numbers = " + contact.phoneNumbers.length);
      var numPhoneNumbers = contact.phoneNumbers.length;
      for (var j = 0; j < numPhoneNumbers; j++)
      {
        alert("Type: " + contact.phoneNumbers[j].type + "\n" +
              "Value: "  + contact.phoneNumbers[j].value + "\n" +
              "Preferred: "  + contact.phoneNumbers[j].pref);
 
        contact.phoneNumbers[j].type = "mobile";
        contact.phoneNumbers[j].value = "408-123-4567";
      }
 
      // save
      contact.save(onSuccess, onFailure);
    }
    else
    {
      //alert ("No phone numbers found in the contact.");
    }
  }
  catch(e)
  {
    alert("updateContact - ERROR: " + e.description);
  }
}
 
function onSuccess()
{
  alert("Update Contact successful.");
}
 
function onFailure(Error)
{
  alert("Update Contact failed: " + Error.code);

Example 9-20 shows you how to add another phone number to the already existing phone numbers.

Example 9-20 JavaScript Code Example for Adding a Phone Number with updateContact

function updateContact(contact)
{
  try
  {
    var phoneNumbers = [1];
    phoneNumbers[0] = new ContactField('home', '650-111-2222', true);
    contact.phoneNumbers = phoneNumbers;
 
    // save
    contact.save(onSuccess, onFailure);
  }
  catch(e)
  {
    alert("updateContact - ERROR: " + e.description);
  }
}
 
function onSuccess()
{
  alert("Update Contact successful.");
}
 
function onFailure(Error)
{
  alert("Update Contact failed: " + Error.code);
}

Example 9-21 shows how to update a contact's phone number, email type, and postal code.

Example 9-21 Java Code Example for updateContact

import oracle.adf.model.datacontrols.device.DeviceManagerFactory;

/*
 * Update Contact - Updating phone number, email type, and adding address postal code
 */
String familyName = "FamilyName";
String phoneNumberNewValue = "650-111-1234";
String emailTypeNew = "work";
String addressPostalCode = "94065";

Contact[] foundContacts = DeviceManagerFactory.getDeviceManager().findContacts(
    "name,phoneNumbers,emails,addresses,note", familyName, true); 

// Assuming there was only one contact returned, we can use the first contact in the array.
// If more than one contact is returned then we have to filter more to find the exact contact 
// we need to update.

foundContacts[0].getPhoneNumbers()[0].setValue(phoneNumberNewValue);
foundContacts[0].getEmails()[0].setType(emailTypeNew);
foundContacts[0].getAddresses()[0].setPostalCode(addressPostalCode);

Contact updatedContact = DeviceManagerFactory.getDeviceManager().updateContact(foundContacts[0]);

Example 9-22 shows you how to add another phone number to the already existing phone numbers.

Example 9-22 Java Code Example for Adding a Phone Number with updateContact

import oracle.adf.model.datacontrols.device.DeviceManagerFactory;

String additionalPhoneNumberValue = "999-888-0000";
String additionalPhoneNumberType = "mobile";
// Create a new phoneNumber that will be appended to the previous one.
ContactField additionalPhoneNumber = new ContactField();
additionalPhoneNumber.setType(additionalPhoneNumberType);
additionalPhoneNumber.setValue(additionalPhoneNumberValue);

foundContacts[0].setPhoneNumbers(new ContactField[] { additionalPhoneNumber });

// Access device features in Java code by acquiring an instance of the DeviceManager 
// from the DeviceManagerFactory.
Contact updatedContact = DeviceManagerFactory.getDeviceManager().updateContact(foundContacts[0]);

9.5.7 How to Use the removeContact Method

The DeviceFeatures data control includes the removeContact method, which enables ADF Mobile applications to leverage the device's interface and file system for managing contacts so end users can remove contacts from the device's address book. ADF Mobile enables you to display the device's interface and optionally pre-populate the removeContact fields. The removeContact method takes in a Contact object as a parameter, as shown in Example 9-23.

Note:

The Contact object that is needed as the input parameter can be found using the findContacts method as described in Section 9.5.5, "How to Use the findContacts Method."

To customize a removeContact operation using the DeviceFeatures data control:

  1. In JDeveloper, drag the removeContact operation from the DeviceFeatures data control in the Data Controls panel and drop it on the page designer as an ADF Mobile Link, Button, or Parameter Form.

    Link or Button: You will be prompted with the Edit Action Binding dialog to enter values for arguments to the removeContact operation. At runtime, a button or link will be displayed on the page, which will use the entered values to perform a removeContact operation when pressed.

    Parameter Form: Customize the form in the Edit Form Fields dialog. At runtime, an editable form will be displayed on the page, which enables the application user to enter values for the various Contact fields. Below this form will be a button, which will use the entered values to perform a removeContact operation when pressed.

  2. You can also drag a Contact return object from under the removeContact operation in the Data Controls panel and drop it on to the page as an ADF Form. You can then customize the form in the Edit Form Fields dialog. When the removeContact operation is performed, the results will be displayed in this form.

Example 9-23 and Example 9-24 show you how to delete a contact that you found using findContacts. For information about the removeContact method and the Contact object, see the DeviceDataControl class in the ADF Mobile Javadoc and refer to the PhoneGap documentation (see http://www.phonegap.com/home).

Example 9-23 JavaScript Code Example for removeContact

// Remove the contact from the device
contact.remove(onSuccess,onError);
 
function onSuccess()
{
  alert("Removal Success");
}
 
function onError(contactError)'
{
  alert("Error = " + contactError.code);
}

Example 9-24 Java Code Example for removeContact

import oracle.adf.model.datacontrols.device.DeviceManagerFactory;

/*
 * Remove the contact from the device
 */
Contact[] foundContacts = DeviceManagerFactory.getDeviceManager().findContacts(
    "name,phoneNumbers,emails,addresses", familyName, true);

// Assuming there is only one contact returned, we can use the first contact in the array.
// If more than one contact is returned we will have to filter more to find the  
// exact contact we want to remove.

// Access device features in Java code by acquiring an instance of the DeviceManager 
// from the DeviceManagerFactory.
DeviceManagerFactory.getDeviceManager().removeContact(foundContacts[0]);

9.5.8 How to Use the startLocationMonitor Method

The DeviceFeatures data control includes the startLocationMonitor method, which enables ADF Mobile applications to provide a geolocation feature so end users can obtain the device's location. ADF Mobile enables you to display the device's interface and optionally pre-populate the startLocationMonitor fields.

ADF Mobile exposes APIs that enable you to acquire the device's current position, allowing you to retrieve the device's current location for one instant in time or to subscribe to it on a periodic basis. Example 9-25 and Example 9-26 show code examples that will allow your application to obtain the device's location. For information about the startLocationMonitor method, see the DeviceDataControl class in the ADF Mobile Javadoc and refer to the PhoneGap documentation (see http://www.phonegap.com/home).

To listen for changes in the device's location using the DeviceFeatures data control:

In JDeveloper, drag the startLocationMonitor operation from the DeviceFeatures data control in the Data Controls panel to the page designer and drop it as an ADF Mobile Link or Button. When prompted by the Edit Action Dialog, populate the fields as follows:

  • enableHighAccuracy: If true, use the most accurate possible method of obtaining a location fix. This is just a hint; the operating system may not respect it. Devices often have several different mechanisms for obtaining a location fix, including cell tower triangulation, Wi-Fi hotspot lookup, and true GPS. Specifying false indicates that you are willing to accept a less accurate location, which may result in a faster response or consume less power.

  • updateInterval: Defines how often, in milliseconds, to receive updates. Location updates may not be delivered as frequently as specified; the operating system may wait until a significant change in the device's position has been detected before triggering another location update.

  • locationListener: EL expression that resolves to a bean method with the following signature:

    void methodName(Location newLocation)
    

    This EL expression will be evaluated every time a location update is received. For example, enter viewScope.LocationListenerBean.locationUpdated (without the surrounding#{}), then define a bean named LocationListenerBean in viewScope and implement a method with the following signature:

    public void locationUpdated(Location currentLocation)
    {
      System.out.println(currentLocation);
      // To stop subscribing to location updates, invoke the following:
      // DeviceManagerFactory.getDeviceManager().clearWatchPosition(
      //     currentLocation.getWatchId());
    }
    

    Note:

    Do not use the EL syntax #{LocationListenerBean.locationUpdate} to specify the locationListener, unless you truly want the result of evaluating that expression to be the name of the locationListener.

Example 9-25 shows how to subscribe to changes in the device's location periodically. The example uses the DeviceManager.startUpdatingPosition method, which takes the following parameters:

  • int updateInterval: Defines how often to deliver location updates, in milliseconds. Location updates may not be delivered as frequently as specified; the operating system may wait until a significant change in the device's position has been detected before triggering another location update. Conversely, location updates may also be delivered at the specified frequency, but may be identical until the device's position has changed significantly.

  • boolean enableHighAccuracy: If set to true, use the most accurate possible method of obtaining a location fix.

  • String watchID: Defines a unique ID that can be subsequently used to stop subscribing to location updates

  • GeolocationCallback: An implementation of the GeolocationCallback interface. This implementation's locationUpdated method is invoked each time the location is updated, as shown in Example 9-25.

For an example of how to subscribe to changes in the device's position using JavaScript, refer to the PhoneGap documentation (see http://www.phonegap.com/home).

Parameters returned in the callback function specified by the locationListener are as follows:

  • double getAccuracy—Accuracy level of the latitude and longitude coordinates in meters

  • double getAltitude—Height of the position in meters above the ellipsoid

  • double getLatitude—Latitude in decimal degrees

  • double getLongitude—Longitude in decimal degrees

  • double getAltitudeAccuracy—Accuracy level of the altitude coordinate in meters

  • double getHeading—Direction of travel, specified in degrees counting clockwise relative to the true north

  • double getSpeed—Current ground speed of the device, specified in meters per second

  • long getTimestamp—Creation of a timestamp in milliseconds since the Unix epoch

  • String getWatchId—Only used when subscribing to periodic location updates. A unique ID that can be subsequently used to stop subscribing to location updates

For more information about the startLocationMonitor and startHeadingMonitor methods, see the DeviceDataControl class in the ADF Mobile Javadoc and refer to the PhoneGap documentation (see http://www.phonegap.com/home).

Example 9-25 Using Geolocation to Subscribe to Changes in the Device's Location

import oracle.adf.model.datacontrols.device.DeviceManagerFactory;
import oracle.adf.model.datacontrols.device.GeolocationCallback;
import oracle.adfmf.framework.contract.adf.Location;

// Subscribe to location updates that will be delivered every 20 seconds, with high accuracy.
// As we can have multiple subscribers, let's identify this one as 'MyGPSSubscriptionID'.
// Notice that this call returns the watchID, which is usually the same as the watchID passed in.
// However, it may be different if the specified watchID conflicts with an existing watchID,
// so be sure to always use the returned watchID.
String watchID = DeviceManagerFactory.getDeviceManager().startUpdatingPosition(20000, true, "
       "MyGPSSubscriptionID", new GeolocationCallback() {
    public void locationUpdated(Location position) {
        System.out.println("Location updated to: " + position);
    }
});

// The previous call returns immediately so that you can continue processing. 
// When the device's location changes, the locationUpdated() method specified in  
// the previous call will be invoked in the context of the current feature.

// When you wish to stop being notified of location changes, call the following method:
DeviceManagerFactory().getDeviceManager().clearWatchPosition(watchID);

To obtain the device's location using the DeviceFeatures data control:

In JDeveloper, drag the startLocationMonitor operation from the DeviceFeatures data control in the Data Controls panel to the page designer and drop it as an ADF Mobile Link or Button. Follow Example 9-25, but stop listening after the first location update is received.

Example 9-26 shows how to get a device's location one time. The example uses DeviceManager.getCurrentPosition, which takes the following parameters:

  • int maximumAge: Accept a cached value no older than this value, in milliseconds. If a location fix has been obtained within this window of time, then it will be returned immediately; otherwise, the call will block until a new location fix can be determined.

  • boolean: enableHighAccuracy If set to true, use the most accurate possible method of obtaining a location fix.

Example 9-26 Using Geolocation to Get the Device's Current Location (One Time)

import oracle.adf.model.datacontrols.device.DeviceManagerFactory;
import oracle.adfmf.framework.contract.adf.Location;

// Get the device's current position, with highest accuracy, and accept a cached location that is 
// no older than 60 seconds.
Location currentPosition = DeviceManagerFactory.getDeviceManager().getCurrentPosition(60000, true);
System.out.println("The device's current location is: latitude=" + currentPosition.getLatitude() + 
    ", longitude=" + currentPosition.getLongitude());

9.5.9 Device Properties

There may be features of your application that rely on specific device characteristics or capabilities. For example, you may want to present a different user interface depending on the device's screen orientation, or there may be a mapping feature that you want to enable only if the device supports geolocation. ADF Mobile provides a number of properties that you can access from Java and EL in order to support this type of dynamic behavior. Table 9-6 lists these properties, along with information about how to query them, what values to expect in return, and whether the property can change during the application's lifecycle.

Table 9-6 Device Properties and Corresponding EL Expressions

Property Static/
Dynamic
EL Expression Sample Value Java API

device.name

Static

#{deviceScope.device.name}

"iPhone Simulator", "Joe Smith's iPhone"

DeviceManager.getName()

device.platform

Static

#{deviceScope.device.platform}

"iPhone Simulator", "iPhone"

DeviceManager.getPlatform()

device.version

Static

#{deviceScope.device.version}

"4.3.2", "5.0.1"

DeviceManager.getVersion()

device.os

Static

#{deviceScope.device.os}

"iOS"

DeviceManager.getOs()

device.model

Static

#{deviceScope.device.model}

"x86_64", "i386", "iPhone3,1"

DeviceManager.getModel()

device.phonegap

Static

#{deviceScope.device.phonegap}

"1.0.0"

DeviceManager.getPhonegap()

hardware.hasCamera

Static

#{deviceScope.hardware.hasCamera}

"true", "false"

DeviceManager.hasCamera()

hardware.hasContacts

Static

#{deviceScope.hardware.hasContacts}

"true", "false"

DeviceManager.hasContacts()

hardware.hasTouchScreen

Static

#{deviceScope.hardware.hasTouchScreen}

"true", "false"

DeviceManager.hasTouchScreen()

hardware.hasGeolocation

Static

#{deviceScope.hardware.hasGeolocation}

"true", "false"

DeviceManager.hasGeolocation()

hardware.hasAccelerometer

Static

#{deviceScope.hardware.hasAccelerometer}

"true", "false"

DeviceManager.hasAccelerometer()

hardware.hasCompass

Static

#{deviceScope.hardware.hasCompass}

"true", "false"

DeviceManager.hasCompass()

hardware.hasFileAccess

Static

#{deviceScope.hardware.hasFileAccess}

"true", "false"

DeviceManager.hasFileAccess()

hardware.hasLocalStorage

Static

#{deviceScope.hardware.hasLocalStorage}

"true", "false"

DeviceManager.hasLocalStorage()

hardware.hasMediaPlayer

Static

#{deviceScope.hardware.hasMediaPlayer}

"true", "false"

DeviceManager.hasMediaPlayer()

hardware.hasMediaRecorder

Static

#{deviceScope.hardware.hasMediaRecorder}

"true", "false"

DeviceManager.hasMediaRecorder()

hardware.networkStatus

Dynamic

#{deviceScope.hardware.networkStatus}

"wifi", "2g", "unknown", "none"Foot 1 

DeviceManager.getNetworkStatus()

hardware.screen.width

Dynamic

#{deviceScope.hardware.screen.width}

320, 480

DeviceManager.getScreenWidth()

hardware.screen.height

Dynamic

#{deviceScope.hardware.screen.height}

480, 320

DeviceManager.getScreenHeight()

hardware.availableWidth

Dynamic

#{deviceScope.hardware.screen.availableWidth}

<= 320, <= 480

DeviceManager.getAvailableScreenWidth()

hardware.availableHeight

Dynamic

#{deviceScope.hardware.screen.availableHeight}

<= 480, <= 320

DeviceManager.getAvailableScreenHeight()

hardware.screen.dpi

Static

#{deviceScope.hardware.screen.dpi}

160, 326

DeviceManager.getScreenDpi()

hardware.screen.diagonalSize

Static

#{deviceScope.hardware.screen.diagonalSize}

9.7, 6.78

DeviceManager.getScreenDiagonalSize()

hardware.screen.scaleFactor

Static

#{deviceScope.hardware.screen.scaleFactor}

1.0, 2.0

DeviceManager.getScreenScaleFactor()


Footnote 1  If both wifi and 2G are turned on, network status will be wifi, as wifi takes precedence over 2G.

9.6 Performing Validation

In ADF Mobile, validation occurs in the data control layer, with validation rules set on binding attributes behaving similarly to those in Oracle ADF. The attribute validation takes place at a single point in the system, during the setValue operation on the bindings.

You can define the following validators for attributes exposed by the data controls:

  • Compare validator

  • Length validator

  • List validator

  • Range validator

All validators for a given attribute are executed, and nested exceptions are thrown for every validator that does not pass. You can define a validation message for attributes, which is displayed when a validation rule is fired at runtime. For more information, see Section 8.9, "Validating Input" and Section 9.6.1, "How to Add Validation Rules."

Note:

Due to a JSON limitation, the value that a BigDecimal can hold is within the range of a Double, and the value that a BigInteger can hold is within the range of a Long. If you want to use numbers greater than those allowed, you can call toString on BigDecimal or BigInteger to serialize values as String.

Table 9-7 lists supported validation combinations for the length validator.

Table 9-7 Length Validation

Compare type Byte Character

Equals

Supported

Supported

Not Equals

Supported

Supported

Less Than

Supported

Supported

Greater Than

Supported

Supported

Less Than Equal To

Supported

Supported

Greater Than Equal To

Supported

Supported

Between

Supported

Supported


Table 9-8 and Table 9-9 list supported validation combinations for the range validator.

Table 9-8 Range Validation

Compare type Byte Char Double Float Integer Long Short

Between

Supported

Supported

Supported

Supported

Supported

Supported

Supported

Not Between

Supported

Supported

Supported

Supported

Supported

Supported

Supported


Table 9-9 Range Validation - math, sql, and util Packages

Compare type java.math.BigDecimal java.math.BigInteger java.sql.Date java.sql.Time java.sql.Timestamp java.util.Date

Between

Supported

Supported

Not supported

Not supported

Not supported

Not supported

Not Between

Supported

Supported

Not supported

Not supported

Not supported

Not supported


Table 9-10 lists supported validation combinations for the list validator.

Table 9-10 List Validation

Compare type String

In

Supported

Not In

Supported


Table 9-11 and Table 9-12 lists supported validation combinations for the compare validator.

Table 9-11 Compare Validation

Compare type Byte Char Double Float Integer Long Short String

Equals

Supported

Supported

Supported

Supported

Supported

Supported

Supported

Supported

Not Equals

Supported

Supported

Supported

Supported

Supported

Supported

Supported

Supported

Less Than

Not supported

Supported

Supported

Supported

Supported

Supported

Supported

Not supported

Greater Than

Not supported

Supported

Supported

Supported

Supported

Supported

Supported

Not supported

Less Than Equal To

Not supported

Supported

Supported

Supported

Supported

Supported

Supported

Not supported

Greater Than Equal To

Not supported

Supported

Supported

Supported

Supported

Supported

Supported

Not supported


Table 9-12 Compare Validation - java.math, java.sql, and java.util Packages

Compare type java.math.BigDecimal java.math.BigInteger java.sql.Date java.sql.Time java.sql.Timestamp java.util.Date

Equals

Supported

Supported

Not supported

Not supported

Not supported

Not supported

Not Equals

Supported

Supported

Not supported

Not supported

Not supported

Not supported

Less Than

Supported

Supported

Not supported

Not supported

Not supported

Not supported

Greater Than

Supported

Supported

Not supported

Not supported

Not supported

Not supported

Less Than Equal To

Supported

Supported

Not supported

Not supported

Not supported

Not supported

Greater Than Equal To

Supported

Supported

Not supported

Not supported

Not supported

Not supported


9.6.1 How to Add Validation Rules

You can define validation rules for a variety of use cases. To add a declarative validation rule to an entity object, use the Overview Editor for Data Control Structure Files - Attributes Page.

For more information about adding validation rules, see the "Adding Validation Rules to Entity Objects and Attributes" section in Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

To add a validation rule:

  1. From the Data Controls panel, right-click on a data controls object and choose Edit Definition.

  2. In the Overview Editor for Data Control Structure Files, select the Attributes page.

    Overview Editor - Attributes page
  3. Select the Validation Rules tab in the lower part of the page and then click Add. In the resulting Add Validation Rule dialog, define the validation rule and the failure handling.

    Add Validation Rule dialog

For more information, see the following:

9.6.2 What You May Need to Know About the Validator Metadata

The validator metadata is placed into the data control structure metadata XML files at design time. Example 9-27 shows a sample length validator.

Example 9-27 Length Validator Declared in Metadata File

<?xml version="1.0" encoding="windows-1252" ?>
<!DOCTYPE PDefViewObject SYSTEM "jbo_03_01.dtd">
<PDefViewObject
   xmlns="http://xmlns.oracle.com/bc4j"
   Name="Product"
   Version="12.1.1.61.36"
   xmlns:validation="http://xmlns.oracle.com/adfm/validation">
   <DesignTime>
      <Attr Name="_DCName" Value="DataControls.ProductListBean"/>
      <Attr Name="_SDName" Value="mobile.Product"/>
   </DesignTime>
   <PDefAttribute
      Name="name">
      <validation:LengthValidationBean
         Name="nameRule0"
         OnAttribute="name"
         CompareType="GREATERTHAN"
         DataType="BYTE"
         CompareLength="5"
         Inverse="false"/>
   </PDefAttribute>
</PDefViewObject>

9.7 Data Change Events

To simplify data change events, JDeveloper uses the property change listener pattern. In most cases you can use JDeveloper to generate the necessary code to source notifications from your beans' property accessors by selecting the Notify listeners when property changes checkbox in the Generate Accessors dialog (see Section 9.2.4.2, "ADF Managed Beans" for details). The PropertyChangeSupport object is generated automatically, with the calls to firePropertyChange in the newly-generated setter method. Additionally, the addPropertyChangeListener and removePropertyChangeListener methods are added so property change listeners can register and unregister themselves with this object. This is what the framework uses to capture changes to be pushed to the client cache and to notify the user interface layer that data has been changed.

Property changes alone will not solve all the data change notifications, as in the case where you have a bean wrapped by a data control and you want to expose a collection of items. While a property change is sufficient for individual items of the list changing, it is not sufficient for cardinality changes. In this case, rather than fire a property change for the entire collection, which would cause a degradation of performance, you can instead refresh just the collection delta. To do this you need to expose more data than is required for a simple property change, which you can do using the ProviderChangeSupport class.

Since the provider change is required only when you have a dynamic collection exposed by a data control wrapped bean, there are only a few types of provider change events to fire:

  • fireProviderCreate—when a new element is added to the collection

  • fireProviderDelete—when an element is removed from the collection

  • fireProviderRefresh—when multiple changes are done to the collection at one time and you decide it is better to simply ask for the client to refresh the entire collection (this should only be used in bulk operations)

The ProviderChangeSupport class is used for sending notifications relating to collection elements, so that components update properly when a change occurs in a Java bean data control. It follows a similar pattern to the automatically-generated PropertyChangeSupport class, but the event objects used with ProviderChangeSupport send more information, including the type of operation as well as the key and position of the element that changed. ProviderChangeSupport captures structural changes to a collection, such as adding or removing an element (or provider) from a collection. PropertyChangeSupport captures changes to the individual items in the collection.

Example 9-28 shows how to use ProviderChangeSupport for sending notifications relating to structural changes to collection elements (such as when adding or removing a child). For more information on the ProviderChangeListener interface and the ProviderChangeEvent class, see the ADF Mobile Javadoc.

Example 9-28 ProviderChangeSupport Code Example

public class NotePad {
   private   static    List                  
                       s_notes               = null;
 
   protected transient PropertyChangeSupport 
                       propertyChangeSupport = new PropertyChangeSupport(this);
   protected transient ProviderChangeSupport 
                       providerChangeSupport = new ProviderChangeSupport(this);
 
    public NotePad() {
        …
    }
 
    public  mobile.Note[] getNotes() {
        mobile.Note n[] = null;
 
        synchronized (this)
        {
            if(s_notes.size() > 0) {
                n = (mobile.Note[])
                    s_notes.toArray(new mobile.Note[s_notes.size()]);
            }
            else {
                n = new mobile.Note[0];
            }
        }
 
        return n;
    }
 
    public void addNote() {
        System.out.println("Adding a note ....");
        Note  n = new Note();
        int   s = 0;
        
        synchronized (this)
        {
            s_notes.add(n);
            s = s_notes.size();
        }
 
        System.out.println("firing the events");
        providerChangeSupport.fireProviderCreate("notes", n.getUid(), n);
    }
    
    public void removeNote() {
        System.out.println("Removng a note ....");
        if(s_notes.size() > 0) {
            int     end = -1;
            Note    n   = null;
 
            synchronized (this)
            {
                end   = s_notes.size() - 1;
                n     = (Note)s_notes.remove(end);
            }
            
            System.out.println("firing the events");
        providerChangeSupport.fireProviderDelete("notes", n.getUid());
        }
    }
    
    public void RefreshNotes() {
        System.out.println("Refreshing the notes ....");
 
        providerChangeSupport.fireProviderRefresh("notes");
    }
    
    public void addProviderChangeListener(ProviderChangeListener l) {
        providerChangeSupport.addProviderChangeListener(l);
    }
 
    public void removeProviderChangeListener(ProviderChangeListener l) {
        providerChangeSupport.removeProviderChangeListener(l);
    }
 
    protected String   status;    
    
    /* --- JDeveloper generated accessors --- */
 
    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }
 
    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }
 
    public void setStatus(String status) {
        String oldStatus = this.status;
        this.status = status;
        propertyChangeSupport.firePropertyChange("status", oldStatus, status);
    }
 
    public String getStatus() {
        return status;
    }
}

Data changes are passed back to the client (to be cached) with any response message or return value from the JVM layer. This allows JDeveloper to compress and reduce the number of events and updates to refresh to the user interface, allowing the framework to be as efficient as possible.

However, there are times where you may need to have a background thread handle a long-running process (such as web-service interactions, database interactions, or expensive computations) and notify the user interface independent of a user action. To update data on an AMX page to reflect the current values of data fields whose values have changed, you can avoid the performance hit associated with reloading the whole AMX page by calling AdfmfJavaUtilities.flushDataChangeEvent to force the currently queued data changes to the client.

Note:

The flushDataChangeEvent method can only be executed from a background thread.

Example 9-29 shows how the flushDataChangeEvent method can be used to force pending data changes to the client. For more information about oracle.adfmf.framework.api.AdfmfJavaUtilities.flushDataChangeEvent, see Oracle Fusion Middleware Java API Reference for Oracle ADF Mobile.

Example 9-29 Data Change Event Example

 
/* Note – Simple POJO used by the NotePad managed bean or data control wrapped bean */      
 
package mobile;
 
import oracle.adfmf.amx.event.ActionEvent;
import oracle.adfmf.framework.api.AdfmfJavaUtilities;
import oracle.adfmf.java.beans.PropertyChangeListener;
import oracle.adfmf.java.beans.PropertyChangeSupport;
 
 
/**
 * Simple note object
 * uid   - unique id - generated and not mutable
 * title - title for the note - mutable
 * note  - note comment - mutable
 */
public class Note {
    /* standard jdev generated property change support */
    protected transient PropertyChangeSupport 
                       propertyChangeSupport = new PropertyChangeSupport(this);
 
 
    private static boolean s_backgroundFlushTestRunning = false;
 
 
    public Note() {
        this("" + (System.currentTimeMillis() % 10000));
    }
 
    public Note(String id) {
        this("UID-"+id, "Title-"+id, "");
    }
 
    public Note(String uid, String title, String note) {
        this.uid     = uid;
        this.title   = title;
        this.note    = note;
    }
 
 
    /* update the current note with the values passed in */
    public void updateNote(Note n) {
        if (this.getUid().compareTo(n.getUid()) == 0) {
            this.setTitle(n.getTitle());
            this.setNote(n.getNote());
        } else {
            throw new IllegalArgumentException("note");
        }
    }
 
 
    /* background thread to simulate some background process that make changes */
    public void startNodeBackgroundThread(ActionEvent actionEvent) {
        Thread backgroundThread   = new Thread() {
            public void run() {
                System.out.println("startBackgroundThread enter - " + 
                                                      s_backgroundFlushTestRunning);
                
                s_backgroundFlushTestRunning = true;
                for(int i = 0; i <= iterations; ++i) {
                    try
                    {
                        System.out.println("executing " + i + " of " + iterations + "
                                " iterations.");
                        
                        /* update a property value */                    
                        if(i == 0) {
                            setNote("thread starting");
                        }
                        else if( i == iterations) {
                            setNote("thread complete");
                            s_backgroundFlushTestRunning = false;                        
                        }
                        else {
                            setNote("executing " + i + " of " + iterations + " iterations.");
                        }
                        setVersion(getVersion() + 1);
                        setTitle("Thread Test v" + getVersion());
                        AdfmfJavaUtilities.flushDataChangeEvent();  /* key line */
                    }
                    catch(Throwable t)
                    {
                        System.err.println("Error in the background thread: " + t);
                    }
 
                    try {
                        Thread.sleep(delay);  /* sleep for 6 seconds */
                    } catch (InterruptedException ex) {
                        // TODO Auto-generated catch block
                        ex.printStackTrace();
                    }
                }
            }
        };
        
        backgroundThread.start();
    }
    
    protected String uid;
    protected String title;
    protected String note;
    protected int    version;
 
    protected int    iterations =  10;
    protected int    delay      = 500;
    
    
    /* --- jdev generated accessors --- */
 
    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }
 
    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }
 
    public String getUid() {
        return uid;
    }
 
    public void setTitle(String title) {
        String oldTitle = this.title;
        this.title = title;
        propertyChangeSupport.firePropertyChange("title", oldTitle, title);
    }
 
    public String getTitle() {
        return title;
    }
 
    public void setNote(String note) {
        String oldNote = this.note;
        this.note = note;
        propertyChangeSupport.firePropertyChange("note", oldNote, note);
    }
 
    public String getNote() {
        return note;
    }
 
    public void setVersion(int version) {
        int oldVersion = this.version;
        this.version = version;
        propertyChangeSupport.firePropertyChange("version", oldVersion, version);
    }
 
    public int getVersion() {
        return version;
    }
 
    public void setIterations(int iterations) {
        int oldIterations = this.iterations;
        this.iterations = iterations;
        propertyChangeSupport.
                 firePropertyChange("iterations", oldIterations, iterations);
    }
 
    public int getIterations() {
        return iterations;
    }
 
    public void setDelay(int delay) {
        int oldDelay = this.delay;
        this.delay = delay;
        propertyChangeSupport.
                firePropertyChange("delay", oldDelay, delay);
    }
 
    public int getDelay() {
        return delay;
    }
}
         
 
 
  
/* NotePad – Can be used as a managed bean or wrapped as a data control */
           
package mobile;
 
import java.util.ArrayList;
import java.util.List;
 
import oracle.adfmf.amx.event.ActionEvent;
import oracle.adfmf.framework.api.AdfmfJavaUtilities;
import oracle.adfmf.java.beans.PropertyChangeListener;
import oracle.adfmf.java.beans.PropertyChangeSupport;
import oracle.adfmf.java.beans.ProviderChangeListener;
import oracle.adfmf.java.beans.ProviderChangeSupport;
 
 
public class NotePad {
    private static List     s_notes                      = null;
    private static boolean  s_backgroundFlushTestRunning = false;
    
    protected transient     PropertyChangeSupport 
        propertyChangeSupport = new PropertyChangeSupport(this);
 
    protected transient     ProviderChangeSupport 
        providerChangeSupport = new ProviderChangeSupport(this);
 
    public NotePad() {
        if (s_notes == null) {
            s_notes = new ArrayList();
            
            for(int i = 1000; i < 1003; ++i) {
                s_notes.add(new Note(""+i));
            }
        }
    }
 
    public  mobile.Note[] getNotes() {
        mobile.Note n[] = null;
 
        synchronized (this)
        {
            if(s_notes.size() > 0) {
                n = (mobile.Note[])s_notes.
                     toArray(new mobile.Note[s_notes.size()]);
            }
            else {
                n = new mobile.Note[0];
            }
        }
 
        return n;
    }
 
    public void addNote() {
        System.out.println("Adding a note ....");
        Note  n = new Note();
        int   s = 0;
        
        synchronized (this)
        {
            s_notes.add(n);
            s = s_notes.size();
        }
 
        System.out.println("firing the events");
        
        /* update the note count property on the screen */
        propertyChangeSupport.
             firePropertyChange("noteCount", s-1, s);
 
        /* update the notes collection model with the new note */
        providerChangeSupport.
             fireProviderCreate("notes", n.getUid(), n);
 
        /* to update the client side model layer */
        AdfmfJavaUtilities.flushDataChangeEvent();
    }
    
    public void removeNote() {
        System.out.println("Removing a note ....");
        if(s_notes.size() > 0) {
            int     end = -1;
            Note    n   = null;
 
            synchronized (this)
            {
                end   = s_notes.size() - 1;
                n     = (Note)s_notes.remove(end);
            }
            
            System.out.println("firing the events");
            
            /* update the client side model layer */
            providerChangeSupport.
                fireProviderDelete("notes", n.getUid());
 
           /* update the note count property on the screen */
           propertyChangeSupport.
                firePropertyChange("noteCount", -1, end);
        }
    }
    
    public void RefreshNotes() {
        System.out.println("Refreshing the notes ....");
 
        /* update the entire notes collection on the client */
        providerChangeSupport.fireProviderRefresh("notes");
    }
    
    public int getNoteCount() {
        int size = 0;
        
        synchronized (this)
        {
            size = s_notes.size();
        }
       return size;
    }
 
    public void 
    addProviderChangeListener(ProviderChangeListener l) {
        providerChangeSupport.addProviderChangeListener(l);
    }
 
    public void
    removeProviderChangeListener(ProviderChangeListener l) {
        providerChangeSupport.removeProviderChangeListener(l);
    }
 
    public void 
    startListBackgroundThread(ActionEvent actionEvent) {
        for(int i = 0; i < 10; ++i) {
            _startListBackgroundThread(actionEvent);
            try {
                Thread.currentThread().sleep(i * 1234);
            } catch (InterruptedException e) {
            }
        }
    }
    
    public void 
    _startListBackgroundThread(ActionEvent actionEvent) {
        Thread backgroundThread   = new Thread() {
            public void run() {
                s_backgroundFlushTestRunning = true;
                
                for(int i = 0; i <= iterations; ++i) {
                    System.out.println("executing " + i + 
                          " of " + iterations + " iterations.");
                    
                    try 
                    {
                        /* update a property value */                    
                        if(i == 0) {
                            setStatus("thread starting");
                            addNote();  // add a note
                        }
                        else if( i == iterations) {
                            setStatus("thread complete");
                            removeNote();  // remove a note
                            s_backgroundFlushTestRunning = false;                        
                        }
                        else {
                            setStatus("executing " + i + " of " + 
                                    iterations + " iterations.");
                            
                            synchronized (this)
                            {
                                if(s_notes.size() > 0) {
                                    Note n =(Note)s_notes.get(0);
                                
                                    n.setTitle("Updated-" + 
                                          n.getUid() + " v" + i);
                                }
                            }
                        }
                        AdfmfJavaUtilities.
                                 flushDataChangeEvent();
                    }
                    catch(Throwable t)
                    {
                        System.err.
                        println("Error in bg thread - " + t);
                    }
 
                    try {
                         Thread.sleep(delay);
                    } catch (InterruptedException ex) {
                        // TODO Auto-generated catch block
                        setStatus("inturrpted " + ex);
                        ex.printStackTrace();
                    }
                }
            }
        };
        
        backgroundThread.start();
    }
 
    
    protected int iterations = 100;
    protected int delay      = 750;
 
    protected String   status;    
    
    /* --- jdev generated accessors --- */
 
    public void 
    addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }
 
    public void 
    removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }
 
    public void setStatus(String status) {
        String oldStatus = this.status;
        this.status = status;
        propertyChangeSupport.
                firePropertyChange("status", oldStatus, status);
    }
 
    public String getStatus() {
        return status;
    }
 
    public void setIterations(int iterations) {
        int oldIterations = this.iterations;
        this.iterations = iterations;
        propertyChangeSupport.
                firePropertyChange("iterations", 
                                   oldIterations, iterations);
    }
 
    public int getIterations() {
        return iterations;
    }
 
    public void setDelay(int delay) {
        int oldDelay = this.delay;
        this.delay = delay;
        propertyChangeSupport.
                firePropertyChange("delay", oldDelay, delay);
    }
 
    public int getDelay() {
        return delay;
    }
}
         

The StockTracker sample application provides an example of how data change events use Java to enable data changes to be reflected in the user interface. This sample application is in the PublicSamples.zip file at the following location within the JDeveloper installation directory of your development computer:

jdev_install/jdeveloper/jdev/extensions/oracle.adf.mobile/Samples

For more information about sample applications, see Appendix E, "ADF Mobile Sample Applications."