Tutorial

Lush

This tutorial creates a simple app for J2ME, BlackBerry and Android devices. It covers the installation, implementation and design of mobile apps with J2ME Polish. For detailed information, please refer to the documentation section.

Installation

J2ME Polish contains a graphical installer which can be started by double-clicking the downloaded jar-File or by calling java -jar j2mepolish-[VERSION].jar from the command line, e.g. "java -jar j2mepolish-2.3.jar".

Since J2ME Polish includes the MicroEmulator, you don't need any additional installations for starting with J2ME Polish (apart from a Java SDK and an Ant installation). It is, however, useful to install the Java ME SDK, the BlackBerry JDEs and Android SDK. Find more information in the Installation section and the platforms documentation.

J2ME Polish from 20,000 miles

J2ME Polish can be a complex beast, but the basics are simple:

  • J2ME Polish consists of 3 parts:
    1. client libraries that contain UI classes, helper classes, serialization & persistence and many more.
    2. build framework that is based on Apache Ant and that compiles sourcecode, includes resources and creates apps.
    3. device database that is XML based and contains the definition of devices, vendors, APIs and more.
  • Your app is based on a Java ME / J2ME / MIDP javax.microedition.midlet.MIDlet.
  • You select your target devices in your build.xml script.
  • J2ME Polish will produce a JAR/JAD or COD/JAD or APK for each target device.
  • You can adjust the design of your app using CSS syntax, by default located in the resources/polish.css.
  • You can customize your app for different devices and platforms by using preprocessing code.

A Sample App

Let's create a cool app. By cool app we mean a simplistic menu that also supports the rendering of a busy indicator upon any page. For the architecture of the app we use a simple model-view-control pattern in which the view and its control are coupled - meaning that the controller is specialized for the current UI. In a mobile context this is not really a disadvantage as the UI flow is often completely different when switching to another form factor or platform such as tablet computers. So we strife to reuse the model classes but we optimize the view and control for the target devices.

By the way, this is the blank sample app that you can find in ${polish.home}/samples/blank after installing J2ME Polish, so there is no need to copy these classes.

The MIDlet itself just forwards control to the controller:

Listing 1: de.enough.polish.app.App.java

package de.enough.polish.app;

import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

import de.enough.polish.app.control.Controller;

/**
 * A simple MIDlet that can be used as a starting point for your own apps.
 */
public class App 
extends MIDlet
{
	
    private Controller controller;
	
    public App() {
        // nothing to init
    }

    protected void startApp(){
    	if (this.controller == null) {
    		this.controller = new Controller( this );
    		this.controller.appStart();
    	} else {
    		this.controller.appContinue();
    	}
    }
    
    protected void pauseApp() {
        this.controller.appPause();
    }

    protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
        this.controller = null;
    }

    public void exit() {
        try {
            destroyApp(true);
        } catch (MIDletStateChangeException e) {
            //#debug error
            System.out.println("Unable to destroyApp" + e);
        }
        notifyDestroyed();
    }
}

The controller controls the life cycle of the app and is responsible for consuming the events and showing new screens.

There are different opinions about the correct architecture for mobile MIDlets and they mostly center around the question how events should be consumed.
  • If you have a user drive app, you should not create an artificial event mechanism, but instead concentrate on a a javax.microedition.lcdui.Command driven architecture.
  • When you have both user driven and background events that yield into the very same operations as UI events, you can consider creating an artificial event layer that abstracts both Commands and background events.
  • In most cases you should not create an artificial event layer, but consume events directly. Normally you should consume Command events as quickly as possible, as you will block UI operations otherwise. With J2ME Polish you can set the polish.executeCommandsAsynchrone variable in your build.xml script to true, so that Command events run in a separate thread:
    <variable name="polish.executeCommandsAsynchrone" value="true" />
  • Whatever you do, do not handle low level events such as key or pointer events unless you really need to. Talking about touch events, J2ME Polish also contains support for touch gestures.



Listing 2: de.enough.polish.app.control.Controller.java

package de.enough.polish.app.control;

import java.io.IOException;

import javax.microedition.lcdui.Image;


import de.enough.polish.app.App;
import de.enough.polish.app.model.Configuration;
import de.enough.polish.app.view.MainMenuList;
import de.enough.polish.io.RmsStorage;
import de.enough.polish.ui.Command;
import de.enough.polish.ui.CommandListener;
import de.enough.polish.ui.Display;
import de.enough.polish.ui.Displayable;
import de.enough.polish.ui.Gauge;
import de.enough.polish.ui.ScreenInfo;
import de.enough.polish.ui.SimpleScreenHistory;
import de.enough.polish.ui.splash2.ApplicationInitializer;
import de.enough.polish.ui.splash2.InitializerSplashScreen;
import de.enough.polish.util.Locale;

