Content Handler

As discussed in previous sections, the content handler is the data provider to the mobile app.  Framework components on the mobile device issue method calls to the content handler configured for the app.  The handler responds by retrieving data or performing some task process.

Mobiliti tries to make developing a content handler program as easy as possible.  The best way to develop a handler is to subclass the framework’s built-in content adapters then override their interface methods and lifecycle exit points.  Only override those methods where you want special logic or need to provide information to the mobile client.

For a handler either on the server or resident on the mobile device, subclass ContentHandler.  You can also have a hybrid approach of content handlers on both the mobile device and a server.  If you have specified an app server in the content.xml, the framework will first make API calls to the resident handler on the device and then forward any unimplemented API calls to a server counterpart with the same class name.  If there is no server version, the handler resident on the device acts as the “server” to retrieve all content.

There is no need to override the onGetList or onGetView methods of the ContentHandler for a provider type of “database”.  The framework’s built-in server components will use the Java classes you model using Java Persistence API (JPA) to retrieve and persist content in a relational database.

Handler Methods

public void authenticate(String userid, String password) throws Exception;

public void settings(HashMap settings) throws Exception;

public String getSchema(String entity) throws Exception;

public String getChoices(String entity, String attribute, HashMap entityIDs) throws Exception;

public String lookup(String entity, String attribute, HashMap lookups) throws Exception;

public String hasNodes(String entity) throws Exception;

public String hasTasks(String entity) throws Exception;

public boolean createAllowed(String entity, String userid);

public boolean readAllowed(String entity, String userid);

public boolean updateAllowed(String entity, String userid);

public boolean deleteAllowed(String entity, String userid);

public boolean executeAllowed(String entity, String userid);

public void onInit(Message message) throws Exception;

public ArrayList onGetList(Message message) throws Exception;

public HashMap onGetView(Message message) throws Exception;

public HashMap onGetNodes(Message message) throws Exception;

public HashMap onEntityCreate(Message message) throws Exception;

public boolean onEntityUpdate(Message message) throws Exception;

public boolean onEntityDelete(Message message) throws Exception;

public boolean onRunTask(Message message) throws Exception;

public void onReturn(Message message) throws Exception;

 

Method Descriptions

Method Description
authenticate Called when the mobile app was configured to display a logon screen and the user submits a logon request.  The program should throw an exception for invalid authentication requests with a message explaining the failure.
settings Called when the mobile app was configured with a settings screen.  Any updates to the mobile settings will call this method.  You can store values in the session cache for subsequent client interactions.
getSchema Called to get the XML schema for the supplied entity.
getChoices Called to return a list of selectable values to be displayed in a drop-down choice box for the specified attribute of the supplied entity.  The values must be tab-delimited (or separated by a ‘|’ character) to indicate the separation of one choice value to the next.
lookup Called to perform a mini search on the supplied lookup criteria in a HashMap.  The returned string must be a JSON array containing one or more records with elements which the user will select to populate the lookup attribute.
hasNodes Called to return the string “true” or “false” if the entity has child nodes for the supplied entity when configuration did not specify this information.Default is “false”.
hasTasks Called to return the string “true” or “false” if the entity has tasks for the supplied entity when configuration did not specify this information.Default is “false”.
Security Control Methods
createAllowed Override this method to return a boolean as to whether records for the supplied entity can be created by the userid.Default is false.
readAllowed Override this method to return a boolean as to whether records for the supplied entity can be read by the userid.Default is true.
updateAllowed Override this method to return a boolean as to whether records for the supplied entity can be updated by the userid.Default is false.
deleteAllowed Override this method to return a boolean as to whether records for the supplied entity can be deleted by the userid.Default is false.
executeAllowed Override this method to return a boolean as to whether tasks for the supplied entity can be run by the userid.Default is true.
Lifecycle Exit Points
onInit Called each time the content handler starts before any of the other exit points are called.  Use this lifecycle exit point to perform some processing on initiation before other methods are used.
onGetList Override this method to return an ArrayList of HashMaps containing entity data.  Each ArrayList item is a entity record.  The HashMap is a key/value map containing entity data.  Each key in the map corresponds (and must match) a data attribute defined in the entity schema listAttributes.
onGetView Override this method to return a HashMap containing entity data.  The HashMap is a key/value map containing entity data.  Each key in the map corresponds (and must match) a data attribute defined in the entity schema viewAttributes.
onGetNodes Override this method to return a HashMap containing entity nodes that have records.  The HashMap is a key/value map containing node record counts.  Each key in the map corresponds (and must match) a node name defined in the entity schema nodes.  The value in the map must contain an Integer object for the number of records found in the child node.
onEntityCreate Override this method to create a record for the entity.  The message argument contains the data supplied by the mobile client to insert into an instance of the entity.
onEntityUpdate Override this method to update a record for the entity.  The message argument contains the data and entity IDs supplied by the mobile client to update an instance of the entity.
onEntityDelete Override this method to delete a record for the entity.  The message argument contains the entity IDs supplied by the mobile client to delete a specific instance of the entity.
onRunTask Override this method to run a particular task for the entity.  The message argument contains the data and entity IDs supplied by the mobile client plus any task information entered by the user to run the task.
onReturn Called when the content handler finishes processing before returning.  Use this lifecycle exit point to perform some processing before the content handler finishes.

