HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

In this article I discuss which displayable components are available in J2ME and explain how commands are associated with them. In particular, I examine how to create commands, add them to displayables, and define command behavior in CommandListeners. I then demonstrate how the resulting code is typically procedural and quickly becomes cluttered when too many commands are created.

Using object-oriented principles and the Command pattern, I present two new classes that provide a simple solution to this problem. These classes provide structure and reusability to the process of creating commands for displayables, resulting in code that's cleaner and easier to maintain.

Overview of Displayable Components
In the Mobile Information Device Profile (MIDP) there's no AWT (and certainly no Swing) for creating graphical user interfaces. Instead, GUIs are created using classes from the javax.microedition.lcdui package. As shown in Figure 1, the abstract Displayable class is the superclass from which you create objects that can be displayed on the screen of a MIDP device.

Figure 1

The two immediate subclasses of Displayable are Canvas and Screen. The Canvas class creates GUI components that use low-level graphical primitives to draw themselves. To use the Canvas class you must subclass it and implement its paint() method, which receives a Graphics parameter. The Graphics class, similar to its J2SE counterpart, provides methods to draw lines, rectangles, arcs, strings, and images.

The Screen class, on the other hand, creates high-level GUI components. Like the Canvas class it's abstract, but it has four direct subclasses that are concrete and ready to use: Alert, List, TextBox, and Form. All but the Form, which is quite blank on its own, are shown in Figure 2. The Alert class displays a message similar to a dialog box. The List presents choices using one of the following schemes: EXCLUSIVE, MULTIPLE, or IMPLICIT. The TextBox allows the user to enter text and can optionally restrict the input format; for example, permit numbers only.

Figure 2

The Form class, unlike its three siblings, is not a specific GUI component; rather it serves as a container for the other components, making it possible to build more complex user interfaces. The components that the Form contains are subclasses of the Item class. These include the ChoiceGroup (equivalent to the List), DateField, Gauge, ImageItem, StringItem, and TextField. There are no layout managers in MIDP; instead, the device automatically handles the layout, traversal, and scrolling of the items that are added to a Form. In most cases, the components are laid out vertically.

Besides having graphical representation, displayable components can also have additional behavior associated with them. To do this, add commands and set a CommandListener that will be notified when the commands are invoked.

Commands and CommandListeners
Figure 3 shows the relationship between the Displayable class, commands, and CommandListeners. Notice that multiple commands may be added to a displayable, but only one CommandListener may be set. I'll examine some of the consequences of this later. For now, let's look at how to create commands and Command-Listeners.

Figure 3

To create a command, specify the label string, type, and priority of the command. The label string represents the command in the user interface. The command type gives a hint to the device on the semantics of the command. It's up to the device implementation to decide which scheme to use for commands of a certain type. The defined command types are BACK, CANCEL, EXIT, HELP, ITEM, OK, SCREEN, and STOP. And the priority is an integer that indicates the importance of the command with respect to other commands. Commands with a smaller number are given a higher priority. When adding more commands than the maximum that the device can display at one time, commands with a higher priority will be shown and those of a lower priority will be available only by other means (a menu, for example).

After creating a command, add it to the displayable by using its addCommand() method. For example:

Command command = new Command("Go",
Command.SCREEN, 0);
displayable.addCommand(command);
The Command class is not where you define what happens when the command is invoked. Instead, you must create a class that implements the CommandListener interface, which contains one method:

public void commandAction(Command c,Displayable d);

This method is called when a command that was added to a displayable is invoked, so this is where you place the behavior for your commands. After creating your CommandListener, associate it with the displayable by using the setCommandListener() method. Notice that the method is not addCommandListener() as would have been the norm in J2SE. As stated earlier, in J2ME there can be only one CommandListener per displayable. This means that the behavior for every command of a displayable must be defined in one implementation of CommandListener.

Procedural Example
I'll use an example to illustrate the use and consequences of the current Command->CommandListener process. Consider a simple MIDlet that displays the three different types of Lists: EXCLUSIVE, MULTIPLE, and IMPLICIT. The MIDlet displays an empty Form with four commands: one for each type of List, and an Exit command.

When the user selects a type, a List of that type with arbitrary choices is displayed. The user interacts with the List and can select OK or Cancel. OK displays a confirmation of the selected List item(s), while Cancel goes back to the Form that offers the List type choices.

Listing 1 shows the code for this example. In line 6, I create the initial Form. Lines 8-11 define the items that will be displayed in each List and a Boolean array that will contain flags indicating the selected items. Lines 13-20 create the four commands for the Form.