/**
 * Controls the UI of the mobile app
 */
public class Controller
implements ApplicationInitializer, CommandListener
{

	private final App midlet;
	private Display display;
	private Configuration configuration;
	private RmsStorage storage;
	
	private Command cmdExit = new Command(Locale.get("cmd.exit"), Command.EXIT, 10);
	private Command cmdBack = new Command(Locale.get("cmd.back"), Command.BACK, 2);
	
	private MainMenuList screenMainMenu;
	private static final int MAIN_ACTION_START = 0;
	private static final int MAIN_ACTION_STOP = 1;
	private static final int MAIN_ACTION_ABOUT = 2;
	private static final int MAIN_ACTION_EXIT = 3;
	
	private SimpleScreenHistory screenHistory;
	private int busyIndicators;
	
	

	/**
	 * Creates a new controller.
	 * @param midlet the main application
	 */
	public Controller(App midlet) {
		this.midlet = midlet;
		this.display = Display.getDisplay(midlet);
		this.screenHistory = new SimpleScreenHistory(this.display);
	}

	/**
	 * Lifecycle: starts the application for the first time.
	 */
	public void appStart() {
		String splashUrl = "/Splash.png";
		Image splashImage = null;
		try {
			splashImage = Image.createImage(splashUrl);
		} catch (Exception e) {
			//#debug error
			System.out.println("Unable to load splash image " + splashUrl +  e);
		}
		int backgroundColor = 0xffffff;
		InitializerSplashScreen splash = new InitializerSplashScreen(splashImage, backgroundColor,  this);
		this.display.setCurrent( splash );
	}

	/**
	 * Lifecycle: pauses the application, e.g. when there is an incoming call.
	 */
	public void appPause() {
		// TODO implement pauseApp, e.g. stop streaming
	}

	/**
	 * Lifecycle: continues the application after it has been paused.
	 */
	public void appContinue() {
		// TODO implement continueApp, e.g. start streaming again
	}

	/**
	 * Initializes this application in a background thread that is called from within the splash screen.
	 */
	public void initApp() {
		long initStartTime = System.currentTimeMillis();
		//#style busyGauge
		Gauge busyGauge = new Gauge(null, false, Gauge.INDEFINITE, Gauge.CONTINUOUS_RUNNING );
		ScreenInfo.setItem(busyGauge);
		ScreenInfo.setVisible(false);
		
		this.storage = new RmsStorage();
		this.configuration = configurationLoad();
		// create main menu:
		this.screenMainMenu = createMainMenu();
		long currentTime = System.currentTimeMillis();
		long maxTime = 1500;
		if (currentTime - initStartTime < maxTime) { // show the splash at least for 1500 ms:
			try {
				Thread.sleep(maxTime - currentTime + initStartTime);
			} catch (InterruptedException e) {
				// ignore
			}
		}
		this.display.setCurrent( this.screenMainMenu );
	}

	private MainMenuList createMainMenu() {
		MainMenuList list = new MainMenuList();
		list.setCommandListener(this);
		list.addCommand(this.cmdExit);
		list.addEntry("Start Busy Indicator");
		list.addEntry("Stop Busy Indicator");
		list.addEntry("entry 3");
		list.addEntry(Locale.get("cmd.exit"));
		return list;
	}

	/**
	 * Loads the configuration of this app.
	 * @return the loaded configuration or an new one.
	 */
	private Configuration configurationLoad() {
		try {
			Configuration cfg = (Configuration) this.storage.read(Configuration.KEY);
			return cfg;
		} catch (IOException e) {
			//#debug info
			System.out.println("Unable to load configuration" + e);
		}
		return new Configuration();
	}

	/**
	 * Persists the configuration.
	 * @return true when saving was successful, otherwise false is returned.
	 */
	private boolean configurationSave() {
		try {
			this.storage.save(this.configuration, Configuration.KEY);
			return true;
		} catch (IOException e) {
			//#debug error
			System.out.println("Unable to store the configuration" + e);
			return false;
		}
	}

	public void commandAction(Command cmd, Displayable disp) {
		if (cmd == this.cmdExit) {
			exit();
		} else if (disp == this.screenMainMenu) {
			if (handleCommandMainMenu(cmd)) {
				return;
			}
		} else if (cmd == this.cmdBack) {
			if (this.screenHistory.hasPrevious()) {
				this.screenHistory.showPrevious();
			} else {
				this.screenHistory.clearHistory();
				this.display.setCurrent(this.screenMainMenu);
			}
		}
		
	}

	/**
	 * Handles commands for the main menu
	 * @param cmd the command of the main menu
	 * @return true when a command was handled
	 */
	private boolean handleCommandMainMenu(Command cmd) {
		if (cmd == MainMenuList.SELECT_COMMAND) {
			int index = this.screenMainMenu.getSelectedIndex();
			switch (index) {
			case MAIN_ACTION_START:
				startBusyIndicator();
				break;
			case MAIN_ACTION_STOP:
				stopBusyIndicator();
				break;
			case MAIN_ACTION_ABOUT:
				break;
			case MAIN_ACTION_EXIT:
				exit();
				return true;
			}
		}
		return false;
	}

	/**
	 * Exits this app
	 */
	private void exit() {
		if (this.configuration.isDirty()) {
			configurationSave();
		}
		this.midlet.exit();
	}
	
	/**
	 * Stops the busy indicator.
	 * When no busy indicators are left, the busy indicator won't be shown any more.
	 * The busy indicator uses ScreenInfo, this element requires the preprocessing variable 
	 * <variable name="polish.ScreenInfo.enable" value="true" />
	 * in your build.xml script.
	 * Each long running operation should call startBusyIndicator() and stopBusyIndicator() for giving the user feedback.
	 * @see #startBusyIndicator()
	 */
	private synchronized void stopBusyIndicator() {
		if (this.busyIndicators > 0) {
			this.busyIndicators--;
			if (this.busyIndicators == 0) {
				ScreenInfo.setVisible(false);
			}
		}
		//#debug
		System.out.println("stop busy indicator: Number of busy indicators: " + this.busyIndicators);
	}

	/**
	 * Starts the busy indicator.
	 * When this is the first indicator, the busy indicator will be made visible.
	 * The busy indicator uses ScreenInfo, this element requires the preprocessing variable 
	 * <variable name="polish.ScreenInfo.enable" value="true" />
	 * in your build.xml script.
	 * Each long running operation should call startBusyIndicator() and stopBusyIndicator() for giving the user feedback.
	 * @see #stopBusyIndicator()
	 * @see #initApp() for initialization of the gauge
	 */
	private synchronized void startBusyIndicator() {
		if (this.busyIndicators == 0) {
			ScreenInfo.setVisible(true);
		}
		this.busyIndicators++;
		//#debug
		System.out.println("start busy indicator: Number of busy indicators: " + this.busyIndicators);
	}
}

