Let the mobile games begin, Part 2

J2ME and .Net Compact Framework in action

1 2 3 Page 2
Page 2 of 3
public class AWTMap extends Frame
    implements WindowListener, ActionListener {
  // UI and handlers
private void listScreen (boolean newSearch) {
    try {
      if (newSearch) {
        SoapObject method = new SoapObject("", "getDirections");
        // Use the SE version for standard JDK Http methods
        HttpTransportSE rpc = new HttpTransportSE(endPointURL, "\"\"");
        rpc.setClassMap(cm);
        method.addProperty("in0", fromStreet.getText());
        method.addProperty("in1", fromCity.getText());
        method.addProperty("in2", fromState.getText());
        method.addProperty("in3", fromZip.getText());
        method.addProperty("in4", toStreet.getText());
        method.addProperty("in5", toCity.getText());
        method.addProperty("in6", toState.getText());
        method.addProperty("in7", toZip.getText());
        Vector v = (Vector) rpc.call (method);
        directionsList = new java.awt.List(10, false);
        directionsList.add("Overview Map");
        for (int i = 0; i < v.size(); i++) {
          directionsList.add((String) v.elementAt(i));
        }
        directionsList.setSize(200, 200);
      }
      Panel top = new Panel ();
      top.setLayout(new FlowLayout(FlowLayout.LEFT));
      top.add(directionsList);
      Panel bottom = new Panel ();
      bottom.setLayout(new FlowLayout(FlowLayout.LEFT));
      bottom.add(startOver);
      bottom.add(showMap);
      
      scroll.remove(content);
      content = new Panel ();
      content.setLayout(new BorderLayout());
      content.add(top, BorderLayout.CENTER);
      content.add(bottom, BorderLayout.SOUTH);
      scroll.add(content);
      setVisible(true);
      
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  
  private void mapScreen (int i) {
    try {
      ImageItem img;
      byte [] imgarray;
      SoapObject method = new SoapObject("", "getMap");
      HttpTransportSE rpc = new HttpTransportSE(endPointURL, "\"\"");
      rpc.setClassMap(cm);
      method.addProperty("in0", new Integer(i));
      method.addProperty("in1", new Integer(200));
      method.addProperty("in2", new Integer(200));
      imgarray = (byte []) rpc.call (method);
      img = new ImageItem(imgarray, 200, 200);
      
      Panel top = new Panel ();
      top.add(img);
      Panel bottom = new Panel ();
      bottom.add(startOver);
      bottom.add(showDirections);
      
      scroll.remove(content);
      content = new Panel ();
      content.setLayout(new BorderLayout());
      content.add(top, BorderLayout.CENTER);
      content.add(bottom, BorderLayout.SOUTH);
      scroll.add(content);
      setVisible(true);
      
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  // Other methods
}

The UI is handwritten using the Java AWT library. AWT proves sufficient for most mobile tasks although it requires a lot of tinkering before the layout renders properly. Also, since AWT does not support any image display widget, we must implement our own ImageItem class. IDE tools such as CodeWarrior Wireless Studio could help us generate the UI code more quickly.

The Web services code is also handwritten against the kSOAP library. For most mobile applications, the remote SOAP interface is not complex, and handwritten SOAP RPC (remote procedure call) code gives us the maximum control. If you really need automatic SOAP stub generators, plug-ins for kSOAP are available for all leading J2ME IDEs.

The .Net Compact Framework client

Figure 3 shows a .Net Compact Framework client for the driving-directions application running a Pocket PC device.

Figure 3. The .Net Compact Framework client in action. Click on thumbnail to view full-size image.

The first thing we notice about the .Net Compact Framework client is its rich UI. The tabbed page interface reduces screen clutter and is easy to navigate on a small device. However, note that not all standard Windows Forms properties are supported in .Net Compact Framework. For example, the tabpage control's .Net Compact Framework edition does not support auto scroll. To support scroll, we must add in the scrollbar controls and manually implement their behaviors.

A great value of .Net Compact Framework is RAD (rapid application development) support from the Visual Studio .Net (VS.Net) IDE. You can literally click your way through the IDE to build simple applications. The two most useful VS.Net RAD tools are the following:

  • The UI design tool allows us to position components accurately—see Figure 4.
  • The Web services tool makes generating and referencing remote service objects a breeze—see Figure 5.
Figure 4. The Visual Studio .Net visual GUI designer
Figure 5. The Visual Studio .Net Web services stub generator. Click on thumbnail to view full-size image.

Using VS.Net to develop a simple client like this, we need to only write a minimum amount of code:

Listing 3. User-written code for the .Net Compact Framework client

namespace CFMap {
  public class CFMapForm : System.Windows.Forms.Form {
    // Auto-generated UI and initialization code
    
    // You need to dig in and find out the right order
    // of the tabbed pages:
    // ... ...
    this.tabControl1.Controls.Add(this.fromPage);
    this.tabControl1.Controls.Add(this.toPage);
    this.tabControl1.Controls.Add(this.directionsPage);
    this.tabControl1.Controls.Add(this.mapPage);
    // ... ...
    // Event handler for the "GetDirections" button
    private void btnGetDirections_Click(object sender,
              System.EventArgs e) {
      String [] directions = service.getDirections(
                    fromStreet.Text, fromCity.Text, 
                    fromState.Text, fromZip.Text, 
                    toStreet.Text, toCity.Text,
                    toState.Text, toZip.Text);
      ArrayList al = new ArrayList ();
      al.Add("Overview Map");
      for (int i = 0; i < directions.Length; i++ ) {
        al.Add( directions[i] );
      }
      directionsList.DataSource = al;
      tabControl1.SelectedIndex = 2;
    }
    // Event handler for the "GetMap" button
    private void btnGetMap_Click(object sender,
                    System.EventArgs e) {
      byte [] imgarray =
        service.getMap(directionsList.SelectedIndex,
                       200, 200);
      mapBox.Image =
        new Bitmap (new MemoryStream(imgarray));
      tabControl1.SelectedIndex = 3;
    }
    
    // Switch to the "To" screen
    private void btnToAddress_Click(object sender,
         System.EventArgs e) {
      tabControl1.SelectedIndex = 1;
    }
    // Switch to the "From" screen
    private void btnFromAddress_Click(object sender,
         System.EventArgs e) {
      tabControl1.SelectedIndex = 0;
    }
  }
}

Although visual programming is extremely powerful for simple applications, visual tools might not scale well to handle sophisticated applications. Take the UI designer and Web service reference generator as examples:

  • It is hard to develop UI paradigms that don't conform to the scripts in the designer. For example, the UI designer is little help if you want to use screen flow (see the J2ME Personal Profile example) rather than tabbed pages.
  • If we want to expand the application later and need to refactor the variable names, the designer will change only the names in generated code. If we do not manually keep the callback delegate names in sync with component names, the application will soon become unmaintainable.
  • If we want to use custom HTTP transport, use a different endpoint URL, or access specific SOAP elements (e.g., headers), we must hack into the generated Reference.cs file and manually change it. We risk losing those changes if we regenerate the stub reference again.

So, the bottom line is that RAD tools have their limits. Developers still need to understand their software's inner workings. The productivity gain introduced by those tools could prove limited for large-scale real-world applications.

The J2ME MIDP client

Figure 6 demonstrates a J2ME MIDP version of the driving-directions application in action on a Motorola Java phone (model i95cl). It resembles the J2ME Personal Profile application except that MIDP uses its own light UI (liquid crystal display UI, or LCDUI) rather than the standard AWT. Listing 4 shows the MIDP client's source code.

Figure 6. The MIDP client in action. Click on thumbnail to view full-size image.

Listing 4. The MIDP client

public class MIDPDirections extends MIDlet implements CommandListener {
  Display display;
  Command fromNext, toNext, cancel, done, exit;
  TextField fromStreet, fromCity, fromState, fromZip;
  TextField toStreet, toCity, toState, toZip;
  String endPointURL;
  public MIDPDirections () {
    endPointURL = "http://128.83.129.226:8080/axis/services/MapPoint";
    display = Display.getDisplay(this);
    fromNext = new Command("NEXT", Command.SCREEN, 2);
    toNext = new Command("NEXT", Command.SCREEN, 2);
    cancel = new Command("CANCEL", Command.SCREEN, 2);
    done = new Command("DONE", Command.SCREEN, 2);
    exit = new Command("EXIT", Command.SCREEN, 2);
    fromStreet = new TextField("Street", "", 20, TextField.ANY);
    fromCity = new TextField("City", "", 20, TextField.ANY);
    fromState = new TextField("State", "", 10, TextField.ANY);
    fromZip = new TextField("Zip", "", 10, TextField.NUMERIC);
    toStreet = new TextField("Street", "", 20, TextField.ANY);
    toCity = new TextField("City", "", 20, TextField.ANY);
    toState = new TextField("State", "", 10, TextField.ANY);
    toZip = new TextField("Zip", "", 10, TextField.NUMERIC);
  }
  public void startApp() {
    fromScreen ();
  }
  public void pauseApp() {
    // Do nothing
  }
  public void destroyApp(boolean unconditional) {
    // Do nothing
  }
  public void commandAction(Command command, Displayable screen) {
    if (command == exit) {
      destroyApp(false);
      notifyDestroyed();
    } else if ( command == done || command == cancel ) {
      startApp ();
    } else if ( command == fromNext ) {
      toScreen ();
    } else if ( command == toNext ) {
      directionScreen ();
    }
  }
  public void fromScreen () {
    Form form = new Form ("From");
    form.append(fromStreet);
    form.append(fromCity);
    form.append(fromState);
    form.append(fromZip);
    form.addCommand(fromNext);
    form.addCommand(cancel);
    form.setCommandListener( (CommandListener) this);
    display.setCurrent(form);
  }
  public void toScreen () {
    Form form = new Form ("To");
    form.append(toStreet);
    form.append(toCity);
    form.append(toState);
    form.append(toZip);
    form.addCommand(toNext);
    form.addCommand(cancel);
    form.setCommandListener( (CommandListener) this);
    display.setCurrent(form);
  }
  public void directionScreen () {
    Vector v = getDirections ();
    Form form = new Form ("Directions");
    for (int i = 0; i < v.size(); i++) {
      form.append((String) v.elementAt(i) + "\n");
    }
    form.addCommand(done);
    form.addCommand(exit);
    form.setCommandListener( (CommandListener) this);
    display.setCurrent(form);
  }
  private Vector getDirections () {
    Vector v = null;
    try {
      SoapObject method = new SoapObject("", "getDirections");
      method.addProperty("in0", fromStreet.getString());
      method.addProperty("in1", fromCity.getString());
      method.addProperty("in2", fromState.getString());
      method.addProperty("in3", fromZip.getString());
      method.addProperty("in4", toStreet.getString());
      method.addProperty("in5", toCity.getString());
      method.addProperty("in6", toState.getString());
      method.addProperty("in7", toZip.getString());
      HttpTransport rpc = new HttpTransport(endPointURL, "\"\"");
      v = (Vector) rpc.call (method);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return v;
  }
}

For large screen MIDP devices, we could add map displays to the application too. However, MIDP only supports the PNG image format, while MapPoint only renders GIF images. An image format conversion module in the portal server can solve this problem.

Comparison points

In general, all three clients get the job done. But how do they compare with each other?

  • Runtime installation: J2ME/MIDP runtimes are factory installed on millions of handsets today. PersonalJava and Personal Profile runtimes are factory installed on most Linux PDAs (e.g., Sharp). For Pocket PC and Palm devices, the user must install the Java runtime through the synchronization link. The .Net Compact Framework runtime will be preinstalled on all future Pocket PC devices. But for now, the user must install the related CAB (cabinet) files on the device first.
  • Performance: On the Pocket PC device, the Jeode PersonalJava client has comparable performance as the .Net Compact Framework client in both startup time and operational smoothness.
  • User interface: .Net Compact Framework has a rich set of UI controls. Most Windows Forms and Visual Studio .Net developers are already familiar with those components. The C# language also has an excellent programming model for UI applications (e.g., delegates for UI events). The AWT library in J2ME Personal Profile and PersonalJava gets the job done. But most Java developers would agree that programming in AWT is not exactly pleasant. However, help is on the horizon: IBM has just released the SWT (Standard Widget Toolkit), which has proven success in the desktop Java world on the Pocket PC platform. In addition, the new Advanced Graphics and UI for J2ME Optional Package (JSR 209) proposes to add optional Swing capability to the new Personal Profile. The LCDUI for MIDP is powerful and easy to use for phone devices.
  • Web services: SOAP Web services support is tightly integrated into .Net Compact Framework and Visual Studio .Net. They support both RPC style and asynchronous Web services. In our J2ME example, I used a third-party lightweight SOAP library (kSOAP). I actually prefer to work closer to the XML wire protocol using kSOAP. For developers who want to be completely shielded from the underlying XML layer, the J2ME Web services optional package allow us to map SOAP calls to Java Remote Method Invocation (RMI)-style remote calls. An unofficial implementation for this optional package (based on proposed draft v0.7) is already available from IBM.
  • Tools: A big advantage for .Net Compact Framework is the VS.Net IDE. In our example, we discovered that VS.Net can greatly reduce handwritten code. However, as I already mentioned, every code generation tool has its limits. If you do not like the VS.Net way of programming, you are out of luck since VS.Net is the only tool currently available for .Net Compact Framework developers. J2ME, on the other hand, has at least a dozen IDE and command-line tools. They collectively offer all the features VS.Net has, but none is as good as VS.Net in a one-to-one test. The most promising J2ME IDE is IBM's WebSphere Studio Device Developer.

Enhance the application

So far, our driving-directions application is simplistic. It is designed for a single user, lacks support for offline operations, and does not take advantage of the local location context. In this section, I discuss ways to enhance the application and explore options available in both J2ME and .Net Compact Framework.

Session management

Our portal only maintains one global scope MPClient object, and all mobile clients must share it. Since the route cache is stored inside the MPClient object, simultaneous users could prove problematic. Before any stateful Web services specification is widely adopted, we can rely on HTTP's built-in state management mechanisms to track user sessions.

To add session management, on the portal server side, we need to only change the service scope from Application to Session in the wsdd descriptor file. The Axis engine will automatically use the servlet container's HttpSession object to maintain one MPClient for each session. By default, the server tracks sessions using HTTP cookies. Now the client-side stub has to understand cookies too. The J2ME and .Net clients require different techniques:

1 2 3 Page 2
Page 2 of 3