Wednesday, March 18, 2015

MAF 2.1 : Mobile Front End for Live Mobile Hacking with an OFM 12c red stack - Part I

During the closing keynote at the Oracle Fusion Middleware Forum XX in Budapest my former colleague Lucas Jellema and I delivered a live development demo. It was great fun to do. During this demo I developed an Oracle MAF frontend on top of Oracle SOA Suite 12c REST-JSON services. All in all it was a good session that was exactly delivered on-time with no errors. I think the demo gods where with us.
In this post I describe some of the details regarding the Oracle MAF part of this demo. To be more specific, how the MAF application is able to work with the REST-JSON Services. In a later post I will go into the details of how to user interface was built. The details of the SOA suite 12c Backend are described by Lucas at the AMIS Technology blog in the following posts:

Preparing ,

Implementing part - 1  and

Implementing part – 2



Before I start with the details I best describe the case first. We decided to create a tablet app that can be used by flight attendants to browse flight details, change the flight status (from boarding to boarding complete for instance), and browse the passenger list. Also, in case a customer wants to file a complaint for some reason, a flight attendant can do this using the app. For all these purposes the SOA Suite 12c backend exposes the necessary RESTJ-SON Services.



To create a MAF application that is able to consume these services I first need to create Connections to these services, next I need to transform the JSON Object to Java objects and finally I need to

Setting up Connections

In order to “talk” to the backend REST-JSON Services I need to connect to them by means of a REST connection. This can be easily created in JDeveloper. Simply create a connection that points to the URL endpoint of the REST-JSON Service.



I used SOAP-UI to mock the services. This helps a lot, especially because Lucas and I are not really connected all the time. This way he could work on the backend and I on the mobile front end.

The JSON Object
When I call the Service it returns a JSON Object that contains all information regarding a specific Flight. The JSON Object looks like the one below. This is the format and structure that Lucas and I agreed on.

1:  {  
2:    "FlightCode":  {  
3:     "CarrierCode": "KL",  
4:     "FlightNumber": "34"  
5:    },  
6:    "FlightStatus": "boarding",  
7:    "FlightDate": "2015-03-07T+01:00",  
8:    "Departure":  {  
9:     "Time": "2015-03-07T09:50:00+01:00",  
10:     "AirportCode": "AMS",  
11:     "AirportName": "Schiphol Airport",  
12:     "City": "Amsterdam",  
13:     "Country": "NL"  
14:    },  
15:    "Destination":  {  
16:     "Time": "2015-03-07T11:55:00-08:00",  
17:     "AirportCode": "SFO",  
18:     "AirportName": "San Francisco International Airport",  
19:     "City": "San Francisco",  
20:     "Country": "US"  
21:    }  
22:  }  

Working with the JSON Object

To work with the JSON Object in the MAF app, when data is read to or from REST services, some additional processing is required to translate the JSON Object (which in fact is a String) to and from Java objects. This proces is called (de-) serialisation.

All of the classes that I created to work with JSON in this MAF app are displayed in the following class Hierarchy. I will discus them bottom up and for clarity in the remaining part of this post I will explain how to work with the FlightDetail Object. That is the one in the middle (top to bottom) of the class hierarchy diagram.


The RestCallerUtil Class

The RestCallerUtil class wraps the MAF calls to the REST service, thus further simplifying the REST interaction, avoiding you to write unnecessary boilerplate code over and over again.
The RestCallerUtil class contains some utility methods such as invokeREAD() (line 12). This methode takes the RequestURI of the service call as an argument and then calls the invokeRequest() method. The InvokeRequest() method finally calls the REST Service (line 54), returning the response of the service call. The MAF Framework has a RestServiceAdapter class that facilitates calling REST Services. to call a REST Service, simply create an instance of that class, set the connection name to the connection that was created earlier (Line 35), set the Request type (Line 37), set the RequestURI (Line 43) and finally call the service (Line 54). The request URI identifies the actual resource that we need. This will be explained later.

The way the RestCallerUtil class is setup enables you to simply add more and more service calls by adding more and more of these simple methods. The method that actually invokes the service call does not change.