In the above code we use some more features of J2ME Polish:

  1. Localization allows you both to customize and internationalize your app quickly with Locale.get("key") calls.
  2. ApplicationInitializer allows you to quickly show a splash screen before doing the real initialization of your app in a background thread.
  3. ScreenInfo allows you to overlay any shown screen with some additional information. In the above code we show a busy indicator.
  4. Persistence support eases the storage and reading of local data with the RmsStorage and similar classes.
  5. Logging allows you to quickly turn on and off logging levels for specific classes and that can be removed completely from the app. Log anything using the #debug preprocessing directive.

This UI class is very simple, it just displays a number of choices that can be selected. The code binds UI elements to CSS styles with the #style preprocessing directive. Behold of the mighty List:


Listing 3: MainMenuList.java

package de.enough.polish.app.view;

import de.enough.polish.ui.List;
import de.enough.polish.util.Locale;

public class MainMenuList extends List {

	/**
	 * Creates a new main menu screen.
	 */
	public MainMenuList() {
		//#style screenMainMenu
		super( Locale.get("main.title"), List.IMPLICIT);
	}
	
	/**
	 * Adds a main menu entry to this screen.
	 * @param name the name of the entry
	 */
	public void addEntry( String name ) {
		//#style itemMainMenuEntry
		append(name, null);
	}
}

The configuration uses the J2ME Polish serialization API to store and retrieve data. Using J2ME Polish this tedious task becomes quite simple really.
The configuration class specifies a version, so that later iterations can easily load previous versions. The dirty flag is used for determining when the configuration has changed and should be persisted again.

Listing 4: Configuration.java

package de.enough.polish.app.model;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import de.enough.polish.io.Externalizable;

public class Configuration implements Externalizable {

	/**
	 * The key under which a configuration is typically stored.
	 */
	public final static String KEY = "_cfg";
	/**
	 * Version for serialization.
	 */
	private final static int VERSION = 100;
	
	/**
	 * Dirty flag, indicates if the configuration has been changed since the last reset or load.
	 */
	private boolean isDirty;
	
	/**
	 * Just an example for a field.
	 */
	private String userName;
	
	/**
	 * Creates a new standard configuration.
	 */
	public Configuration() {
		//TODO specify default values
	}
	
	/**
	 * Checks if the configuration has been changed since the creation or the last reset.
	 * @return true when the configuration has been changed.
	 * @see #resetDirtyFlag()
	 */
	public boolean isDirty() {
		return this.isDirty;
	}
	
	/**
	 * Resets the dirty flag.
	 * @see #isDirty()
	 */
	public void resetDirtyFlag() {
		this.isDirty = false;
	}
	
	/**
	 * Retrieves the user name
	 * @return the user name
	 */
	public String getUserName() {
		return this.userName;
	}
	
	/**
	 * Sets the user name
	 * @param name the user name
	 */
	public void setUserName( String name ) {
		this.isDirty = true;
		this.userName = name;
	}

	public void write(DataOutputStream out) throws IOException {
		out.writeInt( VERSION );
		boolean notNull = (this.userName != null);
		out.writeBoolean( notNull );
		if (notNull) {
			out.writeUTF(this.userName);
		}
	}
	
	public void read(DataInputStream in) throws IOException {
		int version = in.readInt();
		if (version > VERSION) {
			throw new IOException("for invalid version " + version);
		}
		boolean notNull = in.readBoolean();
		if (notNull) {
			this.userName = in.readUTF();
		}
	}
}

Building the Application

To build the app with J2ME Polish we need to create the ${project.home}/build.xml script which controls the build process. This is a standard Ant file, so most IDEs support the editing of this file with syntax highlighting, auto completion etc. Don't worry if you have never worked with Ant before - although it can look quite scary at the beginning it is easy to master. The build.xml file is also included in the J2ME Polish sample apps, the main parts of it are shown in the next listing. To build the sample application, we need to right-click the build.xml within the IDE and select "Execute", "Run Ant..." or "make" depending on the IDE. The build process can also be started from the command-line: enter the project directory and type the ant command.

Hello Ant!

The build.xml script contains various targets, by default the j2mepolish target is invoked. You can select the targets in your IDE or on the command line by calling ant ${target.name}, e.g. ant emulator.

Each target can have dependencies, j2mepolish depends on the init target, for example:

<target name="j2mepolish" depends="init" >

Each dependency is called first when Ant executes the build.xml script. With targets, dependencies and properties you can master complex build scenarios. The important aspect about properties is that they can only be written once, subsequent changes are ignored. One example is the init target that sets the test property to false in the below code. The setting is ignored when calling the emulator target. This target depends on the enableEmulator and the j2mepolish targets. Due to the dependency of the j2mepolish target on the init target, following targets are called in this order:

  1. enableEmulator
  2. init
  3. j2mepolish

So the test property will be set in the enableEmulator target first to true and this value won't be changed by calling the init target later.

Learn more about Ant with one of these tutorials:

If there is one golden role in J2ME Polish, it surely is <property file="${user.name}.properties" />. With that simple line of code you can easily adjust the build process for local settings in a properties file called ${user.name}.properties, e.g. linust.properties. This properties file can contain several Ant properties each on a single line with the name and value separated by an equal sign: device=Generic/AnyMsaPhone.

build.xml Example



Listing 5: build.xml

<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="j2mepolish" name="enough-polish-example">

	<property  name="device" value="Generic/AnyPhone" />
	<property  name="devices" value="${device},Generic/AnyMsaPhone,BlackBerry/7.1,Android/4.0" />


	<property name="polish.home" location="C:\\Program Files\\J2ME-Polish2.3" />

	<taskdef 
		name="j2mepolish"
		classname="de.enough.polish.ant.PolishTask" 
		classpath="${polish.home}/lib/enough-j2mepolish-build.jar" 
	/>

		
	<target name="enableEmulator">
		<property name="test" value="true"/>
	</target>
		
	<target name="deploy">
		<property name="deploy-url" value="http://193.22.164.185:8080/"/>
	</target>
			
	<target name="init">
	  	<property name="test" value="false"/>
		<property name="deploy-url" value=""/>
	</target>
	    
	<target name="j2mepolish" depends="init" >
		<j2mepolish>
		    <!-- general settings -->
			<info 
				copyright="Copyright 2012 Enough Software. All rights reserved." 
				description="An application stub." 
				infoUrl="http://www.enough.de" 
				jarName="${polish.vendor}-${polish.name}-${polish.locale}-app.jar" 
				jarUrl="${deploy-url}${polish.jarName}" 
				name="App" 
				vendorName="Enough Software" 
				version="1.0.4"
		    />
			<!-- selection of supported devices -->
			<deviceRequirements if="test">
				<requirement name="Identifier" value="${device}"/>
			</deviceRequirements>
			<deviceRequirements unless="test">
				<requirement name="Identifier" value="${devices}"/>
			</deviceRequirements>
		    <!-- build settings -->
			<build usePolishGui="true"
				>
				<!-- midlets definition -->
				<midlet class="de.enough.polish.app.App" name="BlankApp" />
				<!-- project-wide variables - used for preprocessing  -->
				<variables>
					<variable
						name="polish.FullScreen"
						value="menu" 
						unless="polish.blackberry || polish.android" 
					/>
					<variable name="polish.TextField.useDirectInput" value="true" />
					<variable name="polish.TextField.supportSymbolsEntry" value="true" />
					<variable name="polish.MenuBar.useExtendedMenuBar" value="true" />
					<variable name="polish.useScrollBar" value="true" />
					<variable name="polish.ScreenInfo.enable" value="true" />
				</variables>
				<!-- obfuscator settings: do not obfuscate when the test-property is true -->
				<obfuscator name="ProGuard" unless="test">
					<keep class="de.enough.polish.example.Dummy" />
				</obfuscator>
				<!-- debug settings: only include debug setting when the test-property is true -->
				<debug if="test" level="error" verbose="true">
					<filter level="debug" package="de.enough.polish.sample.app"/>
					<filter level="info" pattern="de.enough.polish.ui.*"/>
					<filter level="debug" class="de.enough.polish.ui.Container" />
				</debug>
				<resources
					dir="resources/base"
					defaultexcludes="yes"
					excludes="readme.txt"
				>
					<root dir="resources/base" />
					<root dir="resources/base/images" />
					<root dir="resources/base/i18n" />
					<root dir="resources/base/style" />
					<localization>
						<locale name="de" encoding="UTF8"/>
					</localization>
				</resources>
			</build>
			<emulator if="test" />
		</j2mepolish>
	</target>


	<target name="clean">
		<delete dir="build"/>
		<delete dir="dist"/>
	</target>
	
	<target name="emulator" depends="enableEmulator,j2mepolish" />

	<target name="cleanbuild" depends="clean,j2mepolish" />