Messages

Mobiliti uses a loosely-coupled messaging architecture when communicating between the mobile device and the content handler.  The Message object contains entity-specific data and other communication commands used by Mobiliti during networking transactions.  This message structure is automatically converted between Java objects and JSON for network transfer over HTTP using REST techniques when the content handler is on an application server.

For the most part, you do not need to concern yourself with this message object.  There are times though when using one of the lifecycle exit points you have access to its contents to update entity content data or examine internal command parameters.  Be cautious about updating the message object as you could potentially disrupt framework processing.

The following are message objects you can access safely:

public String entityName;

public HashMap entityData;

public HashMap entityDataTypes;

public ArrayList entityIDs;

public ArrayList entityAttributes;

public LinkedHashMap entityNodes;

public LinkedHashMap selectionAttributes;

public ArrayList entityRows;

public int returnCode;

public String returnMsg;

public Locale locale;

public TimeZone timezone;

public String clientID;

public String clientModel;

public String app;

public String userid;

public String location;

public String footer;

public boolean reset;

 

When finishing processing in your content handler, you can set specific return codes and messages in the message object to inform the user or the framework of the condition of the return request.  The return codes are static integer constants in the Message class.

public final static int Ok = 1;

public final static int Information = 2;

public final static int Error = 3;

public final static int Warning = 4;

public final static int RecordUpdated = 5;

public final static int RecordDeleted = 6;

public final static int RecordInserted = 7;

public final static int RecordNotFound = 8;

public final static int RecordFound = 9;

public final static int InvalidAction = 20;

public final static int NoAction = 30;

 

Special Fields

There are several user and display specific fields in the message object you can use during request processing:

User

  • clientID — A unique identifier for the mobile device.
  • clientModel — The hardware model for the mobile device.
  • userid — The userid passed up from the mobile device if a logon was displayed.  If no logon was required, then this defaults to the clientID.
  • locale — The country locale found in the mobile device settings.
  • timezone — The timezone found in the mobile device settings.
  • location — The geo-location of the mobile device in decimal latitude/longitude format.
  • app — The name of the app as specified in the Android manifest.

Display

  • footer — When this field has a value in the message, the mobile device will display the contents of at the bottom of the screen.
  • reset — A boolean flag to tell the mobile device to reset logon information and settings, then return back to the home screen.  This can be used during an error or invalid logon to return the user back to the home screen.

Entity Icons and Groups

A feature of the framework is the ability to show icons to the left of entity records in List screens and/or group related records into collapsable components in the list.  When Mobiliti finds specific named keys in the returned entity HashMaps containing entity data, it will enable the icon and grouping features.  The keys in the map must be specific Message static constants with values being respective icon URL or group labels.

For example, to set icon and groups from a content handler put the information in the returned data using the following keys:

entityDataMap.put(Message.entityImage, “http://myServer/myIcon.png”)  // set entity icon

entityDataMap.put(Message.entityGroup, “My Group Label”)  // set entity group

Or in a task program on the mobile client just update entity objects directly:

entity.setImage(“http://myServer/myIcon.png”)  // set entity icon

entity.setGroup(“My Group Label”)  // set entity group

 

Paging

When retrieving records there may be more in the data source than you want to retrieve all at once. You specify the limit on the number that will be returned by using the rowLimit parameter in the listAttributes section of your content.xml file. By default, only 25 records will be retrieved unless you override that number with a value in the rowLimit parameter.

To properly implement paging for additional records, you must populate the Message.rowsFound field with the total number of records in the data source. If the total number of rows found is higher than the number displaying in a list, Mobiliti will display a more item in the list to get additional records. When retrieving additional records, you can reference the Message.rowStart field to know whether an additional page of records is being requested. If the list is being populated the first time, the rowStart will be 1, otherwise it will be the starting row for the next page of records. Use this field in your content handler to send back the proper group of records.

Footers

Mobiliti provides a way to customize the UI for some screens. At the bottom of a screen is a reserved area for HTML content and widgets. Footer content is supplied by the Message.footer field. When a content handler services a Message request, you can put either a URL or HTML in the message’s footer element to be showed at the bottom on the screen. The footer will show a WebView of the content specified.

You can also add a Javascript interface between Mobiliti and your footer by implementing a call to contentHandlerClassName.anyMethodName(arg) within a script in the HTML. For example:

<script type=”text/javascript”>

function myScript(arg) {

contentHandlerClassName.myJavascriptInterface(arg);

}

</script>

 

Then in your content handler provide a myJavascriptInterface() method to perform the logic for the javascript routine:

public boolean myJavascriptInterface(String arg) {

return doSomethingMethod(arg); // return true if successful

}

 

If the footer field starts with “widget://”, it indicates you want to supply your own UI component instead of HTML. An Android Java class is specified after the “widget://” prefix and will be created when Mobiliti builds the screen layout. This class must implement the “create” method of the WidgetInterface interface so that the visual component can be returned and placed at the bottom of the screen.

The method must also have arguments of ContentHandler (the content handler), Entity (parent Entity of the screen), Entity (selected entity for the screen), and EntityListModel (records in the list). The arguments allow you to reference and access objects you may need during the display and operation of your widget. Here is an example:

public View create(ContentHandler contentHandler, Entity parentEntity, final Entity selectedEntity, EntityListModel items) throws Exception {

ImageButton button = new ImageButton(Controller.getInstance(), null, android.R.attr.buttonStyleSmall);

button.setImageBitmap(Controller.getBitmap(“widget.png”, true));

button.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

// do something

}

});

return button;

}

 

Task Programming

The ability to perform business processes at the server for a mobile client is a powerful feature of Mobiliti.  When the entity schema contains task configurations the framework enables task popups so the user can issue a task request.  The schema configuration will provide the name of a task which must be the name of a task to execute.

You can use the content handler onRunTask() exit point method to perform the task.  The name of the task will be found in the serviceName variable of the supplied Message parameter to the method.  Mobiliti will issue a request to your content handler and call the onRunTask() exit point method.  Your content handler can then perform the task action inline or call other program modules as needed to accomplish the task.

If you do not override the onRunTask() method and the name is a fully qualified class found in either the mobile or server class package, then the framework will execute it locally on the device or on the server respectively instead of using the onRunTask() exit.  Task modules must implement the TaskInterface interface in order for the framework to instantiate task programs in this manner.

Session Cache on server

Although the Mobiliti framework uses a stateless model between network transactions, you can store self-managed data in a session cache that Mobiliti will maintain for you when using server based content handlers.  The data is contained in a HashMap and is stored in the servlet’s session cache allowing you to retain some data state between interactions with the mobile device.  To use it, issue a getSessionCache() request from your content handler program to retrieve or update the cache contents.

Database Handlers

A powerful feature of Mobiliti is the use of Java Persistence API (JPA) for content that is stored in a relational database.  Using JPA is a very declarative way to describe database entities and to access tables.  It uses Java annotations for very high level control with minimal code.

Mobiliti uses the popular open source Hibernate framework for its JPA implementation.  If your content handler is a database type, then it doesn’t require much or any code.  All data access calls will be transparently sent to Hibernate and will use JPA.  You must create individual JPA program modules though that match each of your database tables and correspond to the content schemas configured for your application.  Mobiliti will look for these modules in the “/entities” directory where your content handler program was found.

You must define the JPA fields and getter/setter methods to match the entity schema attribute names exactly.

Sample entity schema

<?xml version=’1.0′ encoding=’ISO-8859-1′?>

<entity name=’Customers’>

<IDs><ID name=’CustomerNumber’ /></IDs>