Lines 22-25 define the OK and the Cancel commands for the Lists.

In the startApp() method, I add the four commands to the Form and set its CommandListener, defined on lines 44-69. In this code, note that I determine the List type according to the invoked command. I then create a List of that type and add the OK and Cancel commands. Finally, I set the CommandListener, defined in lines 71-100, and display the List.

Note that line 79 tests for the OK command as well as the special List.SELECT_COMMAND; this command is invoked when the user makes a selection on a List of type IMPLICIT, so we must test for it.

Listing 1 demonstrates the process of adding commands to displayables and associated Comm- andListeners to define the behavior for the commands. However, defining the behavior separate from the command, combined with the restriction of having only one listener per displayable, yields procedural code that prevents us from leveraging OO principles. Resulting problems include:

  • The CommandListener's commandAction() method quickly becomes monolithic and littered with if/else statements.
  • If you dynamically remove a command from a displayable, the corresponding conditional statement in the commandAction() method remains and will needlessly be tested when other commands are invoked.
  • The CommandListener is tightly coupled with the commands that it services. To add another command to a displayable, you must not only add the code for the command, but also modify the CommandListener.
  • The command and its behavior are defined in two separate places in the code, hindering readability.
  • You can't create objects that define the behavior for your commands. So you can't take advantage of features that OO provides, like inheritance and polymorphism.
After creating a few MIDlets, I became frustrated by these problems in the command creation process. There had to be a better way.

Command Pattern and CommandAction
Creating commands and their behavior the OO way and solving the problems enumerated in the previous section meant opening my copy of Design Patterns (see Resources section) and reviewing the Command pattern. Its intent is to encapsulate a request as an object by providing a method that will be invoked to execute the command. Refactoring (see Resources) confirms this by identifying a series of if/else statements (or switch statements) as a "bad smell" in the code that should be refactored. The proposed solution is to "Replace Conditional with Polymorphism": move each leg of the conditional to an overriding method in a subclass and make the original method abstract.

To do this, I created the CommandAction class, which contains a command and provides the aforementioned method:

public abstract void execute(Displayable d);

This method makes it possible to associate the behavior of the command with the command itself. For convenience, the CommandAction class (see Listing 2) provides two constructors, one that accepts a command and one that accepts the label, type, and priority and creates a command from those parameters. Now, the command and its behavior are encapsulated in an object. For example:

private CommandAction goCommand
= new CommandAction("Go", Command.SCREEN, 0)
{
public void execute(Displayable d) {
// behavior code for the Go command
}
};

The CommandManager Class
The second class I needed was one that accepts CommandActions, adds the commands they contain to displayables, and invokes their execute() method when the command is invoked. I called this class CommandManager. It provides a method to add a CommandAction to a displayable:

public void add(CommandAction cmdAction,
Displayable displayable);
In your MIDlet, create a single CommandManager that will handle all CommandActions for all displayables. A remove method is also provided:
public void remove(CommandAction cmdAction,
Displayable d);

In the CommandManager class, I use three Hashtables, one to answer each of the following questions:

  • Hashtable 1: Given a displayable, how many commands have been added to it?
  • Hashtable 2: Given a command, what is its corresponding CommandAction?
  • Hashtable 3: Given a CommandAction, to how many displayables has it been added?
When a CommandAction is added to a displayable, the CommandManager adds the command contained by the CommandAction to the displayable and increments the counter for that displayable in Hashtable 1. If this counter, after being incremented, is 1, the CommandManager registers itself as the CommandListener for this displayable. If the counter is greater than 1, the CommandListener is already registered.

Similarily, every time a CommandAction is removed from a displayable, the CommandManager removes the corresponding command from the displayable and decrements the counter. If this counter goes back to 0, the CommandManager removes itself as a CommandListener for that displayable by calling setCommandListener(null). The entry is also removed from Hashtable 1.

Hashtable 2 is necessary because the commandAction() method receives a command parameter and we must determine the corresponding CommandAction in order to call its execute() method.

Hashtable 3 tells us whether upon removing a command, we can remove the Command->CommandAction mapping from Hashtable 2. Since it's possible that the same command is used for more than one displayable, we must make sure that no displayables use the command before removing the entry from Hashtable 2.

The complete code for the CommandManager class is shown in Listing 3 . Note: The Hashtables use 1-element int arrays for counters because they require object values; when using integer objects you have to create new integers each time, which results in a lot of garbage to be collected.