</project>

The J2ME Polish task itself contains 4 sections:

  • <info>: The <info> section contains information that's eventually written to the JAD and Manifest files.
  • <deviceRequirements> control the target devices. Depending on the target devices you can use different APIs in your project, for example. J2ME Polish creates optimized app version for each target device.
  • build controls the build process.
  • emulator launches the emulator after a successful build.

J2ME Polish now preprocesses, compiles, preverifies, obfuscates and packages the code automatically for your chosen target devices. The resulting app bundles can be found in the dist folder after J2ME Polish finished the build.

Some of the above configured J2ME Polish features include:

  1. Logging that allows you to quickly turn on and off logging levels for specific classes and that can be removed completely from the app. This is configured in the <debug> section.
  2. Resource assembling that allows you to choose platform and device specific resources. It also allows you to customize your app easily.
  3. Localization allows you to translate your app and customize the used texts.

Target Devices

The J2ME Polish device database contains well over a thousand devices nowadays. Typically you will not build your app for for all of them, but instead concentrate on some key devices.

  • Generic/AnyPhone: A MIDP 2.0 and CLDC 1.1 phone that supports softkeys and the fullscreen mode and the JTWI standard. Use this phone to run a single application on various platforms like Nokia, Motorola, Samsung and so on.
  • Generic/AnyMsaPhone: Similar to Generic/AnyPhone, but it additionally guarantees to support more MSA APIs like the advancedmultimedia-API.
  • BlackBerry/7.1: A BlackBerry 7.1 compatible device
  • BlackBerry/7.0: A BlackBerry 7.0 compatible device
  • BlackBerry/6.0: A BlackBerry 6.0 compatible device
  • BlackBerry/5.0: A BlackBerry 5.0 compatible device
  • Generic/android4.0.3: An Android 4.0.3 / API-Level 15 compatible handset.
  • Generic/android4.0: An Android 4.0 / API-Level 14 compatible handset.
  • Generic/android2.3.3: An Android 4.0.3 / API-Level 10 compatible handset.

Designing the Application with CSS

Design your apps outside of your source code using CSS code that is similar to web CSS. Using CSS has some advantages over code based design:

  • It's flexible: you can start with a simple design and then improve iteratively independent of your source code and without needing to change your source code.
  • It's simpler: instead of creating complex code constructs to animate something, you just use some CSS text.

Having said that you can still use code based design with J2ME Polish by creating and registering instances of de.enough.polish.ui.Style classes - however usage is much simpler on the CSS level.

J2ME Polish parses the CSS files during build time and creates source code from it, since this is the most efficient way to access design information. You can also consume simple CSS during runtime using de.enough.polish.browser.css.CssInterpreter, HtmlBrowser or the html text-effect.

You bind UI elements to CSS styles with the #style preprocessing directive in your Java code.

By default the polish.css file is located in the resources folder of your project. You can configure other resource folder using the <resources> section within the <j2mpepolish> task.

Listing 6: polish.css