. . .

<attributes>

<attribute name=’CustomerName’ type=’Text’ length=’50’ required=’true’ label=”Name” />

<attribute name=’ContactLastName’ type=’Text’ length=’50’ required=’true’ />

<attribute name=’ContactFirstName’ type=’Text’ length=’50’ required=’true’ />

<attribute name=’Phone’ type=’Phone’ length=’50’ required=’true’ />

<attribute name=’AddressLine1′ type=’Text’ length=’50’ required=’true’ />

<attribute name=’AddressLine2′ type=’Text’ length=’50’ />

<attribute name=’City’ type=’Text’ length=’50’ required=’true’ />

<attribute name=’State’ type=’Text’ length=’50’ />

<attribute name=’PostalCode’ type=’Text’ length=’15’ />

<attribute name=’Country’ type=’Text’ length=’50’ required=’true’ />

<attribute name=’SalesRepEmployeeNumber’ type=’Textnumeric’ length=’11’ />

<attribute name=’CreditLimit’ type=’Currency’ length=’22’ />

<attribute name=’CustomerNumber’ type=’Textnumeric’ length=’11’ />

<attribute name=’Address’ type=’Address’ length=’50’ />

</attributes>

. . .

</entity>

 

Sample JPA program

@IdClass(CustomersPK.class)

@Entity

public class Customers {

private static final long serialVersionUID = 1L;

// primary keys

@Id

@GeneratedValue(strategy = GenerationType.AUTO)

@Column

private Integer CustomerNumber;

public Integer getCustomerNumber() {return CustomerNumber;}

public void setCustomerNumber(Integer value) {CustomerNumber = value;}

// columns

@Column(nullable=false)

private String CustomerName;

public String getCustomerName() {return CustomerName;}

public void setCustomerName(String value) {CustomerName = value;}

@Column(nullable=false)

private String ContactLastName;

public String getContactLastName() {return ContactLastName;}

public void setContactLastName(String value) {ContactLastName = value;}

@Column(nullable=false)

private String ContactFirstName;

public String getContactFirstName() {return ContactFirstName;}

public void setContactFirstName(String value) {ContactFirstName = value;}

@Column(nullable=false)

private String Phone;

public String getPhone() {return Phone;}

public void setPhone(String value) {Phone = value;}

@Column(nullable=false)

private String AddressLine1;

public String getAddressLine1() {return AddressLine1;}

public void setAddressLine1(String value) {AddressLine1 = value;}

@Column

private String AddressLine2;

public String getAddressLine2() {return AddressLine2;}

public void setAddressLine2(String value) {AddressLine2 = value;}

@Column(nullable=false)

private String City;

public String getCity() {return City;}

public void setCity(String value) {City = value;}

@Column

private String State;

public String getState() {return State;}

public void setState(String value) {State = value;}

@Column

private String PostalCode;

public String getPostalCode() {return PostalCode;}

public void setPostalCode(String value) {PostalCode = value;}

@Column(nullable=false)

private String Country;

public String getCountry() {return Country;}

public void setCountry(String value) {Country = value;}

@Column

private Integer SalesRepEmployeeNumber;

public Integer getSalesRepEmployeeNumber() {return SalesRepEmployeeNumber;}

public void setSalesRepEmployeeNumber(Integer value) {SalesRepEmployeeNumber = value;}

@Column

private Integer CreditLimit;

public Integer getCreditLimit() {return CreditLimit;}

public void setCreditLimit(Integer value) {CreditLimit = value;}

@Transient

public String getAddress() {

StringBuilder addr = new StringBuilder();

if (AddressLine1 != null && AddressLine1.trim().length() > 0) {

addr.append(AddressLine1.trim()).append(“, “);

}

if (AddressLine2 != null && AddressLine2.trim().length() > 0) {

addr.append(AddressLine2.trim()).append(“, “);

}

if (City != null && City.trim().length() > 0) {

addr.append(City.trim());

}

if (State != null && State.trim().length() > 0) {

addr.append(“, “).append(State.trim());

}

if (PostalCode != null && PostalCode.trim().length() > 0) {

addr.append(“, “).append(PostalCode.trim());

}

if (Country != null && Country.trim().length() > 0) {

addr.append(“, “).append(Country.trim());

}

return addr.toString();

}

}


%d bloggers like this: