ADF Mobile – Preserving the Current Row Across Pages

Introduction

 Unlike “bigADF”, the current row in ADF Mobile is not preserved across pages that bind to the same data collection in your data control. The standard approach to preserve row currency is labor intensive and not straightforward. In this article we will explain a more advanced technique to preserve the row currency which is more elegant and less work and can be easily removed once this has become a native feature in ADF Mobile.

Main Article

The typical use case where you would like to preserve the row currency is the list-form user interface pattern where the user is first presented with a list of objects, and by tapping on one of the rows in the list, the user navigates to a detail page to edit or view the selected object.

ListForm

The Standard Solution

Currently, there are a number of steps you need to take to keep the current row in this use case:

  • Inside the <amx:listItem> element of the list page, you need to add a <amx:setPropertyListener> element to store the row key in a pageFlowScope variable.
  • In the page definition of the detail page, you need to add a setCurrentRowWithKey action, which uses the pageFlowScope variable to set the current row.
  • In the page definition of the detail page, you need to add an invokeAction executable for the setCurrentRowWithKey action to ensure the current row is automatically set when entering the detail page.

For a detailed explanation of these steps, you can take a look at this ADF Mobile tutorial (pages 28-29 and 42-28). While these steps in itself are not difficult to do, it is additional work, and must be repeated for each target page where you want to preserve the row currency. In addition, for developers new to ADF Mobile, this invokeAction concept might be a bit hard to understand. For developers used to working with “bigADF” it might feel uncomfortable to use the invokeAction executable, as Oracle has recommended to move away from this construct now that we have ADF task flows where we can use method activities to execute logic before we navigate to a view activity.
Finally, it is planned that in a future release of ADF Mobile, the current row will be automatically preserved across pages, and that would mean you have to remove this custom row currency code again.

A Better Solution

Understanding the solution requires a bit more understanding of ADF binding internals. When you turn your Java class into an ADF data control, an entry in file DataControls.dcx is created.

DataControls

The ImplDef property inside the AdapterDataControl element specifies the oracle.adf.model.adapter.bean.BeanDCDefinition class that is responsible for creating the iterator bindings in each page, based on the entries in the associated page definition. In this class, method getIteratorBinding returns a new instance of the various iterator bindings needed in each page. So, by subclassing this BeanDCDefinition class, overriding method getIteratorBinding, and registering our subclass in the ImplDef property we can “plug in” custom code to the iterator binding creation process.

The custom code we plug in keeps track of the id and “binding path” for each iterator binding, and stores the current row index with this binding id and path. For example, if you have created a DepartmentService data control, and this data control has a getDepartments method returning an array of departments, then this array will show up as departments in the data control palette.

Data Control Palette

When you drag and drop this departments collection onto a page, and then run the app, the iterator binding id and path we store in our subclassed getIteratorBinding method is departmentsIterator:DepartmentService.departments. And with this binding id-path key we store the current row index. The current row index will be 0 by default, but the user might perform actions in page to select a specific row which will change the current row index, as we will see later on. When you then navigate to another page that requires an iterator binding that has the same binding id-path key, we apply the previously stored current row index on the newly created iterator binding.