Object-Oriented Example
Let's now implement the previous example using the CommandAction and CommandManager classes (see Listing 4).

Line 7 creates the CommandManager that will be used for all commands of all displayables.

Now, instead of having the if/else statements to determine which type of List the user chose, we can define a class that extends CommandAction and constructs a command and its behavior based on the List type (lines 14-27).

Lines 29-34 define the Exit command.

Remember that the OK command can be directly invoked by the user, but the special List.SELECT_COMMAND can also be invoked when the List type is IMPLICIT. Lines 36-60 define the OK command as a subclass of CommandAction because we have to create two types, one for the OK command and one for the List.SELECT_COMMAND. For convenience, I provided both of the CommandAction constructors.

Lines 62-67 define the Cancel command.

Lines 70-73 show how easy it is to add the List CommandActions to the Form using the CommandManager. You don't have to bother with CommandListeners; the command behavior is defined in the CommandAction. Similarily, lines 20-22 add the OK and Cancel CommandActions to the List.

Benefits
I find that using the CommandAction and CommandManager classes yield benefits at all stages of coding: creation, modification, and maintenance. These benefits are the results of making the process more object-oriented. The key is to encapsulate a command and its behavior in a single class. Having the two close together in code also improves readability.

The CommandManager is a helper class that defines once and for all the mechanism of adding and removing commands to displayables and setting the CommandListener. This reuse reduces clutter in client code.

OO principles can now be put to use when creating commands. Since commands and CommandListeners are loosely coupled, adding or removing commands does not require changes to a CommandListener. Your code becomes more modular, and you can add new commands to your system without having to modify or recompile existing classes.

Conclusion
J2ME is an exciting platform for writing applications that run on wireless devices. After writing several MIDlets, I realized that there was a better way to create commands and CommandListeners. Since these are used in just about every MIDlet, I felt it was worthwhile to carefully consider the issue.

When a situation like this arises, add some utility classes to help you program using the design you find most advantageous. Take the time to do this once and for all; you'll be glad you did when you're using those classes over and over again.

I hope I've encouraged you to strive for object-orientation in general and to continue exploring J2ME in particular.

