http://www.developer.com/

Back to article

Building a Simple BlackBerry Application Interface


August 14, 2008

As I dig deep into building applications for the BlackBerry, I'd like to employ test-driven development (TDD). I've constructed a simple assertion-based framework, BBTest, designed for unit testing BlackBerry applications, but so far it is only a domain model. A testing framework isn't of much value without a way of viewing its output, so I am building an as-simple-as-possible user interface for the framework. By using BBTest, you'll have a way to test-drive your applications as well.

Building BlackBerry applications is much like building Java Swing applications. The same general concepts apply, but there also are some very interesting differences, from how list controls work all the way up to how you support multiple entry points in an application.

The design of the BlackBerry means that I need to use a single application entry point (in other words, the main method) to trigger both the given application that I'm testing, as well as the unit tests for that application. In Eclipse, both the application source code and test framework source code need to sit side-by-side in one Eclipse project. (I'm using a build script to copy over the source from the BBTest project.)

To trigger the tests separately requires a second Eclipse project. This second project represents an alternate entry point for the primary application. When the BlackBerry (or BlackBerry simulator) executes this alternate entry point, the primary application gets triggered with a different argument to its main method. That will allow me to code the main method to take a different action—in this case, to execute the primary application's unit tests.

The steps to configure the alternate entry point are as follows:

  1. Open the project properties for the secondary (testing) project.
  2. Select BlackBerry Project Properties.
  3. Select the Application tab.
  4. From the Project type: dropdown, select Alternate CLDC Application Entry Point.
  5. Ensure that the Alternate entry point for: dropdown shows the primary project as being selected.
  6. In the field marked Argument passed to "static public void main(String args[])":, enter unittest.

Figure 1 shows the BlackBerry Project Properties dialog.



Click here for a larger image.

Figure 1: The Eclipse BlackBerry Project Properties dialog.

BlackBerry Application Fundamentals

The main class for my primary application is shown is Listing 1. BlackBerry applications extend the class net.rim.device.api.ui.UiApplication. For applications with a user interface, the primary job of this main class is to create a screen, push it onto the stack of screens that the BlackBerry tracks, and then enter the event dispacher. If the application requires additional screens, they get pushed onto or popped from the stack. The BlackBerry routes input events, including trackwheel events and key press events, to the screen on the top of the stack.

Programming BlackBerry interfaces is similar to programming Java Swing applications. Per the Javadoc, "Only one thread at a time (generally, the event-dispatching thread) may gain access to an interface component at once." Other interactions with user interface components must be executed by using invokeLater or invokeAndWait.

Listing 1 shows that I execute the unit test framework if the first argument passed to main is the String "unittest"—to correspond with the property setting for the alternate entry point. Otherwise, the primary application's main screen gets pushed onto the stack.

The TestApp class, shown in Listing 2, is the simple user interface I created for purposes of quickly displaying some test results. SampleTest is a MicroTestGroup subclass with a couple simple passing tests and one failing test.

Listing 1: A main application class.

package com.langrsoft.app;

import net.rim.device.api.ui.*;
import com.langrsoft.agent.*;

public class AgentApp extends UiApplication {
   public static void main(String args[]) {
      AgentApp app = new AgentApp();
      if (isInTestMode(args))
         app.test();
      else
         app.go();
      app.enterEventDispatcher();
   }

   private void test() {
      pushScreen(new TestApp(new SampleTest()));
   }

   private static boolean isInTestMode(String[] args) {
      return args.length > 0 && "unittest".equals(args[0]);
   }

   public void go() {
      pushScreen(new MainApp());
   }
}

Listing 2: TestApp.

package com.langrsoft.app;

import net.rim.device.api.system.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import com.langrsoft.bbtest.*;

public class TestApp extends MainScreen
   implements ResultListener {
   private ListField list = new ListField();
   private TestListFieldModel model = new TestListFieldModel();

   public TestApp(final MicroTestGroup group) {
      list.setCallback(model);

      add(list);

      group.setListener(this);
      new Thread(new Runnable() {
         public void run() {
            group.execute();
         }
      }).start();
   }

   public void ran(final MicroTest test) {
      UiApplication.getUiApplication().invokeLater(new Runnable() {
         public void run() {
            list.insert(0);
            model.insert(test);
         }
      });
   }

   protected void onObscured() {
      UiApplication.getApplication().requestForeground();
   }

   public boolean keyChar(char key, int status, int time) {
      if (key == Characters.ESCAPE) System.exit(0);
      return false;
   }
}

The TestApp class is a subclass of MainScreen. This means that TestApp can be pushed onto the application stack, and will be rendered to produce a visible interface. The TestApp class implements the ResultListener interface, and calls setListener to register itself with the MicroTestGroup. As tests execute, the unit test framework invokes callback method ran, passing in the MicroTest object.

The ListField Control

I chose a ListField as the presumably simplest control to show the list of passing or failing unit tests. It turns out that the ListField is a bit more involved. A ListField requires a callback object, one that will manage and draw the underlying data that the ListField presents. The ListField callback must implement the ListFieldCallback interface, which declares four methods: drawListRow, get, getPreferredWidth, and indexOfList.