Here is the complete code of our subclass:

  /**
   * This method contains the logic to preserve row currency across pages.
   * For each iterator binding the binding id, concatenated with the "binding path" is stored in an 
   * 'iteratorsCurrentRowIndex' map together with the current index. 
   * The iterator binding itself is created by calling super. If the binding id-path of the newly 
   * created iterator binding is already present in the map, the row index in the map is set as current 
   * index on the newly created iterator binding.
   * @param object
   * @param xmlAnyDefinition
   * @param bindingContainer
   * @return
   */
  public BasicIterator getIteratorBinding(Object object, XmlAnyDefinition xmlAnyDefinition,
                                          BindingContainer bindingContainer) {
      BeanBindingIteratorBaseDefinition bindingDef = (BeanBindingIteratorBaseDefinition)xmlAnyDefinition;
      String binds = bindingDef.getBinds();
      // build up the iterator binding path by walking up the MasterBinding path until Root is
      //encountered.
      StringBuffer iteratorPath = new StringBuffer(binds);
      BeanBindingIteratorBaseDefinition curBindingDef = bindingDef;
      while (curBindingDef.getMasterBinding() != null) {
          iteratorPath.insert(0, curBindingDef.getMasterBinding()+".");
          AmxIteratorBinding curIter = (AmxIteratorBinding)bindingContainer.get(curBindingDef.getMasterBinding());
          curBindingDef = (BeanBindingIteratorBaseDefinition)curIter.getMetadataDefinition();
      }
     // prefix iter path with binding id, so we can support multiple iterators in to same data collection that can
     // have their own current row index.
      iteratorPath.insert(0, bindingDef.getId() + ":");
      BasicIterator iter = super.getIteratorBinding(object, xmlAnyDefinition, bindingContainer);
      BasicIterator previousIter = (BasicIterator)iteratorsCurrentRowIndex.put(iteratorPath.toString(), iter);
      if (previousIter != null && previousIter.getCurrentIndex() > -1 &&
          iter.getTotalRowCount() > previousIter.getCurrentIndex()) {
          iter.setCurrentIndex(previousIter.getCurrentIndex());
      }
      return iter;
  }

And as explained before, to use this class we simply specify it in the ImplDef property in DataControls.dcx.

DataControlsModified

Now, the last step is to set the current row index when we tap on a row in an ADF Mobile list view. This can be done by using a setPropertyListener as shown below (the code has been updated to work with ADF Mobile patch 11.1.2.4.1, now using rowKey and currentIndexWithKey as from and to properties) .

<amx:listView var="row" value="#{bindings.departments.collectionModel}"
              fetchSize="#{bindings.departments.rangeSize}" id="lv1">
  <amx:listItem id="li1" action="view">
    <amx:outputText value="#{row.name}" id="ot2"/>
    <amx:setPropertyListener from="#{row.rowKey}" type="action"
                             to="#{bindings.departmentsIterator.iterator.currentIndexWithKey}" id="spl2"/>
  </amx:listItem>
</amx:listView>

That’s all! Every page you will navigate to that uses the same binding id and path will show the department row the user tapped on.If you have a use case where you do NOT want to preserve the row currency from a page, you only need to make sure that the id of the binding iterator is different from the previous page. You can change the iterator id in the page definition XML. If you do so, don’t forget to change the control bindings that reference this iterator as well.

Once ADF Mobile natively supports this feature, you can simply change the value of the ImplDef property in DataControls.dcx back to its default. In a future release, ADF Mobile will also automatically set the current row index when tapping on a row in the list. Once that is implemented you can also remove the <amx:setPropertyListener> inside the <amx:listItem> element.

A sample application (JDeveloper 11.1.2.4) that contains the source code and demonstrates this technique can be downloaded here.

One More Step: Preventing an Empty Data Control

You probably first created your list and edit page by dragging and dropping from your data control, and then you changed the ImplDef property in DataControls.dcx. You run your mobile application and you are happy to see how the row currency is preserved. However, there is one little issue that you hit once you restart JDeveloper, or when you hit the refresh button on the data control palette. Your data control will show up empty, as shown below.

DataControlsEmpty

If you look at the log window in JDeveloper, you will understand why your data control shows as an empty node: JDeveloper is unable to find our PreserveRowCurrencyBeanDefinition subclass.

DCLoadError

To fix this, we need to extend the default design-time bean definition lookup mechanism. To fix this issue you need to install the CustomBeanDCDefinition extension jar file in the [jdev_home]\jdev\extensions directory, restart JDeveloper, and then add the library Enable Custom DC Bean Definition to your project.

Library

Note that this is a generic extension, it will enable loading of any custom subclass registered in the ImplDef property in DataControls.dcx. If you are interested in the details of this extension, you can download the JDeveloper extension project here. If you compile the project, right-mouse-click on the project node and choose Deploy to target Platform, the same jar file will be copied to the [jdev_home]\jdev\extensions directory.

A-team has logged enhancement request 16971389 to make use of a custom DC bean definition fully supported out of the box.

Add Your Comment