ADF Mobile : Implementing the “Pull to Refresh” Pattern

0

One very common pattern in Mobile Apps is the use of “pull” to refresh a list of data in an App.

For one of my projects I suggested to use this pattern and I had to figure out a way to implement this in ADF Mobile. In this post I show you a way to implement this. If you are only interested in the refresh part, you might want to skip the first 5 steps. These explain in short the webservice and the basic ADF Mobile app. From step 6 on I explain how to implement the pattern

1. The webservice

For this example I use a simple webservice that returns a List of Locations. The webservice is a service enabled Application Module. I simply created Business Component (EO and VO) on the Locations table from the HR schema. I exposed the Locations view object as AllLocations, and added a method called getCurrentCount() on the Application Module Impl class. This method simply invokes the getEstimatedRowCount() to find out the number of rows. Next I created a new Service Interface on the Application Module. And that is about it for the service.

webserviceApp

This all resides in a separate application and is deployed on a remote weblogic server. The mobile app will use this webservice.

2. The Mobile App

Create a simple ADF Mobile Application. In this application I need to call out to the webservice. For that I use a webservice datacontrol. Simply invoke the “create webservice” dialog, give the webservice a name and enter the URL of the WSDL.

createDC-1

In the second part of the wizard, I simply select the two operations that I need.

CreateDC-2

The webservice datacontrol is now finished. Both operations are now available from the datacontrol. The datacontrol looks like this:

datacontrol

3. The custom model

For the application I want to use a custom object model in order to be independent of the webservice data structure. This is common practice and recommended approach. The class that I use for this is called MyLocation.

MyLocation-Class

I use the following properties:


package nl.amis.technology.mob.objects;
public class MyLocation {
private int LocationId;
private String StreetAddress;
private String PostalCode;
private String City;
private String StateProvince;
private String CountryId;
public MyLocation() {
super();
}
}

Simply generate accessors for these and the class is finished.

4. Invoking the webservice

Now I create a new class that I use in my mobile application to provide the app with data. This class is called LocationInfoDC. In this class I create a list of locations that I can display in the mobile app.


public class LocationInfoDC {
private static List s_locationsList;
private transient ProviderChangeSupport providerChangeSupport = new ProviderChangeSupport(this);
public LocationInfoDC() {
super();
if (s_locationsList == null) {
s_locationsList = new ArrayList();
retreiveLocationInformation();
}
}
public void AddLocation(MyLocation l) {
s_locationsList.add(l);
}
public MyLocation[] getAllLocations() {
MyLocation locs[] = null;
locs = (MyLocation[])s_locationsList.toArray(new MyLocation[s_locationsList.size()]);
return locs;
}
......

For webservice invocation I use AdfmfJavaUtilities.invokeDataControlMethod(). This enables me to invoke a Datacontrol Method whithout having a page Definition available. It is a powerful mechanism.


// This calls the DC method and gives us the Return
GenericType result = (GenericType) AdfmfJavaUtilities.invokeDataControlMethod("HrWsDc"
, null
,"findAllLocations"
,pnames, params, ptypes);

The AdfmfJavaUtilities.invokeDataControlMethod() returns a GenericType. This response needs to be “converted” to the custom object model described in step 3.


// The Return wraps the findAllLocations Result, so get that out of the Result
s_locationsList.clear();
for (int i = 0; i < result.getAttributeCount(); i++)
{
GenericType row = (GenericType)result.getAttribute(i);
MyLocation locationInformation = (MyLocation)
GenericTypeBeanSerializationHelper.fromGenericType(MyLocation.class, row);
AddLocation(locationInformation);
}

Another important part is the invocation of the client page refresh. 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. I refresh the collection delta, 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)

I use the fireProviderRefresh, asking for the client to refresh the entire collection.

The trick that does the magic:


providerChangeSupport.fireProviderRefresh("allLocations");

For this class I also generate a datacontrol so I can use it on the mobile page.

5. The Mobile Page

The view for the list is a simple amx page.

Create the page and drag the AllLocations collection from the datacontrol onto the page. In the popup menu pick “ADF Mobile List View”.

dndAsList

Actually that is all that needs to be done. Now after deploying, the app looks like this.

Now it is time to implement the “pull to refresh”.

6. Implementing the pull to refresh: Calling the service

In order to refresh the list, I need to know if there are actually new records available in the database.

This information is provided by the getCurrentCount webservice operation. First I create a new pageflowscoped backing bean to hold all logic involved in this functionality. In this bean I have one property. This is used to store the latest record count. This count is used to make sure that the findAllLocations webservice is only invoked if necessary.