colors {
	fontColor: #ccc;
	focusedFontColor: #000;
	pressedFontColor: #666;
	bgColor:  #222;
	focusedBgColor:  #ee4;
	borderColor: fontColor;
	focusedBorderColor: focusedFontColor;
	screenBackgroundColor: #333;	
}

/**
  * The title style is a predefined style which is used
  * for all screen-titles unless you specify a 'title-style' attribute within a screen style.
  */
title {
	padding: 2px;
	font-size: large;
	font-style: bold;
	font-color: fontColor;
	background-color: bgColor;
	border: none;
	layout: horizontal-center | horizontal-expand;
	layout: center | vertical-center | expand;
	min-height: imageheight( busy01.png );
}

/*********************   MAIN MENU   ******************************************************************************************************/
.screenMainMenu {
	background-color: screenBackgroundColor;
	layout: vertical-center;
}

.itemMainMenuEntry {
	margin-left: 5%;
	margin-right: 5%;
	font-color: fontColor;
	layout: expand | center;
}

.itemMainMenuEntry:hover {
	background-color: focusedBgColor;
	font-color: focusedFontColor;	
}

.itemMainMenuEntry:pressed {
	background-color: focusedBgColor;
	font-color: pressedFontColor;	
}


/*********************   SCROLL BAR   ******************************************************************************************************/
/* You need to activate the scrollbar by setting the "polish.useScrollBar" variable to "true" in your build.xml script. */
scrollbar {
	scrollbar-slider-color: #333;
	scrollbar-slider-width: 3;
	/* the scrollbar should be faded out when not used: */
	scrollbar-fadeout: true;
	opacity: 80%;
}

/*********************   BUSY INDICATOR   ******************************************************************************************************/
.busyGauge {
	padding-top: 9px;
	padding-left: 1px;
	view-type: gauge-images;
	gauge-images-sources: busy01.png,busy02.png,busy03.png,busy04.png,busy05.png,busy06.png,busy07.png,busy08.png,busy09.png,busy10.png,busy11.png,busy12.png;
	gauge-images-interval: 100;
}


/*********************   MENU BAR     *******************************************************************************************************/
/* You need to activate the extended menubar that allows this finetuned design by setting the 
   "polish.MenuBar.useExtendedMenuBar" variable to "true" in your build.xml script. */

menubar
{
	padding-top: 3px;
	padding: 2px;
	background-color: bgColor;
}

menu {
	margin-left: 2px;
	margin-right: 2px;
	min-width: 60%;
	padding: 2px;
	background {
		type: round-rect;
		color: bgColor;
	}
}

/** Style for the first layer of subcommands: **/
menu1 extends menu {
	background-color: argb(150, 255, 255, 0 );
	background-type: simple;
      layout: right;
      margin-bottom: 4px;
      margin-right: 4px;
}


/**
  * The menuItem style is a predefined style
  * for the actual commands in a fullscreen-menu.
  * When the menuItem style is not defined,
  * the menu style will be used instead. 
  */
menuItem {
	margin-top: 2px;
	padding: 2px;
	padding-left: 5px;
	font {
		color: focusedBgColor;
		size: medium;
		style: bold;
	}
	layout: left;
	/**
	 * Yes, it's possible to use preprocessing within CSS files. Use any preprocessing
	 * variables like polish.midp2 or polish.api.nokia-ui. You can also access resource
	 * information using these property functions:
	 * - exists( resourcename ): checks whether a resource is defined for the current target device
	 * - imagewidth( imagename ): retrieves the width of the given image
	 * - imageheight( imagename ): retrieves the height of the given image
	 */
	//#if ${ exists( arrowRight.png ) }
		command-child-indicator: url(arrowRight.png);
	//#else
		command-child-indicator-color: blue;
		command-child-indicator-width: 8;
		command-child-indicator-height: 10;
	//#endif
}

.menuItem:hover {
	background-color: focusedBgColor;
	font-color: focusedFontColor;
	layout: left | horizontal-expand;
}

.menuItem:pressed {
	background-color: focusedBgColor;
	font-color: pressedFontColor;
	layout: left | horizontal-expand;
}

leftcommand
{
	margin: 0px; /* default */
	padding-top: 3px;
	padding-bottom: 0px;
	font-color: fontColor;
	font-style: bold;
	layout: left;
}

leftcommand:pressed {
	font-color: focusedBgColor;
}

rightcommand extends leftcommand
{
	layout: right;
}

rightcommand:pressed {
	font-color: focusedBgColor;
}

Learn more CSS here:

  • Learn more about CSS styling options with J2ME Polish in the CSS Basics section.
  • The Visual Guides provide additional information about screen-, item- or effect-specific CSS attributes.
  • Use CSS media queries to adapt your design dynamically during runtime.
  • Animate CSS to create great & simple effects.