Resources

  • Gamma, E., Helm, R., Johnson, R., and Vlissides, J. (1995). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
  • Fowler, M. (2000). Refactoring: Improving the Design of Existing Code. Addison-Wesley.
  • Knudsen, J. (2001). Wireless Java: Developing with Java 2, Micro Edition. Apress.
  • Java 2 Platform, Micro Edition: http://java.sun.com/products/j2me
  • Mobile Information Device Profile: http://java.sun.com/products/midp
  • Java 2 Platform Micro Edition, Wireless Toolkit: http://java.sun.com/products/j2mewtoolkit
  • Java 2 Platform, Standard Edition, version 1.4.0: http://java.sun.com/j2se/1.4

    Author Bio
    Fred Daoud is an independent contractor and a Sun Certified Java developer. He uses J2SE, J2EE, design patterns, and OO principles to develop networked GUI applications. Fred holds a computer engineering degree from McGill University in Montreal. [email protected]

    	
    
    
    Listing 1
    
    
    1. import javax.microedition.lcdui.*;
    2. import javax.microedition.midlet.*;
    3.
    4. public class ListDemo1 extends MIDlet {
    5.   private Display _display;
    6.   private Form _form = new Form("Choose a type of List:");
    7. 
    8.   private static final String[] _items
    9.     = {"J2ME", "J2SE", "J2EE"};
    10.   private static final boolean[] _flags
    11.     = new boolean[_items.length];
    12. 
    13.   private Command _exCmd = new Command("EXCLUSIVE",
    14.     Command.SCREEN, 0);
    15.   private Command _multCmd = new Command("MULTIPLE",
    16.     Command.SCREEN, 0);
    17.   private Command _impCmd = new Command("IMPLICIT",
    18.     Command.SCREEN, 0);
    19.   private Command _exitCmd = new Command("Exit",
    20.     Command.EXIT, 0);
    21. 
    22.   private Command _okCmd = new Command("OK",
    23.     Command.SCREEN, 0);
    24.   private Command _cancelCmd = new Command("Cancel",
    25.     Command.SCREEN, 0);
    26. 
    27.   public void startApp() {
    28.     _form.addCommand(_exCmd);
    29.     _form.addCommand(_multCmd);
    30.     _form.addCommand(_impCmd);
    31.     _form.addCommand(_exitCmd);
    32.     _form.setCommandListener(_formListener);
    33. 
    34.     _display = Display.getDisplay(this);
    35.     _display.setCurrent(_form);
    36.   }
    37. 
    38.   public void pauseApp() { }
    39. 
    40.   public void destroyApp(boolean unconditional) {
    41.     notifyDestroyed();
    4.   }
    43. 
    44.   private CommandListener _formListener
    45.   = new CommandListener() {
    46.     public void commandAction(Command cmd,
    47.     Displayable displayable) {
    48.       if (cmd == _exitCmd) {
    49.         notifyDestroyed();
    50.         return;
    51.       }
    52.       int listType = -1;
    53.       if (cmd == _exCmd) {
    54.         listType = Choice.EXCLUSIVE;
    55.       }
    56.       else if (cmd == _multCmd) {
    57.         listType = Choice.MULTIPLE;
    58.       }
    59.       else if (cmd == _impCmd) {
    60.         listType = Choice.IMPLICIT;
    61.       }
    62.       List list = new List("Select an item:",
    63.         listType, _items, null);
    64.       list.addCommand(_okCmd);
    65.       list.addCommand(_cancelCmd);
    66.       list.setCommandListener(_listListener);
    67.       _display.setCurrent(list);
    68.     }
    69.   };
    70. 
    71.   private CommandListener _listListener
    72.   = new CommandListener() {
    73.     public void commandAction(Command cmd,
    74.     Displayable displayable) {
    75.       List list = (List) displayable;
    76.       if (cmd == _okCmd || cmd == List.SELECT_COMMAND) {
    77.         list.getSelectedFlags(_flags);
    78.         StringBuffer msg
    79.           = new StringBuffer("You selected:");
    80.         for (int i = 0; i < _flags.length; i++) {
    81.           if (_flags[i]) {
    82.             msg.append("\nItem ");
    83.             msg.append(String.valueOf(i + 1));
    84.             msg.append(" - ");
    85.             msg.append(list.getString(i));
    86.           }
    87.         }
    88.         Alert alert = new Alert("Info",
    89.           msg.toString(), null, AlertType.INFO);
    90.         alert.setTimeout(Alert.FOREVER);
    91.         _display.setCurrent(alert);
    92.       }
    93.       else if (cmd == _cancelCmd) {
    94.         list.removeCommand(_okCmd);
    95.         list.removeCommand(_cancelCmd);
    96.         list.setCommandListener(null);
    97.         _display.setCurrent(_form);
    98.       }
    99.     }
    100.   };
    101. }
    
    
    Listing 2
    
    
    1. import javax.microedition.lcdui.*;
    2. 
    3. public abstract class CommandAction {
    4.   private Command _command;
    5. 
    6.   public CommandAction(String label, int type,
    7.   int priority) {
    8.     _command = new Command(label, type, priority);
    9.   }
    10.   public CommandAction(Command command) {
    11.     _command = command;
    12.   }
    13. 
    14.   public Command getCommand() {
    15.     return _command;
    16.   }
    17.   public void setCommand(Command command) {
    18.     _command = command;
    19.   }
    20. 
    21.   public abstract void execute(Displayable d);
    22. }
    
    
    
    Listing 3
    
    
    1. import javax.microedition.lcdui.*;
    2. import java.util.*;
    3. 
    4. public class CommandManager implements CommandListener {
    5.   private Hashtable _dToC = new Hashtable();
    6.   private Hashtable _cToD = new Hashtable();
    7.   private Hashtable _commands = new Hashtable();
    8. 
    9.   public void add(CommandAction cmdAction,
    10.   Displayable displayable) {
    11.     int[] cmdCount = increment(_dToC, displayable);
    12.     if (cmdCount[0] == 1) {
    13.       displayable.setCommandListener(this);
    14.     }
    15.     increment(_cToD, cmdAction);
    16. 
    17.     Command command = cmdAction.getCommand();
    18.     displayable.addCommand(command);
    19.     _commands.put(command, cmdAction);
    20.   }
    21. 
    22.   public void remove(CommandAction cmdAction,
    23.   Displayable displayable) {
    24.     Command command = cmdAction.getCommand();
    25.     displayable.removeCommand(command);
    26. 
    27.     int[] cmdCount = (int[]) _dToC.get(displayable);
    28.     cmdCount[0]--;
    29.     if (cmdCount[0] == 0) {
    30.       _dToC.remove(displayable);
    31.       displayable.setCommandListener(null);
    32.     }
    33.     
    34.     int[] dispCount = (int[]) _cToD.get(cmdAction);
    35.     dispCount[0]--;
    36.     if (dispCount[0] == 0) {
    37.       _commands.remove(command);
    38.     }
    39.   }
    40. 
    41.   public void commandAction(Command command,
    42.   Displayable displayable) {
    43.     CommandAction cmdAction 
    44.       = (CommandAction) _commands.get(command);
    45.     if (cmdAction != null) {
    46.       cmdAction.execute(displayable);
    47.     }
    48.     else {
    49.       System.err.println("No command action for command!");
    50.     }
    51.   }
    52.   
    53.   private int[] increment(Hashtable ht, Object target) {
    54.     int[] count = (int[]) ht.get(target);
    55.     if (count == null) {
    56.       count = new int[1];
    57.       ht.put(target, count);
    58.     }
    59.     count[0]++;
    60.     return count;
    61.   }
    62. }
    
    
    Listing 4
    
    
    1. import javax.microedition.lcdui.*;
    2. import javax.microedition.midlet.*;
    3. 
    4. public class ListDemo2 extends MIDlet {
    5.   private Display _display;
    6.   private Form _form = new Form("Choose a type of List:");
    7.   private CommandManager _cm = new CommandManager();
    8. 
    9.   private static final String[] _items
    10.     = {"J2ME", "J2SE", "J2EE"};
    11.   private static final boolean[] _flags
    12.     = new boolean[_items.length];
    13. 
    14.   private class ListCommand extends CommandAction {
    15.     private List _list;
    16.     public ListCommand(int type, String label) {
    17.       super(label, Command.SCREEN, 0);
    18.       _list = new List("Select an item:",
    19.         type, _items, null);
    20.       _cm.add(new OkCommand("OK", Command.SCREEN, 0), _list);
    21.       _cm.add(new OkCommand(List.SELECT_COMMAND), _list);
    22.       _cm.add(_cancelCmd, _list);
    23.     }
    24.     public void execute(Displayable displayable) {
    25.       _display.setCurrent(_list);
    26.     }
    27.   }
    28. 
    29.   private CommandAction _exitCmd = new CommandAction(
    30.   "Exit", Command.EXIT, 0) {
    31.     public void execute(Displayable displayable) {
    32.      notifyDestroyed();
    33.     }
    34.   };
    35. 
    36.   private class OkCommand extends CommandAction {
    37.     public OkCommand(String label, int type, int priority) {
    38.       super(label, type, priority);
    39.     }
    40.     public OkCommand(Command command) {
    41.       super(command);
    42.     }
    43.     public void execute(Displayable displayable) {
    44.       List list = (List) displayable;
    45.       list.getSelectedFlags(_flags);
    46.       StringBuffer msg = new StringBuffer("You selected:");
    47.       for (int i = 0; i < _flags.length; i++) {
    48.         if (_flags[i]) {
    49.           msg.append("\nItem ");
    50.           msg.append(String.valueOf(i + 1));
    51.           msg.append(" - ");
    52.           msg.append(list.getString(i));
    53.         }
    54.       }
    55.       Alert alert = new Alert("Info",
    56.         msg.toString(), null, AlertType.INFO);
    57.       alert.setTimeout(Alert.FOREVER);
    58.       _display.setCurrent(alert);
    59.     }
    60.   };
    61. 
    62.   private CommandAction _cancelCmd = new CommandAction(
    63.   "Cancel", Command.SCREEN, 0) {
    64.     public void execute(Displayable displayable) {
    65.       _display.setCurrent(_form);
    66.     }
    67.   };
    68. 
    69.   public void startApp() {
    70.     _cm.add(new ListCommand(Choice.EXCLUSIVE, "EXCLUSIVE"), _form);
    71.     _cm.add(new ListCommand(Choice.MULTIPLE, "MULTIPLE"), _form);
    72.     _cm.add(new ListCommand(Choice.IMPLICIT, "IMPLICIT"), _form);
    73.     _cm.add(_exitCmd, _form);
    74. 
    75.     _display = Display.getDisplay(this);
    76.     _display.setCurrent(_form);
    77.   }
    78. 
    79.   public void pauseApp() { }
    80. 
    81.   public void destroyApp(boolean unconditional) {
    82.     notifyDestroyed();
    83.   }
    84. }
    
     
    

    All Rights Reserved
    Copyright ©  2004 SYS-CON Media, Inc.
      E-mail: [email protected]

    Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.