public class LocationsBackingBean {
String count="0";
private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
public LocationsBackingBean() {
}
public void setCount(String count) {
String oldCount = this.count;
this.count = count;
propertyChangeSupport.firePropertyChange("count", oldCount, count);
}
public String getCount() {
return count;
}

To call the webservice that returns the current record count (getCurrentCount) I also use AdfmfJavaUtilities.invokeDataControlMethod().

The returned value (= the current number of rows in the database table) is compared to the previous amount returned by this service call. If there are more rows in the database table, I want to refresh the List. To do that, I simply call out to the “findAllLocations” service. After successful invocation, the count is updated to the new value.


public void checkForUpdates(ActionEvent ea) {
......
String result;
try {
// This calls the DC method and gives us the Return
result = (String)
AdfmfJavaUtilities.invokeDataControlMethod("HrWsDc"
, null
, "getCurrentCount"
, pnames, params, ptypes);
// After service call, compare result to current count
// If new records are available, refresh the list by calling the webservice via invoking
// the corresponding method binding
if (Integer.valueOf(this.count).compareTo(Integer.valueOf(result))<0){
AdfELContext adfELContext = AdfmfJavaUtilities.getAdfELContext();
MethodExpression me = AdfmfJavaUtilities.getMethodExpression(
"#{bindings.retreiveLocationInformation.execute}"
, Object.class, new Class[]{});
me.invoke(adfELContext, new Object[]{});
// after succesfully refreshing the list, update the current number of rows
setCount(result);
}
} catch (AdfInvocationException e) {
............ }
catch (Exception e2) {
............... }
}

7. Implementing the pull to refresh: Configure the Listview

I want to refresh the list with Locations whenever the list is pulled down.

For this I use the amx:actionlistener component. This component allows you to declaratively invoke an action. This component is meant to be a child component of any tag that has some kind of listener attribute like actionListener, or valueChangeListener, etc. The type attribute defines which listener of the parent this actionListener should be fired for. These methods fire before the listener on the parent component fires.

In the actionListener I invoke the checkForUpdates method that was described earlier.

The component has two attributes: type and binding. Type is set to swipeDown and in the binding attribute I use the checkForUpdates method that is implemented in the locationsbean.

<amx:actionListener type="swipeDown"
binding="#{pageFlowScope.locationsBackingBean.checkForUpdates}" />

The complete code for this simple Listview now looks like this:

<amx:listView var="row" value="#{bindings.allLocations.collectionModel}"
fetchSize="#{bindings.allLocations.rangeSize}"
id="lv1">
<amx:listItem id="li1">
<amx:actionListener type="swipeDown"
binding="#{pageFlowScope.locationsBackingBean.checkForUpdates}" />
<amx:tableLayout width="100%" id="tl1">
<amx:rowLayout id="rl1">
<amx:cellFormat width="10px" id="cf3"/>
<amx:cellFormat width="60%" height="43px" id="cf1">
<amx:outputText value="#{row.streetAddress}"

id="ot2" inlineStyle="font-size:x-small;"/>
</amx:cellFormat>
<amx:cellFormat width="10px" id="cf2"/>
<amx:cellFormat width="40%" halign="end" id="cf4">
<amx:outputText value="#{row.city}"

styleClass="adfmf-listItem-highlightText" id="ot3"
inlineStyle="font-size:small;"/>
</amx:cellFormat>
</amx:rowLayout>
</amx:tableLayout>
</amx:listItem>

8. The result

After deploying the app it is time to test the behavior. After starting the app and the list shows some Locations.

Now add 1 or 2 Locations to the database and commit these changes.

add 2 records

Swipe the list down, release and see what happens…….

 

pulldownresult

 

The new Locations are displayed.

9. Some Final Notes

This example (and actually the pattern) works for lists that show rather static data (no update or deletes). Every “pull down” gesture checks the server for new rows. That is, it checks if there are more rows then during the last call, and if so, the new rows are returned and added to the list. So whenever there are deleted or changed rows, this is not reflected in this sample. I could have implemented a service only returning the new Locations and add those to the list, but for this example I decided to simply fetch all Locations and rebuild the list. One other improvement is to only invoke the webservices if the “pull down” is invoked on the first row in the list. This post was initially posted on lucbors.blogspot.com

Share.

About Author

Luc Bors is Expertise Lead ADF and technical specialist/architect at AMIS, Nieuwegein (The Netherlands). He developed several Workshops and training on ADF and also is an ADF and JHeadstart instructor. Luc is a member of the ADF Methodology group and publishes articles on ADF in oracle technology related magazines, on the AMIS technology blog, (http://technology.amis.nl/blog).

Leave a Reply