Optimize for Different Devices and Platforms

You can adapt your app for different platforms or device features in J2ME Polish with following methods:

Preprocess your Source Code

Preprocessing changes your Java and CSS source code before it gets compiled. You will typically use preprocessing to either detect and react to device capabilities during build time or to detect and react to design settings or resources during build time.

Some examples:

When an API is required for a specific functionality, just use the polish.api.[api-name] symbol, which is defined for each supported API:

//#if polish.api.mmapi
   // this device supports the Mobile Media API
   [...]
//#else
   // this device does not support the mmapi
   [...]
//#endif

You can differentiate between MIDP versions with the "polish.midp1" and the "polish.midp2" symbol:

//#if polish.midp2
   // this device supports MIDP/2.0 standard
   [...]
//#else
   // this support does support the MIDP/1.0 standard
   [...]
//#endif

Check specific plaforms by using the polish.JavaPlatform preprocessing variable:

//#if polish.blackberry
	// we run on a BlackBerry Java platform
	//#if polish.JavaPlatform >= BlackBerry/6.0
	   // we run on BlackBerry 6.0 or higher
	   [...]
	//#endif
//#elif polish.JavaPlatform >= Android/2.3.3
	// we run on Android 2.3.3 or higher
//#endif

You can combine several requirements as well:

//#if polish.api.mmapi || polish.midp2
   // this device supports at least the basic Mobile Media API
   [...]
//#endif

You can also check out resources with property functions either in your code in the polish.css file.

In the following example we just add the smiley markup code when there is actually an image called emoticon_cry.png:

//#if ${exists(emoticon_cry.png)}
	addMarkup(new ReplacementMarkup(":-(", "<img src=\"/emoticon_cry.png\"/>"));
//#endif

And in this CSS code we adjust the left padding and minimum height of the title style to the width and height of the titleicon.png image. This results in the title text being rendered next to the image, not on top of it.

backgrounds {
	titleBgIcon {
		type: image;
		image: url( titleicon.png );
		anchor: left | vcenter;
		x-offset: 2px;
		color: transparent;
	}
	titleBgGradient {
		type: vertical-gradient;
		top-color: black;
		bottom-color: #333;
	}
}

title {
	padding-left: ${imagewidth(titleicon.png)} + 4;
	background {
		type: combined;
		foreground: titleBgIcon;
		background: titleBgGradient;
	}
	font-color: #eee;
	layout: expand | vertical-center | left;
	min-height: ${imageheight(titleicon.png)} + 4;
}

Use the #condition preprocessing variable to exclude or include a complete Java source file. This is useful to use platform or API specific code without needing to resort to preprocessing within the file. By convention the #condition statement should be placed at the beginning of the file.

//#condition polish.android
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.DialogInterface;
import android.content.Intent;

public class MyActivity extends Activity {
	[...]
}

Preprocessing variables typically derrive from the Device Database, but you can define them in the build.xml script or in property files as well:

<variables>
   <variable name="update-url" value="http://www.enough.de/update" />
</variables>

The defined variable can be used in the source code with the "#="-directive:

//#ifdef update-url:defined
   //#= public static final String UPDATE_URL = "${ update-url }";
//#else
   // no variable definition was found, use a default-value:
   public static final String UPDATE_URL = "http://default.com/update";
//#endif

Preprocessing variables are also used to configure the J2ME Polish Lush UI. You can find configuration options in the Visual Guide.

Find more information about preprocessing:

Use Platform Specific Source Folders

Similar to the #condition preprocessing variable you can configure J2ME Polish to use different source folders depending on your chosen conditions. This allows you to have clean source code free of processing statements that still can contain platform or API specific code.

<sources>
	<source dir="source/src" />
	<source dir="source/android" if="polish.android" />
	<source dir="source/midp" unless="polish.android" />
</sources>

The <sources> section is part of the <build> section in your build.xml script.

Load Classes Dynamically

Loading classes dynamically is the "traditional" way for adapting Java code to different situations. It typically works like this:

  • Define an interface to access functionality in a defined way.
  • Implement that interface in a number of specific subclasses.
  • Detect during runtime what class you would like to load.
  • Load and use the class.

There are also two additional steps for J2ME Polish apps:

  • Make sure to target devices that correspond to your used APIs or exclude classes during build time using the #condition preprocessing directive.
  • Exclude the classes from obfuscation by using the #dontobfuscate proprocessing directive.

Let's look at a specific example - we try to use some vendor specific APIs for dynamically switching on the screen's background light. Note that this functionality is actually part of the J2ME Polish UI - the DeviceControl class uses class loading when targeting a generic device such as Generic/AnyPhone or Generic/AnyMsaPhone (or uses vendor specific APIs directly when targeting a more specific phone).

