|
Download the source
Because devices that support the Mobile Information Device Profile (MIDP) can have very small screens, information that fits comfortably in a single window in a desktop application usually cannot be viewed in the same way in a MIDP application. What's needed is a way for a MIDlet to display the same information across multiple screens, with a simple and predictable user interface. One way is to use the Dynamic List/Detail View design pattern, sometimes referred to as the Master/Detail View design pattern.
This design pattern works by combining two views of the same data. The list or master view summarizes the data in list form. A set of customers could be listed by name, for example. The detail view presents in more depth one of the data items summarized in the list view. The detail view for a customer would list all the important information about that customer, such as the customer's name, address, and phone number. Although the list view is typically a single screen, the detail view may spread itself across several screens. Consistent and easy navigation between the screens in both views is crucial.
Let's implement a list/detail view in a simple customer-management application. Each customer is represented by an instance of the Customer class:
// A simple class to hold basic information about
// customers. This class can be persisted.
import java.io.*;
public class Customer implements Persistent {
private int customerID;
private String customerName;
private String contactName;
private String address;
private String city;
private String telephone;
private String fax;
public Customer(){
}
public Customer( int id, String cust,
String contact, String addr,
String city, String tel,
String fax ){
this.customerID = id;
this.customerName = ( cust != null ? cust : "" );
this.contactName = ( contact != null ? contact : "" );
this.address = ( addr != null ? addr : "" );
this.city = ( city != null ? city : "" );
this.telephone = ( tel != null ? tel : "" );
this.fax = ( fax != null ? fax : "" );
}
public int getID() { return customerID; }
public String getCustomerName(){ return customerName; }
public String getContactName(){ return contactName; }
public String getAddress(){ return address; }
public String getCity(){ return city; }
public String getTelephone(){ return telephone; }
public String getFax(){ return fax; }
public String toString() {
StringBuffer b = new StringBuffer();
b.append( '{' );
b.append( customerID );
b.append( ',' );
b.append( customerName );
b.append( ',' );
b.append( contactName );
b.append( ',' );
b.append( address );
b.append( ',' );
b.append( city );
b.append( ',' );
b.append( telephone );
b.append( ',' );
b.append( fax );
b.append( '}' );
return b.toString();
}
public byte[] persist() throws IOException {
ByteArrayOutputStream bout =
new ByteArrayOutputStream();
DataOutputStream dout =
new DataOutputStream( bout );
dout.writeInt( getID() );
dout.writeUTF( getCustomerName() );
dout.writeUTF( getContactName() );
dout.writeUTF( getAddress() );
dout.writeUTF( getCity() );
dout.writeUTF( getTelephone() );
dout.writeUTF( getFax() );
dout.flush();
byte[] ret = bout.toByteArray();
dout.close();
return ret;
}
public void resurrect( byte[] data ) throws IOException {
ByteArrayInputStream bin =
new ByteArrayInputStream( data );
DataInputStream din =
new DataInputStream( bin );
customerID = din.readInt();
customerName = din.readUTF();
contactName = din.readUTF();
address = din.readUTF();
city = din.readUTF();
telephone = din.readUTF();
fax = din.readUTF();
din.close();
}
}
|
Note that this class implements the Persistent interface:
import java.io.*;
// A simple interface for building persistent objects on
// platforms where serialization is not available.
public interface Persistent {
byte[] persist() throws IOException;
void resurrect( byte[] data ) throws IOException;
}
|
This interface supports data persistence on platforms where serialization is unavailable, like the MIDP. A complete application would use this interface to store and retrieve customer information from a record store, but for this simple application we'll initialize a sample set of customers in code:
public static Customer[] customers = {
new Customer( 1, "Acme Inc.", "W. Coy", "123 Main Street",
"Runnerville", "408-555-1234", null ),
new Customer( 2, "Prassy Fittings", "Joe Smith",
"321 Oak Street",
"Prassy", "408-555-4321", null ),
new Customer( 3, "Ajax Car Sales", "A. Jax",
"456 Main Street", "Runnerville",
"408-555-4567", "408-555-4568" }
};
|
The list view is implemented as a subclass of MIDP's List class named CustomerList. The list fills itself from the customer array, generating short forms for customer names if necessary. This list is created with the implicit selection mode for quick selection of individual items.
import javax.microedition.lcdui.*;
public class CustomerList extends List {
private static final int NAME_LEN = 12;
private Customer[] customers;
// Initialize the customer list.
public CustomerList( Customer[] customers ){
super( "Customers", List.IMPLICIT );
this.customers = customers;
for( int i = 0; i < customers.length; ++i ){
Customer cust = customers[i];
String name = shorten( cust.getCustomerName(),
NAME_LEN );
append( name, null );
}
}
// Return the currently selected customer, if any.
public Customer getSelectedCustomer(){
int sel = getSelectedIndex();
if( sel >= 0 && sel < customers.length ){
return customers[sel];
}
return null;
}
// Utility to shorten a name to the given length.
private static String shorten( String str, int len ){
if( str.length() <= len-3 ) return str;
return str.substring( 0, len-3 ) + "...";
}
}
|
Now we need a class for the details view. We make CustomerDetail a subclass of the MIDP Form class because forms let us display data of different kinds easily. The customer data is actually split across two forms: a primary details screen to show the most important information and a secondary details screen to show the rest. The forms are chained together: CustomerDetail exposes getNext and getPrevious methods to move between them.
import javax.microedition.lcdui.*;
// Displays the details about a customer. The public
// form of the constructor creates the primary details
// screen, for displaying the most important information.
// A second screen, the secondary details screen, is also
// created to display the remaining information.
public class CustomerDetail extends Form {
private Customer customer;
private CustomerDetail previous;
private CustomerDetail next;
// Constructs the primary details screen.
// Also constructs the secondary details screen.
public CustomerDetail( Customer cust,
CommandListener listener,
Command backCommand,
Command moreCommand ){
super( "Details" );
customer = cust;
append( new StringItem( "Name:",
cust.getCustomerName() ) );
append( new StringItem( "Contact:",
cust.getContactName() ) );
append( new StringItem( "Phone:",
cust.getTelephone() ) );
append( new StringItem( "Fax:",
cust.getFax() ) );
addCommand( backCommand );
addCommand( moreCommand );
setCommandListener( listener );
next = new CustomerDetail( this, listener,
backCommand );
}
// Private constructor to create the secondary
// details screen.
private CustomerDetail( CustomerDetail previous,
CommandListener listener,
Command backCommand ){
super( "More Details" );
this.previous = previous;
customer = previous.getCustomer();
append( new StringItem( "Address:",
customer.getAddress() ) );
append( new StringItem( "City:",
customer.getCity() ) );
addCommand( backCommand );
setCommandListener( listener );
}
// Returns the underlying customer.
public Customer getCustomer(){
return customer;
}
// For the primary details screen, this returns
// null. For the secondary details screen, this
// returns the primary details screen.
public CustomerDetail getPrevious(){
return previous;
}
// For the primary details screen, this returns
// the secondary details screen. For the latter,
// this returns null.
public CustomerDetail getNext(){
return next;
}
}
|
When an application creates a CustomerDetail, it passes two Command objects to the constructor. These commands are used to navigate between the primary and secondary details forms, and also back to the list view. Note that for moving backwards from one screen to another, the recommended type of command object to create is Command.BACK, not Command.SCREEN. This alternative lets the implementation choose the most appropriate button on the device for backward screen navigation, which may not be one of the two soft (programmable) buttons that a MIDP application normally uses when the Command.SCREEN type is specified.
The final piece of our application is the main MIDlet class, which initializes the list view and then handles the events generated by both views to navigate between the various screens:
import javax.microedition.lcdui.*;
// MIDlet that uses the list-detail view design
// pattern to display customer information.
public class ListDetailMIDlet extends MIDlet
implements CommandListener {
private Display display;
private CustomerList list;
// The command to move to the previous screen. Notice
// the use of the Command.BACK type.
public static final Command backCommand =
new Command( "Back",
Command.BACK, 1 );
// The exit command.
public static final Command exitCommand =
new Command( "Exit",
Command.EXIT, 1 );
// The command to move to the next screen.
public static final Command moreCommand =
new Command( "More",
Command.SCREEN, 1 );
// Our list of customers. Statically defined only as
// an example, in practice you would read this information
// from a record store.
public static final Customer[] customers = {
new Customer( 1, "Acme Inc.", "W. Coy",
"123 Main Street",
"Runnerville", "408-555-1234", null ),
new Customer( 2, "Prassy Fittings", "Joe Smith",
"321 Oak Street",
"Prassy", "408-555-4321", null ),
new Customer( 3, "Ajax Car Sales", "A. Jax",
"456 Main Street",
"Runnerville", "408-555-4567",
"408-555-4568" ),
};
// The constructor.
public ListDetailMIDlet(){
}
// The event handling. When an item is selected in
// the list view, switch to the primary details form
// for that item. If the details view is active,
// handle switching from one form to the other and
// also back out to the list view.
public void commandAction( Command c,
Displayable d ){
if( c == exitCommand ){
exitMIDlet();
} else if( d == list ){
if( c == List.SELECT_COMMAND ){
Customer cust = list.getSelectedCustomer();
if( cust != null ){
CustomerDetail detail =
new CustomerDetail( cust, this,
backCommand,
moreCommand );
getDisplay().setCurrent( detail );
}
}
} else if( c == backCommand ){
Displayable prev = null;
if( d instanceof CustomerDetail ){
prev = ((CustomerDetail) d).getPrevious();
}
getDisplay().setCurrent(
prev != null ? prev : list );
} else if( c == moreCommand ){
if( d instanceof CustomerDetail ){
Displayable next =
((CustomerDetail) d).getNext();
if( next != null ){
getDisplay().setCurrent( next );
}
}
}
}
protected void destroyApp( boolean unconditional )
throws MIDletStateChangeException {
exitMIDlet();
}
public void exitMIDlet(){
notifyDestroyed();
}
public Display getDisplay(){ return display; }
// Create the list view and make it the initial screen.
protected void initMIDlet(){
list = new CustomerList( customers );
list.addCommand( exitCommand );
list.setCommandListener( this );
getDisplay().setCurrent( list );
}
protected void pauseApp(){
}
protected void startApp()
throws MIDletStateChangeException {
if( display == null ){
display = Display.getDisplay( this );
initMIDlet();
}
}
}
|
Although a fairly simple example, this MIDlet demonstrates the List/Detail View design pattern quite well and can serve as the basis for your own applications.
Eric Giguere is a software developer for iAnywhere Solutions, a subsidiary of Sybase, where he works on Java technologies for handheld and wireless computing. He holds BMath and MMath degrees in Computer Science from the University of Waterloo and has written extensively on computing topics.
Back To Top
|