I'll detail the ListFieldCallback interface methods shortly. For now, you can look at Listing 3, which contains my implementation of the interface, TestListFieldModel.

Here's a step-through of the code in the TestApp constructor:

  • Set the callback on the ListField object to an instance of TestListFieldModel.
  • add the list to the MainScreen. This adds the user interface component to the screen so that it will be visible when the screen is rendered.
  • Register this TestApp instance as the ResultListener on the MicroTestGroup object.
  • Create and start a separate thread to execute tests on the MicroTestGroup object.

When the callback method ran gets invoked, its job is to add the test to the list. Two steps are required: First, the code calls insert on the ListField object (list) to grow its size by one. Second, the test object is passed to the ListFieldCallback (which I have named model). Using invokeLater allows this code to properly execute on the application's event queue.

The keyChar method hook allows the TestApp object to trap character events from the BlackBerry. In this case, I exit the application if the user presses the escape key.

The ListFieldCallback (see Listing 3) has two primary jobs: to manage the list of tests, and to draw a given list row entry. The drawListRow method gets called for each row that must be painted. Code in drawListRow first calls get, which returns the MicroTest at the appropriate index from the tests Vector. Using the Graphics object, the code then sets an appropriate color (green for a passing test, red for a failing test), and then draws some text at the coordinate x=0, y=the y coordinate of the current row in the ListField.

The indexOfList method is designed to support the user doing a text search against the contents of the list. Because this is intended to be a minimal implementation, I've not yet implemented any real functionality in this method. The same thing applies to getPreferredWidth, I'm not yet worried about what this method should return. It works (see Figure 2)—that's all I care about for now.

Automatically Running the Test Application

When in development mode, I want my unit tests to show automatically when the BlackBerry simulator starts up. Accomplishing this goal requires two steps. First, the secondary application (the one with the alternate entry point) must be configured to "autorun." The setting is found in the BlackBerry Project Properties under the Application tab, where you'll find a checkbox labeled Auto-run on startup (refer to Figure 1). Second, the application must be configured to come to the foreground; otherwise, it will execute in the background by default (which means you won't be able to see it). A one-line implementation of the callback method onObscured is all that's needed—simply tell the UiApplication singleton object to requestForeground.

Figure 2: The quick & dirty BBTest user interface.

Listing 3: TestListFieldModel.

package com.langrsoft.app;

import java.util.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import com.langrsoft.bbtest.*;
import com.langrsoft.ui.util.*;

public class TestListFieldModel implements ListFieldCallback {
   private Vector tests = new Vector();

   public void drawListRow(
      ListField listField, Graphics graphics, int index,
         int y, int width) {
         MicroTest test = (MicroTest)get(listField, index);
         graphics.setColor(test.passed() ? Color.GREEN :
            Color.RED);
         graphics.drawText(toString(test), 0, y);
   }

   public Object get(ListField listField, int index) {
      return tests.elementAt(index);
   }

   public int getPreferredWidth(ListField listField) {
      return listField.getWidth();
   }

   private String toString(MicroTest test) {
      return test.name() + " [" + test.className() + "]";
   }

   public int indexOfList(ListField listField, String prefix,
      int start) {
      return listField.getSelectedIndex();
   }

   public void insert(MicroTest test) {
      tests.insertElementAt(test, 0);
   }
}

Wrapping Up

As part of building this rudimentary user interface for the unit testing framework, I made some minor improvements to the core BBTest code. I also learned that you do not want to code a catch block to trap Throwable instances; otherwise, the BlackBerry generates some odd exceptions. I changed the BBTest code to catch RuntimeException instead. The RIM (Research In Motion, the company that makes the BlackBerry) Javadoc for Throwable discusses the details of this special implementation.

Overall, the very small amount of code I've presented here points out a significant number of interesting BlackBerry development challenges. It took me a good bit of time to search for solutions and otherwise dig out of the pitfalls; hopefully, this information can help you avoid wasting similar time.

The simple BBTest user interface does the job of showing which tests passed and failed. I hope to make this a robust open source unit testing tool for the BlackBerry, so it needs a bit more work. I intend to add some nicer graphics, replace the list with a tree widget, make the font smaller, and introduce a few more usability features to make the test tool more effective.

Coding user interface applications in any environment, BlackBerry included, is a risky proposition: it's always too easy to allow the model, view, and controller code to be highly intermingled, making for an unmaintainable mess. Because I'm a proponent of TDD, I'll be using this current rudimentary BBTest tool to rebuild the testing tool, but in a test-first manner, to help ensure that it ends up with the right design.

Download the Code

You can download the sample code for agent.zip and bbtest.zip here.

About the Author

Jeff Langr is a veteran software developer with over a quarter century of professional software development experience. He's written two books, including Agile Java: Crafting Code With Test-Driven Development (Prentice Hall) in 2005. Jeff has contributed a couple chapters to Uncle Bob Martin's upcoming book, Clean Code. Jeff has written over 80 articles on software development, with over forty appearing at Developer.com. You can learn more about Jeff at his site, http://langrsoft.com, or you can contact him via email at jeff at langrsoft dot com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date