First let's define an interface to enable or disable the background light:

//#condition polish.HasOptionalApis
package de.enough.polish.util.devicecontrol;

/**
 * Controls a device
 */
public interface DeviceController {
	
	/**
	 * Turns the backlight on on a device until lightOff() is called
	 * 
	 * @return true when backlight is supported on this device.
	 * @see #lightOff()
	 */
	boolean lightOn();
	
	/**
	 * Turns the backlight off
	 * @see #lightOn()
	 */
	void lightOff();
	
	/**
	 * Checks if backlight can be controlled by the application
	 * 
	 * @return true when the backlight can be controlled by the application
	 */
	boolean isLightSupported();
}

Note that in the interface we already use the polish.HasOptionalApis preprocessing condition. We use this to exclude this file when targeting more specific devices - or to use dynamic class loading only when required. This preprocessing symbol is set automatically when the device has the OptionalPackage capability defined (e.g. in custom-devices.xml):

<capability name="OptionalPackage" value="nokia-ui,samsung" />

The NokiaDeviceController is an implementation of the DeviceController that uses the Nokia-UI API:

//#condition polish.optional-api.nokia-ui

//#dontobfuscate

package de.enough.polish.util.devicecontrol;

import com.nokia.mid.ui.DeviceControl;

public class NokiaDeviceController 
implements DeviceController, Runnable 
{

	Object lightsLock = new Object();
	private boolean isLightOn = false;
	
	public boolean lightOn() {
		synchronized (this.lightsLock) {
			if (isLightSupported()) {
				if (!this.isLightOn) { 
					this.isLightOn = true;
					Thread t = new Thread(this);
					t.start();
				}
				return true;
			}  else {
				return false;
			}
		}
	}

	public void lightOff() {
		synchronized(this.lightsLock ) {
			this.isLightOn = false;
			DeviceControl.setLights(0,0);
		}
	}

	public boolean isLightSupported() {
		return true;
	}

	/**
	 * Keeps the backlight on until lightOff() is being called.
	 */
	public void run() {
		int displaytime = 10000;
		long sleeptime = (displaytime * 90) / 100;
		boolean increaseAfterFirstLoop = true;
		while(this.isLightOn)
		{
			DeviceControl.setLights(0,0);
			DeviceControl.setLights(0,100);
			try {
				Thread.sleep(sleeptime);
			} catch (InterruptedException e) {
				// ignore
			}
			if (increaseAfterFirstLoop) {
				increaseAfterFirstLoop = false;
				displaytime = 20000;
				sleeptime = 18000;
			}
		}
	}

}

In the code above we use the #condition preprocessing directive to ensure that this file is only added to the compilation when the device could support the Nokia UI API:

//#condition polish.optional-api.nokia-ui

When the file is included into compilation, we use the #dontobfuscate directive to ensure that the NokiaDeviceController is excluded from obfuscation.

Now let's load the class dynamically:

String[] classNames = new String[] {
	"de.enough.polish.util.devicecontrol.NokiaDeviceController",
	"de.enough.polish.util.devicecontrol.SamsungDeviceController",
	"de.enough.polish.util.devicecontrol.LgDeviceController"
};
for (int i = 0; i < classNames.length; i++) {
	String className = classNames[i];
	try {
		DeviceController control = (DeviceController) Class.forName(className).newInstance();
		// okay, this class could be loaded
		this.controller = control;
		break;
	} catch (Exception e) {
		// ignore, try next class
	}
}

In the above code we use the class loading mechanism itself to detect if the current device supports a specific API. Another way would be to probe for System properties or use the de.enough.polish.util.DeviceInfo helper class.

CSS media queries

You can use CSS media queries to adapt the design of your application during runtime. A common use case is to structure the design of your app into 3 dimensions:

  1. low res,
  2. medium res,
  3. high res

and to load the corresponding images with CSS media queries, like this:

/** default style: **/
.mainScreen {
	background {
		image: url( bg-default.png );
		anchor: bottom | right;
	}
}

/** adapt styles for specific resolutions: */
@media (min-device-width: 240px) and (min-device-height: 320px) {
	.mainScreen {
		background {
			image: url( bg-large.png );
			anchor: bottom | right;
		}
	}
}
@media (max-device-width: 176px) and (max-device-height: 208px) {	
	.mainScreen {
		background {
			image: url( bg-small.png );
			anchor: bottom | right;
		}
	}
}
/* HVGA resolution (portrait, landscape) */
@media 	(max-width: 320px) and (max-height: 480px), (max-width: 480px) and (max-height: 320px) {
	.mainScreen {
		background {
			image: url( bg-hvga.png );
			anchor: bottom | right;
		}
	}
}