1:  package com.fhacust.app.mobile.utils;  
2:  import java.util.logging.Level;  
3:  import oracle.adfmf.dc.ws.rest.RestServiceAdapter;  
4:  import oracle.adfmf.framework.api.Model;  
5:  import oracle.adfmf.util.Utility;  
6:  import oracle.adfmf.util.logging.Trace;  
7:  public class RestCallerUtil {  
8:    public RestCallerUtil() {  
9:      super();  
10:    }  
11:    //GET  
12:    public String invokeREAD(String requestURI){  
13:      return this.invokeRestRequest(RestServiceAdapter.REQUEST_TYPE_GET, requestURI, "");  
14:    }  
15:    //POST  
16:    public String invokeUPDATE(String requestURI, String payload){  
17:      return this.invokeRestRequest(RestServiceAdapter.REQUEST_TYPE_POST, requestURI, payload);  
18:    }  
19:    public String invokeUPDATEcust(String requestURI, String payload){  
20:      return this.invokeRestRequestCust(RestServiceAdapter.REQUEST_TYPE_POST, requestURI, payload);  
21:    }  
22:    /**  
23:     * Method that handles the boilerplate code for obtaining and configuring a service   
24:     * adapter instance.   
25:     *   
26:     * @param httpMethod GET, POST, PUT, DELETE  
27:     * @param requestURI the URI to appends to the base REST URL. URIs are expected to start with "/"  
28:     * @return  
29:     */  
30:    private String invokeRestRequest(String httpMethod, String requestURI, String payload){  
31:      String restPayload = "";  
32:      RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();  
33:      restServiceAdapter.clearRequestProperties();  
34:      //set URL connection defined for this sample.   
35:      restServiceAdapter.setConnectionName("FlightsREST");  
36:      //set GET, POST, DELETE, PUT  
37:      restServiceAdapter.setRequestType(httpMethod);  
38:      //this sample uses JSON only. Thus the media type can be hard-coded in this class  
39:      //the content-type tells the server what format the incoming payload has  
40:      restServiceAdapter.addRequestProperty("Content-Type", "application/json");  
41:      //the accept header indicates the expected payload fromat to the server  
42:      restServiceAdapter.addRequestProperty("Accept", "application/json; charset=UTF-8");  
43:      restServiceAdapter.setRequestURI(requestURI);      
44:      restServiceAdapter.setRetryLimit(0);    
45:      Trace.log(Utility.ApplicationLogger,Level.INFO, this.getClass(),"invokeRestRequest", requestURI);  
46:      //variable holding the response  
47:      String response = "";  
48:      //set payload if there is payload passed with the request  
49:      if(payload != null){    
50:         //send with empty payload  
51:         restPayload = payload;  
52:      }  
53:      try {  
54:        response = (String) restServiceAdapter.send(restPayload);  
55:      } catch (Exception e) {  
56:        //log error  
57:        Trace.log("REST_JSON",Level.SEVERE, this.getClass(),"invokeRestRequest", e.getLocalizedMessage());  
58:      }  
59:      Trace.log(Utility.ApplicationLogger,Level.INFO, this.getClass(),"response= ", response);  
60:      return response;  
61:    }  


The FlightCustURI class contains methods that return the request URIs for a specific REST request like GetFlightsByNumber, PostFlightStatusUpdate and PostCustomerComplaint.
For each and every resource that we want to use, this class defines a static String holding the exact URI. In order to write this class it is necessary to know the URI's. Let's look at the REST Service call for FlightDetails, or more specific, the details of a given flight. That call looks like this :

http:///FlyingHigh/FlightService/FlightService/flights/KL34

The part in bold is the endpoint that was defined in the REST connection that was setup earlier. The part /flights/KL34  is the URI that we are looking for. The flightnumber, in this case KL34, is dynamic and changes if we need info for a different flight. Now based on this information we can create the FlightCustURI class and add the getFlightsByNumberURI method. This method takes the flightNumber as an argument and returns the exact URI that we need to call the service (Line 7).

1:  package com.fhacust.app.mobile.uri;  
2:  public class FlightCustURIs {  
3:    private static final String FLIGHTS_URI = "/flights";  
4:    private static final String CUSTOMER_URI = "/complaints";    
5:    //Flights URI  
6:    public static String GetAllFlightsURI(){ return FLIGHTS_URI; }  
7:    public static String GetFlightsByNumberURI(String flightNumber){ return FLIGHTS_URI+"/"+flightNumber;}  
8:    public static String GetPassengersinFlight(String flightNumber){ return FLIGHTS_URI+"/"+flightNumber+"/passengerlist";}  
9:    public static String PostFlightStatusUpdateURI(){ return FLIGHTS_URI;}  
10:    //customer URI  
11:    public static String PostCustomerComplaintURI(){return CUSTOMER_URI;}  
12:  }  

From JSON to Java

Now that the infrastructure for calling the REST-JSON Service is setup, I must make sure that I can work with the JSON Object in my MAF app. I use a couple of classes that help me to convert the JSON Object to my Java Objects. In this section I will explain how I get from the JSON Object for FlightDetails to my Java Object that represents the Flight Details. In this scenario I use the following 3 classes:
  • JsonObjectToFlightDetailsObject (Translates JSON String to Java Object)
  • FlightDetailsObject (Mimics the JSON payload for FlightDetail)
  • FlightDetailsEntity (Definition of FlightDetails Object)

All of these team up to take care of the JSON conversion.

1:  package com.fhacust.app.mobile.json.helper;  
2:  import com.fhacust.app.mobile.entities.FlightDetailsEntity;  
3:  import java.util.logging.Level;  
4:  import oracle.adfmf.framework.api.JSONBeanSerializationHelper;  
5:  import oracle.adfmf.util.logging.Trace;  
6:  public class JsonObjectToFlightDetailsObject {  
7:    public JsonObjectToFlightDetailsObject() {  
8:      super();  
9:    }  
10:    public static FlightDetailsEntity getFlightsObject(String jsonObjectAsString) {  
11:      FlightDetailsEntity flightsResult = null;  
12:      //object that serializes the JSON payload into the Java object  
13:      JSONBeanSerializationHelper jbsh = new JSONBeanSerializationHelper();  
14:      try {  
15:        flightsResult = (FlightDetailsEntity) jbsh.fromJSON(FlightDetailsEntity.class, jsonObjectAsString);  
16:      } catch (Exception e) {  
17:        Trace.log("JSONArray_to_JavaArray", Level.SEVERE, JsonObjectToFlightDetailsObject.class, "getFlightsObject",  
18:             "Parsing of REST response failed: " + e.getLocalizedMessage());  
19:      }  
20:      return flightsResult;  
21:    }  
22:  }  

The FlightDetailsObject is used to translate the JSON Object to the Java FlightDetailsEntity Object in one go. Note that this FlightDetailsObject  class is not really necessary in this specific case because the JSON Object is not an array of objects. However if the JSON object that is returned by the service call is an actual Array of objects, the FlightDetailsObject can be used to work with the array. For our current example it now has only setters and getters for the flightDetails object of type FlightDetailsEntity. In case of an array I would change that to FlightDetailsEntity[].

1:  package com.fhacust.app.mobile.json.dao;  
2:  import com.fhacust.app.mobile.entities.FlightDetailsEntity;  
3:  public class FlightDetailsObject {  
4:    /**  
5:     * class that mimics the structure of the JSON payload for the flights request  
6:     *  
7:     */  
8:    private FlightDetailsEntity flightDetails = null;  
9:    public FlightDetailsObject() {  
10:      super();  
11:    }  
12:    public void setFlightDetails(FlightDetailsEntity flightDetails) {  
13:      this.flightDetails = flightDetails;  
14:    }  
15:    public FlightDetailsEntity getFlightDetails() {  
16:      return flightDetails;  
17:    }  
18:  }  


Finally I use class called FlightDetailsEntity which is the detailed implementation of the FlightDetails object with all the necessary getters and setters.

1:  package com.fhacust.app.mobile.entities;  
2:  import oracle.adfmf.java.beans.PropertyChangeListener;  
3:  import oracle.adfmf.java.beans.PropertyChangeSupport;  
4:  public class FlightDetailsEntity {  
5:    private FlightCodeEntity flightcode;  
6:    private String flightDate;  
7:    private String flightStatus;  
8:    private SlotEntity departure;  
9:    private SlotEntity destination;  
10:    public FlightDetailsEntity() {  
11:      super();  
12:    }  
13:    here go getters and setters  

And the FlightCodeEntity class that is used to hold FlightNumber and CarrierCode.

1:  package com.fhacust.app.mobile.entities;  
2:  import oracle.adfmf.java.beans.PropertyChangeListener;  
3:  import oracle.adfmf.java.beans.PropertyChangeSupport;  
4:  public class FlightCodeEntity {  
5:    private String CarrierCode;  
6:    private String FlightNumber;  
7:    public FlightCodeEntity() {  
8:      super();  
9:    }  
10:   here go getters and setters  

Note that I created two other groups of these classes (FlightPassengerList and the CustomerComplaint) that enable me to work with these REST-JSON services as well.

Calling the REST Services from the MAF app

So far I created everything that I need in order to call the REST-JSON Service and to deserialise the result of that service call. While preparing this demo Lucas and I decided to actually have 4 services that the MAF UI needs. These are the following services.
  • findFlightByNumber
  • getFlightPassengers
  • updateFlightStatus
  • createCustomerComplaint
In order to actually call these services, using all the infrastructure code that was created in the first part of this post, I now create a JavaClass that I call FlightPassengersDC. This class holds all the methods for all of the service calls that I need. Because I only discuss the flightDetails part in this post, I will not post all the code here. It can be downloaded from GIT (link is provided at the bottom of this post) but I will show the part up till findFlightByNumber().

All that I need to call the service is a simple method ( getFlightDetails() ) uses the RequestURI (Line 34) to get the correct URI,  the RestCallerUtil (Line 36) to call the service and finally the JsonObjectToFlightDetailsObject.getFlightsObject() (Line 37) to deserialise the result to Java Objects.


1:  ackage com.fhacust.app.mobile.datacontrol;  
2:  import com.fhacust.app.mobile.entities.CustomerComplaintEntity;  
3:  import com.fhacust.app.mobile.entities.DepartureDestinationEntity;  
4:  import com.fhacust.app.mobile.entities.FlightCodeEntity;  
5:  import com.fhacust.app.mobile.entities.FlightDetailsEntity;  
6:  import com.fhacust.app.mobile.entities.FlightPassengerListEntity;  
7:  import com.fhacust.app.mobile.entities.PassengerEntity;  
8:  import com.fhacust.app.mobile.json.helper.CustomerComplaintToJson;  
9:  import com.fhacust.app.mobile.json.helper.FlightDetailsEntityToJson;  
10:  import com.fhacust.app.mobile.json.helper.JsonObjectToFlightDetailsObject;  
11:  import com.fhacust.app.mobile.json.helper.JsonObjectToFlightPassengerListObject;  
12:  import com.fhacust.app.mobile.uri.FlightCustURIs;  
13:  import com.fhacust.app.mobile.utils.RestCallerUtil;  
14:  import oracle.adfmf.java.beans.ProviderChangeListener;  
15:  import oracle.adfmf.java.beans.ProviderChangeSupport;  
16:  public class FlightPassengersDC {  
17:    String flightNumber ="KL34";  
18:    //Details for a selected flight.   
19:    private FlightDetailsEntity flightDetails = null;  
20:    //all Passengerrs. We fecth the passnegers only once as this is not expected to  
21:    //change in the context of the flight  
22:    private FlightPassengerListEntity flightPassengers = null;  
23:    public FlightPassengersDC() {  
24:      super();  
25:    }   
26:    public void findFlightByNumber(String flightNr){  
27:      if (flightNr!=null&&flightNr!=""){  
28:        this.flightNumber=flightNr;  
29:      }  
30:      getFlightDetails();  
31:    }  
32:    public FlightDetailsEntity getFlightDetails() {  
33:      if(flightDetails == null){   
34:        String restURI = FlightCustURIs.GetFlightsByNumberURI(this.flightNumber);  
35:        RestCallerUtil rcu = new RestCallerUtil();  
36:        String jsonObjectAsString = rcu.invokeREAD(restURI);  
37:        FlightDetailsEntity flights = JsonObjectToFlightDetailsObject.getFlightsObject(jsonObjectAsString);  
38:        flightDetails = flights;  
39:      }  
40:      return flightDetails;  
41:    }  
42:  more here below .........  


That is all. Now I can call the services and translate the payload to and from Java Objects. It is time to expose the data to the UI. This is done by using a Data control.

From Rest Service to MAF UI: The datacontrol

The datacontrol can be created from the FlightPassengersDC Class. Data controls in MAF expose the service data model to the mobile client developer. I can create a datacontrol based on this class, and the datacontrol will expose all the methods from the class as operations, and all .

  


From here on it is easy to create AMX pages that display flightDetails and Passengers. The easiest way is to simply drag and drop from the datacontrol on to an AMX page. The code in GIT shows the end result of you would do it like this. For the live demo that was all that time permitted.

In a next post I will describe how I created a more fancy User Interface.

Resources


The code for the MAF app can be downloaded from GIT.

Wednesday, December 03, 2014

MAF 2.0 : Custom Toggle Springboard Functionality (or how I discovered AdfmfSlidingWindowUtilities)

Mobile apps usually have the possibility to toggle the springboard by using an icon that is displayed in the header of the app. The Oracle MAF reference app, Work Better, also tries to implement this behavior. The showing of the springboard works fine, however, hiding it does not really work as expected. In this post I show you how to implement a working custom toggle springboard functionality.

Default Toggle Springboard Implementation

First let's take a look at how the toggle springboard functionality works out of the box. In your application configuration file you need to set the "Show Springboard Toggle Button" to true in order to enable toggle functionality.

This image is described in the surrounding text
All the rest is taken care of by the framework at runtime and these setting results in the default toggle springboard icons to show up on both iOS and Android. Note that this of course also works with a custom springboard.


This image is described in the surrounding text

An obsolete way to implement Custom Springboard Toggle (you might want to skip reading this)

Now lets see what we need to implement the custom functionality. First we need to show the springboard. This can be done by calling gotoSpringboard() on the containerUtilities class, or by invoking it from the applicationFeatures Datacontrol.

 AdfmfContainerUtilities.gotoSpringboard();  

This all pretty straightforward and provided by the framework.

Second we need to be able to hide the springboard. This can be done by no particular builtin. However, when you call gotoFeature(), the springboard is hidden and the requested feature is displayed.

 AdfmfContainerUtilities.gotoFeature("feature.id");  

It works ok, but what if you don't select a feature to go to, and simply want to stay on the already active feature ? In that case we could really use a hideSpringboard() method, or something similar.
If we create a hideSpringboard() and combine it under one button with the gotoSpringboard() we can use this one button to show and hide the springboard. In order to implement all this we also need to know whether or not the springboard is visible. For that we can use a simple custom property in a bean, lets call it springboardToggleFlag.

Whenever the springboard is shown, we invert the state of the springboardToggleFlag:

 springboardToggleFlag=!springboardToggleFlag;  

and the app knows the state of the springboard. All that we need to do from here is find a way to nicely show and hide the springboard.

While figuring out how to implement the rest of this example I surprisingly found that the solution is already provided by the Framework and also somewhat documented and also available in the PublicSamples provided by Oracle. Because I never had any previous requirement to implement this functionality I totally missed that Oracle added this to the framework. Also I am not sure in what specific version it was added. I know now that the in the MAF 2.0.0 docs it is mentioned very briefly, and in the MAF 2.0.1 docs it is described in a more elaborate way, including a sample app. The API documentation was already available in MAF 2.0.0.  Below you can read the details on where to find this samples and docs.

The (Somewhat) Out of the Box Implementation

By implementing the oracle.adfmf.framework.api.AdfmfSlidingWindowUtilities interface in the application lifecycle listener (ALCL), you can use an application feature as a sliding window, which displays concurrently with the other application features that display within the navigation bar or springboard. You can use a sliding window to display content that always present within the application, such as a Springboard.

An example of the implementation can be found in a workspace called "slidingWindows", and is aprt of the Public Samples. This application demonstrates the use of the AdfmfSlidingWindowUtilities API, which can be used to display multiple features on the screen at the same time. This sample shows how you can create a custom springboard using the AdfmfSlidingWindowUtilities API.


Note that the sliding window plugin API can only be used for features defined within the application that do not appear in the navigation bar and is not the springboard feature .

So in order to make a custom springboard that nicely slides in and out of view we need to instruct the app that it has NO springboard, and create a custom feature that functions as a springboard.



All the other details of this implementation can be found in the sample app.

Resources

https://docs.oracle.com/middleware/mobile201/mobile/ADFMF.pdf
https://docs.oracle.com/middleware/mobile201/mobile/OEPMF.pdf (is missing the description of the Sample app)
https://docs.oracle.com/middleware/mobile201/mobile/api-ref/oracle/adfmf/framework/api/AdfmfSlidingWindowUtilities.html
http://docs.oracle.com/middleware/mobile201/mobile/api-ref/oracle/adfmf/framework/api/AdfmfSlidingWindowOptions.html


Tuesday, November 18, 2014

MAF 2.0 : Loading Images in a Background Process - Part I

Images are heavily used in Mobile apps. For instance a list that contains employees usually shows the images of these employees. This works well when you have a WIFI connection, but what if you are using slower connections ? If you look at a mobile twitter app you will see that, depending on connectivity, images are loaded instantaneously or delayed. In this post I explain how you can load the images of a List in a background process, after the other content has been loaded.

Tuesday, October 14, 2014

OOW14 : One week in a nutshell

Two weeks ago I visited Oracle Open World in San Francisco. A glimpse of my activity during Oracle Open World can be found in this post. If you want to hear more, please visit the AMIS Oracle Open World Review Session at October 16th.


Saturday
After a one hour delay at Amsterdam Schiphol Airport the airplane with, amongst others, several AMIS employees took of for San Francisco. There we met with our colleagues who flew in earlier this week.

Book : Oracle Mobile Application Framework Developer Guide

It has been a while since my previous post, and I have a very good reason for that. I have been busy, very busy. But now, after a long time of writing and rewriting I can finally announce that my book, Oracle Mobile Application Framework Developer Guide, is available for you to buy. Many thanks to my reviewers Chris Muir, Frank Nimphius and Joe Huang who guided and helped me all the way.















If you are starting with Oracle MAF development, this book will give you a jump start. You will learn the concepts of MAF and MAF development and learn tips and tricks for MAF development.
In Oracle Mobile Application Framework Developer Guide, I explain how to use this powerful framework to create multiplatform mobile apps based on a single code base. Detailed examples and ready-to-use code are provided throughout the book. A complete, step-by-step sample application highlights the robust functionality of Oracle Mobile Application Framework, including data visualization, UX patterns, geographical maps, push notifications, and more.
A glimpse of the content:
  • Configure your IDE for Android and Apple iOS application development
  • Build AMX pages and task flows for mobile applications
  • Work with the binding layer and data controls
  • Create application features and configure access to them in the springboard and navigation bar
  • Call web services using a data control and create an on-device database
  • Implement device interaction.
  • Debug, test, and secure Oracle Mobile Application Framework applications
And finally you will learn how to build an interactive sample app.
Table of contents
Part One: Understanding the Oracle Mobile Application Framework
1: Introduction to Mobile Application Development
2: Setting Up JDeveloper and Your Development Platform
3: Oracle JDeveloper for Mobile Application Framework Development
4: Building AMX Pages
5: Bindings and Data Controls
6: Application Features
7: Using Web Services and Local Database
8: Device Interaction
9: Debugging and Testing Mobile Application Framework Applications
10: Security and Deployment
Part Two: Developing the Sample Application
11: Explaining the TAMCAPP Sample Application
12: Developing the Springboard
13: Building the Conference Session Feature
14: Building the Attendees Feature
15: Developing the Maps and Social Network
16: Configuring Security and Preferences
17: Implementing Push Notifications
18: Enhancing TAMCAPP
If you want to read the book, You can buy it at the #oow14 bookstore or on-line :
http://www.mhprofessional.com/product.php?isbn=0071830855
Here you can also read the The of Contents and the first chapter to get a glimpse of the book.
The book is also available at Amazon.com
http://www.amazon.com/gp/aw/d/0071830855/ref=mp_s_a_1_1?qid=1411508554&sr=1-1

Tuesday, June 18, 2013

ODTUG KScope 2013 : I'm Speaking

Next week the ODTUG KScope conference kicks of in New Orleans.


During this conference I will present 3 sessions:
Keep your eyes on this weblog for more information on ADF and Mobile stories of ODTUG KScope13.
 

Friday, May 31, 2013

ADF Mobile : Oracle eCourse available

Today Oracle published the first in a series of online eLearning training materials. The ADF Academy presents free and online : Developing Applications with ADF Mobile. The main goal is to deliver technical training material to everybody that needs it. You get an online training, where you can view audio and video to learn ADF. Whenever you want to you can stop the recordings to try out everything that you learned so far, or perhaps to get a drink.



Developing Applications with ADF Mobile is available here.
Take to opportunity and learn to work with a great framework to develop mobile applications for iOS and Adroid.

Also keep an eye on lucbors.blogspot.com for more ADF Mobile related content.

Thursday, February 28, 2013

ADF Mobile : Implementing the "Pull to Refresh" Pattern

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.
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.

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

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

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.


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".


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.



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

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.