Java ME: The Lost Application

As a professional Java ME developer I’ve never really come to terms with the MIDlet class and its underlying aesthetics. As a sort of remedy I often use a simple utility-class I wrote some years ago to conceal the principles of the MIDlet class. It concerns the way you write your Java ME-application so I came to call it Application. I added a few goodies as well.

If you too feel what Java ME-books teach you about writing MIDlets add a lot of unnecessary code and introduce other complexities this utility class might be of help to you as well. Here is a summary of what the Application-class will give you:

  • no need to call MIDlet.destroyApp() yourself (if there was a need to begin with)
  • possibility to listen to a shutdown-event (and pause/wakeup)
  • get hold of the MIDlet instance from one place (instead of passing it around)
  • simple log-method with timestamp
  • easy way to show an alert
  • find out how long the application has been running
  • share application-wide attributes

This is a brief account of how to use the Application-class; short code-snippets followed by explanations:

// ...
public class MyApp extends Application {
    // ...
    protected void execute() {
    // ...

Instead of extending the MIDlet-class you extend the Application-class. Your execution starts in the abstract method execute() which is the only method you have to implement yourself. You could compare it to the startApp()-method of the MIDlet-class.

// ...
public class MyClass implements Application.Events {
    public MyClass() {
        // ...
        Application.addApplicationListener(this);
    }
    // ...
    public void pauseEvent() {}
    public void wakeupEvent() {}
    public void shutdownEvent() {}
    // ...

If you want to know when the MIDlet is paused, woken up or shut down you implement the Application.Events interface, add the corresponding methods and make sure you call addApplicationListener().

Display.getDisplay(Application.midlet()).setCurrent(myDisplayable);

You can always get hold of the current MIDlet instance by calling midlet().

Application.log("-----");
Application.log("Entered constructor", this);

With log() you can print messages to System.out. A timestamp is always added and you can include an object to see its name in the message (useful with this).

Application.alert("Hello user!");

To show an alert to the user just call alert().

long elapsed = Application.elapsedTimeMillis();

By calling elapsedTimeMillis() you can find out how long the application has been running in milliseconds.

// ...
Application.setAttribute("app-name", "MyFineApplication");
// ...
String appName = Application.getAttribute("app-name")
//...

With setAttribute() and getAttribute() you can share attributes anywhere in your classes, i.e. store an attribute in one class and retrieve it in another.

Application.quitApplication();

Finally, to shutdown the application you just call quitApplication(). If you listen to the application events the shutdownEvent will be called where you can perform any clean up code.

Here is a very small demo application (two classes only) that use all the features of the Application-class, which then follows itself.

public class AppDemo extends Application implements Application.Events {

    protected void execute() {
        String appName = "The Application";
        String appVersion = "v0.1";
        Application.setAttribute("app-name", appName);
        Application.setAttribute("app-version", appVersion);

        Application.log("-------------------------------------");
        Application.log(appName+" "+appVersion, this);

        Application.addApplicationListener(this);

        AppUI ui = new AppUI();
        Display.getDisplay(Application.midlet()).setCurrent(ui);
    }

    public void pauseEvent() {}

    public void wakeupEvent() {}

    public void shutdownEvent() {
        long elapsed = Application.elapsedTimeMillis();
        Application.log("Shutting down application after "+elapsed+" ms", this);
    }

}
public class AppUI extends Form implements CommandListener, Application.Events {

    Command alert = new Command("Alert", Command.OK, 1);
    Command exit = new Command("Exit", Command.EXIT, 1);

    public AppUI() {
        super(Application.getAttribute("app-name")+" "+
                Application.getAttribute("app-version"));

        Application.addApplicationListener(this);

        addCommand(alert);
        addCommand(exit);
        setCommandListener(this);
    }

    public void commandAction(Command command, Displayable d) {
        if (command == alert) {
            long elapsed = Application.elapsedTimeMillis();
            Application.alert("I've been running for "+elapsed+" ms");
        } else if (command == exit) {
            Application.quitApplication();
        }
    }

    public void pauseEvent() {}

    public void wakeupEvent() {}

    public void shutdownEvent() {
        setCommandListener(null);
    }

}

And here is the actual Application-class, the heart of the matter:

public abstract class Application extends MIDlet {

    public interface Events {
        public void pauseEvent();
        public void wakeupEvent();
        public void shutdownEvent();
    }

    private static Application mApplication = null;
    private long mStartTimeMillis = -1;
    private Vector mApplicationListeners = new Vector();
    private Hashtable mAttributes = new Hashtable();
    private boolean mIsFirstTime = true;

    ////////////////////////////////////////////////
    // MIDlet methods
    //
    protected Application() {
        if (mStartTimeMillis < 0) {
            mStartTimeMillis = System.currentTimeMillis();
        }
        mApplication = this;
    }

    protected void startApp() {
        if (mIsFirstTime) {
            mIsFirstTime = false;
            execute();
        } else {
            wakeupEvent();
        }
    }

    protected void pauseApp() {
        pauseEvent();
    }

    protected void destroyApp(boolean unconditional) {
        quitApplication(true);
    }

    ////////////////////////////////////////////////
    // Abstract methods
    //
    protected abstract void execute();

    ////////////////////////////////////////////////
    // Utility methods
    //
    public static MIDlet midlet() {
        return mApplication;
    }

    public static long elapsedTimeMillis() {
        return System.currentTimeMillis() - mApplication.mStartTimeMillis;
    }

    public static void quitApplication() {
        mApplication.quitApplication(false);
    }

    public static void addApplicationListener(Events listener) {
        mApplication.mApplicationListeners.addElement(listener);
    }

    public static void removeApplicationListener(Events listener) {
        mApplication.mApplicationListeners.removeElement(listener);
    }

    public static void setAttribute(String key, String value) {
        mApplication.mAttributes.put(key, value);
    }

    public static String getAttribute(String key) {
        return (String)mApplication.mAttributes.get(key);
    }

    public static void alert(String alertMessege) {
        Alert alert = new Alert(null, alertMessege, null, null);
        alert.setTimeout(Alert.FOREVER);
        Display.getDisplay(mApplication).setCurrent(alert);
    }

    public static void log(String logMessage) {
        System.out.println("["+elapsedTimeMillis()+"] "+logMessage);
    }

    public static void log(String logMessage, Object object) {
        if (object != null) {
            log(object.getClass().getName()+": "+logMessage);
        } else {
            log("(null): "+logMessage);
        }
    }

    /////////////////////////////////////////////////////
    // Private methods
    //
    private void quitApplication(boolean isCalledFromDestroyApp) {
        if (isCalledFromDestroyApp) {
            shutdownEvent();
            mApplicationListeners.removeAllElements();
            notifyDestroyed();
        } else {
            destroyApp(true);
        }
    }

    private void pauseEvent() {
        Enumeration listeners = mApplicationListeners.elements();
        while (listeners.hasMoreElements()) {
            ((Events)listeners.nextElement()).pauseEvent();
        }
    }

    private void wakeupEvent() {
        Enumeration listeners = mApplicationListeners.elements();
        while (listeners.hasMoreElements()) {
            ((Events)listeners.nextElement()).wakeupEvent();
        }
    }

    private void shutdownEvent() {
        Enumeration listeners = mApplicationListeners.elements();
        while (listeners.hasMoreElements()) {
            ((Events)listeners.nextElement()).shutdownEvent();
        }
    }
}

I've prepared a small zip-file which contains the sourcecode presented above. It can be downloaded here:
[CLICK HERE TO DOWNLOAD SOURCECODE]

I recommend you to tinker around and customize the Application-class to your own needs. I'd be delighted if you'd consider sharing your changes and extensions. The comment function of this blog might be an excellent place for this.

Enjoy!

Darius Katz
Senior Consultant at Jayway

.

This Post Has 2 Comments

  1. As a professional j2me developer myself, I can’t believe I never did something like that, instead of copy/pasting something like that from project to project. This is a really good idea!
    I will customize the class since I’m becoming an LWUIT fan, to use LWUIT log and alert. But this is just me.
    It’s hard to think in something general enough to worth adding, but maybe some generic closeQuiet methods like
    closeQuiet(Connection conn){
    if(conn != null)
    try{conn.close()} catch(Exception ex){log(ex);}
    }
    for connections, resources and stuff like that…I always end up writing something like it for one reason or another

  2. Hi,
    in SEMC platform (don’t know if other platform do the same), MIDlet’s pauseApp is not called for all system event (incoming call, minimizing, …). Instead, Canvas’ events showNotify/hideNotify are called.
    Any thoughts of how we could inject this Canvas’ event back to Application, so that pauseEvent could be spread for all listeners?
    Nice post!
    BR,
    Camilo.

Leave a Reply

Close Menu