Showing posts with label adaptive. Show all posts
Showing posts with label adaptive. Show all posts

Thursday, April 28, 2011

keyReleased not called

Do not take for granted that a call to keyPress will have a keyReleased counterpart.
Yes, Java ME VMs may not call it and the simple tip here is to not rely on keyReleased at all.

One exception, however, is when you have a very closed set of target devices and can do manual tests on all of them.
Do I need to say to NOT trust an emulator execution?

Even in this situation, when would you use keyReleased instead of keyPressed?
Lets say you have a very nice custom UI with buttons, where they can be in one of the following states:


  • Unselected: focus is not at this UI element

  • Selected: got focus

  • Pressed


My personal experience is that the pressed state is hard to perceive because users do not care about it.
They want to get things done. The faster the better.

So, instead of wasting jar size with code (or worst, images!) to draw a pressed state, run everything you need from keyPressed alone.

Of course, there are other usages of keyReleased, for example, one-button-games (OBGs), where the time between the key press and the key release might be very important to the game mechanics.
But even in these cases we can use only key press.

Most OBGs only have two states:


  • Doing: jump, fire, turn, whatever

  • Not doing


And the time doing is measured with the time between a key press and a key release.

To use only key presses at this situation you have to start the doing/action with a press and keep doing/acting until another press happens.
The change to the game mechanics is subtle and you can reach more devices with less code changes.

I had to review some of my components that relied on keyReleased to stop a key repetition, so can you.

Related topics:

Wednesday, April 27, 2011

hasPointerEvents wrong return

Touch enabled handsets are more and more common these days. Some does not even have a keyboard!

Custom User Interfaces should adapt to touch screens and, at least, display with bigger selectable items.

One easy way to know if your MIDlet is running on a touch enabled handset is to call Canvas.hasPointerEvents.

Unfortunately some Java ME Virtual Machines do not return true even if the handset DOES have a touch screen.

One workaround is to have a boolean attribute and set it to true if pointer pressed or released events are triggered:


class C extends Canvas {
boolean pointerEvents;
protected void pointerPressed(int x, int y) {
pointerEvents = true;
// treat the pointer pressed event
// ...
}
protected void pointerReleased(int x, int y) {
pointerEvents = true;
// treat the pointer released event
// ...
}
public boolean hasPointerEvents() {
return pointerEvents || super.hasPointerEvents();
}
}

This way the rest of your application UI might correctly adapt and present bigger items.

Related topics:

Sunday, April 17, 2011

sizeChanged not called

To achieve the Single Jar theory we must deal with handsets fragmentation upfront.
One special piece of these fragments is the orientation change.

Screen orientations are: portrait (height > width) and landscape (width > height).

When the change happens the Java Virtual Machine should notify the running MIDlet with a call to sizeChanged method on the currently displayed Canvas.
Unfortunately this is not guaranteed to happen.

To make sure your application is always presented the right way you can store the last sizeChanged parameter values on attributes and check them on paint method.
Check the code below:


class C extends Canvas {
int lastWidth, lastHeight;
protected void sizeChanged(int w, int h) {
lastWidth = w;
lastHeight = h;
// adjust your user interface to the
// new width and height
// ...
}
protected void paint(Graphics g) {
if (super.getWidth() != lastWidth
|| super.getHeight() != lastHeight) {
sizeChanged(super.getWidth(), super.getHeight());
}
// paint your user interface
// ...
}
}

Related topics:

Friday, December 10, 2010

Touch Menu

Cros-posted at Technè - Blog de Tecnologia do C.E.S.A.R.

Continuing with the details on how to develop touch enabled Java ME applications, today we will see a way of using such screens as input: if the user touchs an area it will trigger an action, like a menu.

This menu will also adapt to screen orientation.

First lets create a class to represent a menu entry. It knows how to paint the entry and stores a Command to be delivered when the user touchs the area:

public class TouchItem {

Command command;

public TouchItem(Command command) {
this.command = command;
}

// for this to work it is necessary that
// Graphics origin to be translated, so it
// is on the top left corner of the item,
// and that the clipping area has the
// width and height of the item
public void paint (Graphics g) {
g.drawString(command.getLabel(),
g.getClipWidth() / 2, g.getClipHeight() / 2,
Graphics.HCENTER | Graphics.BASELINE);
g.drawRect(0, 0, g.getClipWidth(), g.getClipHeight());
}
}

Then we create a class responsible for painting all menu entries. These first two classes can also be used to implement board games like Chess, for example.
public class TouchGrid {

private final TouchItem items[][];
private int itemWidth, itemHeight;
private int x, y;
private int nextLine, nextColumn;

public TouchGrid(int x, int y, int width, int height, int columns, int lines) {
this.items = new TouchItem[columns][lines];
setRectangle(x, y, width, height);
}

public void setRectangle (int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.itemWidth = width / this.items.length;
this.itemHeight = height / this.items[0].length;
}

// fill all columns in a line before moving to
// the next line. Calling this method four times
// will end up with the following menu:
// [ 1, 2 ]
// [ 3, 4 ]
// on a grid with two columns and two lines
public void add (TouchItem item) {
this.items[nextColumn][nextLine] = item;
nextColumn = (nextColumn + 1) % items.length;
if (nextColumn == 0) {
nextLine = (nextLine + 1) % items[0].length;
}
}

public void paint (Graphics g) {
Clip clip = new Clip(g);
int itemX = x;
for (int i = 0; i < items.length; i++) {
int itemY = y;
for (int j = 0; j < items[i].length; j++) {
if (items[i][j] != null) {
// needed by TouchItem to
// paint itself properly
g.setClip(itemX, itemY, itemWidth, itemHeight);
g.translate(itemX, itemY);
items[i][j].paint(g);
// reset Graphics
g.translate(-g.getTranslateX(), -g.getTranslateY());
clip.applyTo(g);
}
itemY += itemHeight;
}
itemX += itemWidth;
}
}

// here we map the point pressed to an item
public TouchItem getTouchItemAt (int x, int y) {
if (x < this.x || y < this.y) {
return null;
}

int i = (x - this.x) / this.itemWidth;
int j = (y - this.y) / this.itemHeight;

if (i >= items.length || j >= items[0].length) {
return null;
}

return items[i][j];
}
}

At paint method above we used below class to store the clip information and restore it:
public class Clip {

private int x, y;
private int width, height;

public Clip (Graphics g) {
x = g.getClipX();
y = g.getClipY();
width = g.getClipWidth();
height = g.getClipHeight();
}

public void applyTo (Graphics g) {
g.setClip(x, y, width, height);
}
}

We need a class that extends Canvas to paint the grid. It will also initialize the grid with the menu options:
public class TouchMenuCanvas extends Canvas {

private TouchGrid touchGrid;
private CommandListener commandListener;

public TouchMenuCanvas () {
final int columns = 2;
final int lines = 3;
int margin = getWidth() / 25;
// 2 = left and right margins
int width = getWidth() - (2 * margin);
// 2 = top and bottom margins
int height = getHeight() - (2 * margin);
touchGrid = new TouchGrid(margin, margin, width, height, columns, lines);
for (int i = 0; i < lines * columns; i++) {
Command c = new Command("item " + (i+1), Command.SCREEN, 1);
touchGrid.add(new TouchItem(c));
}
}

// when orientation changes we need to update
// grid rectangle
protected void sizeChanged(int width, int height) {
int margin = width / 25;
width = width - (2 * margin);
height = height - (2 * margin);
touchGrid.setRectangle(margin, margin, width, height);
}

// we need to override this method because there
// is no getCommandListener on API
public void setCommandListener(CommandListener listener) {
super.setCommandListener(listener);
commandListener = listener;
}

protected void paint(Graphics g) {
g.setColor(0xffffff); // white
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(0); // black
this.touchGrid.paint(g);
}

protected void pointerPressed(int x, int y) {
TouchItem item = this.touchGrid.getTouchItemAt(x, y);
if (item != null) {
commandListener.commandAction(item.command, this);
}
}
}

The last piece is a MIDlet to show the Canvas:
public class TouchMenuMIDlet extends MIDlet implements CommandListener {

TouchMenuCanvas menuCanvas;

public TouchMenuMIDlet() {
menuCanvas = new TouchMenuCanvas();
menuCanvas.setCommandListener(this);
menuCanvas.addCommand(new Command("Exit", Command.EXIT, 1));
}

public void commandAction(Command c, Displayable d) {
if (c.getCommandType() == Command.EXIT) {
this.notifyDestroyed();
} else {
System.out.println(c.getLabel());
}
}

protected void destroyApp(boolean unconditional) {
}

protected void pauseApp() {
}

protected void startApp() throws MIDletStateChangeException {
Display.getDisplay(this).setCurrent(menuCanvas);
}

}

Below is the application running on DefaultCldcPhone1 of Java Platform Micro Edition SDK 3.0 on both orientations.








We hope this helps. See you next time.

Thursday, December 9, 2010

Touch Screen support

Cros-posted at Technè - Blog de Tecnologia do C.E.S.A.R.

More and more cell phones have touch screens. And when we hear about them the first thing that we think are the most expensive smartphones. Big screens and amazing computer power.

But there are also feature phones with such screens. And amongst them there is a lot supporting Java ME. So, how do you enable an application to get the user's "point"?

Since MIDP 1.0 Canvas already had the following callback methods: pointerPressed, pointerDragged and pointerReleased. And for information there were also hasPointerEvents and hasPointerMotionEvents.

TIP: SDK 3.0 emulator already have touch support, but if you are using WTK 2.5 go to wtklib\devices\DefaultColorPhone\DefaultColorPhone.properties and change the value of touch_screen to true.

To better understand these pointer methods here is a simple example. A Canvas where we can draw freely. It bevahes like Microsoft Paint pencil.

public class TouchCanvas extends Canvas {
// starting point of the line drawn at paint method
int x1 = -1, y1 = -1;
// line final point
int x2, y2;
// flag used to know when to clear the screen
boolean clear = true;

protected void pointerPressed(int x, int y) {
x1 = x;
y1 = y;
}
protected void pointerReleased(int x, int y) {
x1 = y1 = -1;
}
protected void pointerDragged(int x, int y) {
x2 = x;
y2 = y;
repaint();
}

protected void paint(Graphics g) {
if (clear) {
g.setColor(0xffffff); // white
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(0); // black
clear = false;
}

if (x1 >= 0 && y1 >= 0) {
g.drawLine(x1, y1, x2, y2);
x1 = x2;
y1 = y2;
}
}
}

The MIDlet below shows the Canvas and give the user a option to clear the screen.

public class TouchMIDlet extends MIDlet implements CommandListener {

TouchCanvas touchCanvas = new TouchCanvas();

public TouchMIDlet() {
touchCanvas.addCommand(new Command("Clear", Command.OK, 1));
touchCanvas.addCommand(new Command("Exit", Command.EXIT, 1));
touchCanvas.setCommandListener(this);
}

protected void destroyApp(boolean unconditional) { }

protected void pauseApp() { }

protected void startApp() {
Display.getDisplay(this).setCurrent(touchCanvas);
}

public void commandAction(Command c, Displayable d) {
if (c.getCommandType() == Command.EXIT) {
notifyDestroyed();
} else {
touchCanvas.clear = true;
touchCanvas.repaint();
}
}
}

For a more complex sample lets change two classes from Bar Chart to add touch dragging. First we add the following method to BarChart class:
public int getBarWidth (Font font) {
return font.stringWidth(widestName);
}

Now we add the following code to BarChartCanvas:
private int lastX, lastY;
protected void pointerPressed(int x, int y) {
lastX = x;
lastY = y;
}
protected void pointerDragged(int x, int y) {
int w = barChart.getBarWidth(Font.getDefaultFont());
// dragged a distance bigger than the bars width
if (lastX - x >= w || lastY - y >= w) {
barChart.nextBar();
repaint();
lastX = x;
} if (x - lastX >= w || y - lastY >= w) {
barChart.previousBar();
repaint();
lastX = x;
}
}

The smaller the bars be smoother the movement will be. This code also works on landscape mode (SDK 3.0 Emulator: View .. Orientation .. 270).

Another common action is the long press. As we do not have a callback method for it we need to count the time the user is pressing and not dragging or releasing. Below is a way to change our TouchCanvas:

private Timer timer;
protected void showNotify() {
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
// long press x and y
int lpx = -1, lpy = -1;
public void run() {
if (x1 >= 0) {
if (lpx == x1 && lpy == y1) {
lpx = lpy = -1;
System.out.println("long press");
} else {
lpx = x1;
lpy = y1;
}
}
}
},
// starting now and checking at every second
0, 1000);
}
protected void hideNotify() {
timer.cancel();
timer = null;
}

We hope this helps. See you next time.

Related topics:

Tuesday, December 7, 2010

Bar Chart

Cros-posted at Technè - Blog de Tecnologia do C.E.S.A.R.

When developing a mobile application one must consider screen size fragmentation. If Apple have only two screen sizes for iPhone until now, this is the exception. All other manufacturers vary the screen sizes a lot.

To make developers life easier each technology provides widgets and ways to combine them. But there are times when you need an specific look and fell. What to do? Low level graphics. 2D, 3D, does not matter.

Even if you only have one target screen size it is a good practice to code using proportions. Avoid absolute values of x, y, width and height. Here we show how to do this with a BarChart and Java ME.

It is good to point that there already are components that do this. Our main point here is to present good low level visual programming.

First thing is to have a class for our data.


class ChartEntry {
String name;
float value;
public BarChartEntry(String name, float value) {
this.name = name;
this.value = value;
}
}

And another class to draw the data. Values on top of the bars and labels at the bottom. But if an entry has negative value we do the opposite and draw the value at the bottom.

public class BarChart {
private Vector entries = new Vector();
private float maxValue;
private float minValue;
private String widestName = "";
private int firstBar;

// Every time an entry is added we check for the limits
public void addEntry(BarChartEntry entry) {
entries.addElement(entry);
if (maxValue < entry.value) {
maxValue = entry.value;
}
if (minValue > entry.value) {
minValue = entry.value;
}
if (widestName.length() < entry.name.length()) {
widestName = entry.name;
}
}

public void nextBar () {
this.setFirstBar(this.firstBar +1);
}
public void previousBar () {
this.setFirstBar(this.firstBar -1);
}
private void setFirstBar(int firstBar) {
if (firstBar >= 0 && firstBar < this.entries.size())
this.firstBar = firstBar;
}

// similar to Graphics.drawRect.
// x and y is the top left corner of a rectangle
// w is for width and h is for height
public void draw(Graphics g, int x, int y, int w, int h) {
Font font = g.getFont();
// 2 = value and label strings drawn at top and bottom
int availableHeight = h - (2 * font.getHeight());
int barWidth = font.stringWidth(widestName);
// empty space between bars
int barMargin = w / 25;
int maxBars = (int) Math.floor(w / (barWidth + barMargin));
float valuesRange = maxValue - minValue;
int barOrigin = y + h - font.getHeight();
if (minValue < 0) {
// move origin line up
barOrigin = barOrigin
+ (int) ((availableHeight * minValue) / valuesRange);
}

g.drawLine(x, barOrigin, x + w, barOrigin);

// draw chart entries
BarChartEntry drawEntries [] = new BarChartEntry[this.entries.size()];
int barX = x;
int barCount = 0;
this.entries.copyInto(drawEntries);
for (int i = 0; i < maxBars && this.firstBar + i < drawEntries.length; i++) {
BarChartEntry chartEntry = drawEntries[this.firstBar + i];
// we get current bar height with a simple rule of three
// availableHeight -> valuesRange
// barHeight -> chartEntry.value
int barHeight = (int) ((availableHeight * chartEntry.value) / valuesRange);
int textCenterX = barX + barMargin + (barWidth / 2);
if (barHeight > 0) {
g.drawString(chartEntry.name, textCenterX, barOrigin,
Graphics.HCENTER | Graphics.TOP);
g.fillRect(barX + barMargin, barOrigin - barHeight, barWidth,
barHeight);
g.drawString(String.valueOf(chartEntry.value), textCenterX,
barOrigin - barHeight, Graphics.HCENTER
| Graphics.BOTTOM);
} else {
g.drawString(chartEntry.name, textCenterX, barOrigin,
Graphics.HCENTER | Graphics.BOTTOM);
g.fillRect(barX + barMargin, barOrigin, barWidth, -barHeight);
g.drawString(String.valueOf(chartEntry.value), textCenterX,
barOrigin - barHeight, Graphics.HCENTER | Graphics.TOP);
}
barX = barX + barMargin + barWidth;
barCount++;
}
}
}

Below is a Canvas that shows a bar chart and changes the first bar shown according to pressed keys.

public class BarChartCanvas extends Canvas {

BarChart barChart = new BarChart();

public BarChartCanvas() {
barChart.addEntry(new BarChartEntry("J", 8.0f));
barChart.addEntry(new BarChartEntry("FE", 10.0f));
barChart.addEntry(new BarChartEntry("MAR", 6.0f));
barChart.addEntry(new BarChartEntry("ABRI", 9.0f));
barChart.addEntry(new BarChartEntry("MAIO", -2.0f));
barChart.addEntry(new BarChartEntry("JUNHO", -4.0f));
barChart.addEntry(new BarChartEntry("JULHO", 6.0f));
barChart.addEntry(new BarChartEntry("AGOSTO", 9.0f));
}

protected void paint(Graphics g) {
g.setColor(0xffffff);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(0);
barChart.draw(g, 0, 0, getWidth(), getHeight());
}

protected void keyPressed(int keyCode) {
switch (keyCode) {
case Canvas.KEY_NUM2:
case Canvas.KEY_NUM4:
barChart.previousBar();
break;
case Canvas.KEY_NUM6:
case Canvas.KEY_NUM8:
barChart.nextBar();
break;
default:
switch (super.getGameAction(keyCode)) {
case Canvas.LEFT:
case Canvas.UP:
barChart.previousBar();
break;
case Canvas.RIGHT:
case Canvas.DOWN:
barChart.nextBar();
break;
}
break;
}
this.repaint();
}
}

And the MIDlet used to show this Canvas.

public class BarChartMIDlet extends MIDlet implements
CommandListener {

BarChartCanvas chartCanvas = new BarChartCanvas();

public BarChartMIDlet() {
chartCanvas.addCommand(new Command("Exit", Command.EXIT, 1));
chartCanvas.setCommandListener(this);
}

protected void destroyApp(boolean unconditional) { }

protected void pauseApp() { }

protected void startApp() {
Display.getDisplay(this).setCurrent(chartCanvas);
}

public void commandAction(Command c, Displayable d) {
this.notifyDestroyed();
}

}

Below are screens of this application running on DefaultCldcPhone1 emulator of Java Platform Micro Edition SDK 3.0.






As expected in landscape mode we can draw one more bar. We can change the screen orientation with menu View .. Orientation.
We hope this helps.

Saturday, November 13, 2010

XML Data Binding

Cross-posted (in Portuguese) at Technè - Blog de Tecnologia do C.E.S.A.R.

It is an easy task to load XML data as Java Objects if you are using SE or EE versions. You can use, for example, JAXB or Castor. Is it possible to do it as easily on Java ME? Yes, let me show you how.

The first restriction we have to face is the reduced number of reflection features available on CLDC. It is not possible to use constructors with parameters or call methods. And we should avoid using Class.forName, because we would need to add exceptions to the obfuscator.

This proposal uses two classes to implement the Data Binding Unmarshall. One to represent each tag found and the other to deal with XML parsing details. The code of the first classe is:


public class XMLTag {
// if you do not have enough memory, use lazy
// instantiation on these attributes
private Hashtable attributes = new Hashtable();
private Vector childs = new Vector();

public void setAttributeValue(String attribute, String value) {
if (attribute != null && value != null) {
attributes.put(attribute, value);
}
}
public String getAttributeValue (String attribute) {
return (String) attributes.get(attribute);
}

public void addChild (XMLTag child) {
childs.addElement(child);
}
public Enumeration getChilds () {
return childs.elements();
}
public XMLTag getChildAt (int index) {
return (XMLTag) childs.elementAt(index);
}
}

Lets take an RSS version 2.0 XML sample:

<rss version="2.0">
<channel>
<item>
<title>item title</title>
<link>http://site.com</link>
</item>
<item>
<title>item title 2</title>
<link>http://site.com/2</link>
</item>
</channel>
</rss>

Below are the necessary classes to deal with above tags:

class RSS extends XMLTag {
Channel channel;
public void addChild(XMLTag child) {
if (child instanceof Channel) {
channel = (Channel) child;
}
}
}
class Channel extends XMLTag {
public void addChild(XMLTag child) {
if (child instanceof Item) {
super.addChild(child);
}
}
}
class Item extends XMLTag {
}

We do not need to create classes for title and link tags because these tags are considered attributes of item. Their values will be stored at XMLTag.attributes.

To prevent the use of Class.forName we use a map with tags and classes. The key is a String with the tag name and the value is a Class that extends XMLTag. This map will be passed as parameter to the class responsible for the XML parsing. Below is our map:


Hashtable map = new Hashtable();
map.put("rss", RSS.class);
map.put("channel", Channel.class);
map.put("item", Item.class);

The chosen parser is SAX from JSR 172. It notifies a Handler every time it finds an XML snippet. At the tag begin it calls startElement, at the tag end it calls endElement and when it finds text it calls characters.

A Stack is used to pile each tag found during the parsing. This way, when rss tag is found an RSS instance is added to the Stack. Below are the Stack changes until we find item tag:

As title tag does not have a corresponding Class it is treated as an attribute of the tag at the top of the Stack. The value we receive at characters method is saved in a buffer until we find the end of title tag. When we reach the end of title tag we store the value of buffer using XMLTag.setAttributeValue.

When we reach the end of a mapped tag we remove it from the Stack and add it as a child of the new Stack top.

At the end of the XML data the Stack will be empty and all instances will be related:

Below is the source code of the second class:


class XMLBinder extends org.xml.sax.helpers.DefaultHandler {

private Hashtable map = new Hashtable();
private Stack stack = new Stack();
private XMLTag rootElement;

private String attribute;
private StringBuffer value = new StringBuffer();

/**
* @param map with String keys and XMLTag values
*/
public XMLBinder(Hashtable map) {
Enumeration e = map.keys();
while (e.hasMoreElements()) {
Object key = e.nextElement();
Object tag = map.get(key);
if (validateMapping(key, tag)) {
this.map.put(key, tag);
} else {
throw new IllegalArgumentException("key " + key);
}
}
}
private boolean validateMapping (Object key, Object tag) {
return key instanceof String
&& tag instanceof Class
&& XMLTag.class.isAssignableFrom((Class) tag);
}

public XMLTag unmarshall (InputStream in) throws IOException {
try {
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
parser.parse(in, this);
return rootElement;
} catch (Exception ex) {
throw new IOException("caused by " + ex);
}
}

public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
Class tag = (Class) map.get(qName);
if (tag != null) {
try {
XMLTag newTag = (XMLTag) tag.newInstance();
addAttributesToXMLTag(attributes, newTag);
stack.push(newTag);
} catch (Exception e) {
throw new SAXException("caused by " + e);
}
} else {
attribute = qName;
}
}
private void addAttributesToXMLTag (Attributes attributes, XMLTag newTag) {
if (attributes != null) {
for (int i = attributes.getLength() - 1; i >= 0; i--) {
String attrName = attributes.getQName(i);
String attrValue = attributes.getValue(i);
newTag.setAttributeValue(attrName, attrValue);
}
}
}

public void characters(char[] ch, int start, int length) {
if (attribute != null) {
value.append(ch, start, length);
}
}

public void endElement(String uri, String localName, String qName)
throws SAXException {
if (stack.isEmpty()) {
throw new SAXException("no mapping for " + qName);
}
if (attribute != null && attribute.equals(qName)) {
XMLTag parent = (XMLTag) stack.peek();
parent.setAttributeValue(attribute, value.toString());
attribute = null;
value.setLength(0);
} else {
XMLTag child = (XMLTag) stack.pop();
if (stack.isEmpty() == false) {
XMLTag parent = (XMLTag) stack.peek();
parent.addChild(child);
} else {
rootElement = (XMLTag) child;
}
}
}
}

For example, if we want to read RSS feed from Technè – Blog de Tecnologia do c.e.s.a.r – we use the following code:

String url = "http://techne.cesar.org.br/feed/?lang=en";
InputStream in = Connector.openInputStream(url);
XMLBinder binder = new XMLBinder(map);

rss = (RSS) binder.unmarshall(in);
Enumeration e = rss.channel.getChilds();
while (e.hasMoreElements()) {
Item i = (Item) e.nextElement();
// i.getAttributeValue("title")
// i.getAttributeValue("link")
}

Hope this helps.

Tuesday, November 9, 2010

Check for updates

Cross-posted (in Portuguese) at Technè - Blog de Tecnologia do C.E.S.A.R.

Nowadays it is very common to have a "Check for updates" feature on mobile apps. Specially if they are Android or iPhone apps, because their platform already provide this. And what about Java ME apps? Here is a way.

First we need an official site and a public file in it. For example, http://mysite.com/myapp.txt. This file will have the application latest version number and the url from where it can be downloaded. The following lines will have the description of what have changed:

"[version] [url]

[description]"

Then we add an UI option for this feature, say, "Check for updates". When user selects this option the app downloads the file content with:


InputStream in = Connector.openInputStream("http://mysite.com/myapp.txt");
ByteArrayOutputStream out = new ByteArrayOutputStream();
int i = 0;
while ((i = in.read()) >= 0) {
out.write(i);
}
String content = new String(out.toByteArray());

Current application version may be stored in a constant (final) attribute, but can be read dinamically with MIDlet.getAppProperty(“MIDlet-Version”). To trigger an update the versions need only to be different.

IMPORTANT: description must be used. This clarifies to the user the benefits of updating.

If the user selects to go on with the update the application only need to call
MIDlet.platformRequest(url).
This will open the handset browser to present the url.

As most users do not care about manually checking for new versions we might also add an automatic verifier. If the application is connected this verification can be done in between other connections. If the application is offline it can check how much time has passed since the last check.

Add the following auxiliar methods:


private long byteArrayToLong(byte [] buf) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(buf);
DataInputStream dis = new DataInputStream(in);
return dis.readLong();
}
private byte[] longToByteArray(long value) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(out);
dos.writeLong(value);
return out.toByteArray();
}

And the following code on MIDlet.startApp():


RecordStore rs = RecordStore.openRecordStore("lastUpdateCheck", true);
long now = System.currentTimeMillis();
if (rs.getNumRecords() == 0) {
// first application execution
byte[] data = longToByteArray(now);
rs.addRecord(data, 0, data.length);
} else {
final long MONTH = 30*24*60*60*1000;
long before = byteArrayToLong(rs.getRecord(1));
if (now - before > MONTH) {
// show "Check for Update" Alert
// save current time
byte[] data = longToByteArray(now);
rs.setRecord(1, data, 0, data.length);
}
}
rs.closeRecordStore();

When developing an update remember to keep the same RecordStore names and how they are used. Users do not like to loose their configurations. To make sure that this will happen use the same MIDlet-Name and MIDlet-Vendor on jad file.

Combined with Crash Reports, continuous updates add credibility to the application and the development team.
Hope this helps.

Related topics:

Sunday, November 7, 2010

Adding Crash Report

Cross-posted (in Portuguese) at Technè - Blog de Tecnologia do C.E.S.A.R.

Using only Twitter API for Java ME it is possible to add Crash Report to a Java ME application.

Crash Report is a set of information with details of an issue that made an application stop working.
Operating Systems like Microsoft Windows, Mac OS and Linux distros have this feature.
We browsers like Mozilla Firefox and Google Chrome also do. Android too since version 2.2 .

It is recommended that the application has its own Twitter account. This way all development team can follow application reports.

What should go inside a Crash Report?


  1. Application name and version: MIDlet-Name e Version

  2. Name and value of attributes and variables

  3. Exception that caused the issue

  4. Handset model running the app: microedition.platform



Below is a class that will handle all this info:

public class CrashReport {
private StringBuffer report = new StringBuffer();
private CrashReport (String content) {
report.append(content);
}
public CrashReport (MIDlet midlet, String details, Throwable e) {
if (midlet != null) {
report.append(midlet.getAppProperty("MIDlet-Name"));
report.append(' ');
report.append(midlet.getAppProperty("MIDlet-Version"));
report.append(' ');
}
if (details != null) {
report.append(details).append(' ');
}
if (e != null) {
report.append(e).append(' ');
}
String platform = System.getProperty("microedition.platform");
if (platform != null) {
report.append(platform);
}
}
public String toString () {
return report.toString();
}
public byte [] toByteArray () {
return toString().getBytes();
}
public static CrashReport fromByteArray (byte[] data) {
return new CrashReport(new String(data));
}
}


As soon as we get an unexpected exception we show a dialog stating that there was an error as asking if the details can be sent to the development team. If the user answers no, the report is discarded. If the answer is yes, the app tries to send the message right away.

Below is the class responsible to send the report:


public class CrashReportTwitterSender {
private TweetER tweetEr;
public CrashReportTwitterSender () throws IOException {
// check previous post on how to get a credential instance
UserAccountManager accountManager = UserAccountManager.getInstance(credential);
try {
if (accountManager.verifyCredential()) {
tweetEr = TweetER.getInstance(accountManager);
} else {
throw new IOException("Could not verify credential");
}
} catch (LimitExceededException ex) {
throw new IOException("caused by " + ex.getClass().getName());
}
}
public boolean sendReport (CrashReport report) {
try {
tweetEr.post(new Tweet(report.toString()));
return true;
} catch (Exception ex) {
return false;
}
}
}


If an error happens when the app is trying to send the report we ask the user to save it and send later.
If the user answers no, the report is discarded. If the answer is yes, the app saves the report for future usage.

Below is the class responsible to save CrashReport instances at RecordStore:


public class CrashReportStore {
private RecordStore reports;
public CrashReportStore () throws IOException {
try {
reports = RecordStore.openRecordStore("reports", true);
} catch (RecordStoreException ex) {
throw new IOException("caused by " + ex.getClass().getName());
}
}
public void addReport (CrashReport report) throws IOException {
byte [] data = report.toByteArray();
try {
reports.addRecord(data, 0, data.length);
} catch (RecordStoreException ex) {
throw new IOException("caused by " + ex);
}
}
public boolean hasReports () {
try {
return this.reports.getNumRecords() > 0;
} catch (RecordStoreNotOpenException ex) {
return false;
}
}
public void close() throws IOException {
try {
this.reports.closeRecordStore();
} catch (RecordStoreException ex) {
throw new IOException("caused by " + ex);
}
}
public void sendAllReports (CrashReportTwitterSender sender) throws IOException {
try {
RecordEnumeration re = this.reports.enumerateRecords(null, null, false);
while (re.hasNextElement()) {
sendReportAndDeleteRecord(sender, re.nextRecordId());
}
} catch (Exception ex) {
throw new IOException("caused by " + ex);
}
}
private void sendReportAndDeleteRecord (CrashReportTwitterSender sender, int id)
throws RecordStoreException
{
byte [] data = this.reports.getRecord(id);
CrashReport report = CrashReport.fromByteArray(data);
if (sender.sendReport(report)) {
this.reports.deleteRecord(id);
}
}
}


Next time the application is started it can check if CrashReportStore.hasReports().
If there is none we do not need to bother. If there is some, we ask the user to send the reports now or later.

Below is a sample code to test what we wrote here. It should be pasted at some class that extends MIDlet.


CrashReport report = new CrashReport(this, "crash report test", null);
CrashReportTwitterSender sender = new CrashReportTwitterSender();
if (sender.sendReport(report) == false) {
CrashReportStore store = new CrashReportStore();
store.addReport(report);
store.close();
}


Related topics:

Saturday, September 25, 2010

Nine Slice Scalling

Below is the public contract (constructor and methods) of my NineSliceImage component. It may be used to draw Buttons, Title bars and Frames.

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

public class NineSliceImage {

public NineSliceImage (Image img, int vMargin, int hMargin) { ... }

public void paintFrameAround (Graphics g, int x, int y, int width, int height) { ... }
public Image createFrameFor (int width, int height) { ... }

}

And below is a sample usage:

class MyClass extends Canvas {
MyClass () throws java.io.IOException {
Image i = Image.createImage("/pattern.PNG");
this.nsi = new NineSliceImage(i, 5, 5);
}
protected void paint(Graphics g) {
nsi.paintFrameAround(g, 20, 20, 20, 20);
}
}

Where patter is the following image:

And the resulting frame is:

Full component source code below:

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

public class NineSliceImage {

private Image originalImage;
private int verticalMargin;
private int horizontalMargin;
int middlePieceHeight;
int middlePieceWidth;

public NineSliceImage (Image img, int vMargin, int hMargin) {
validateConstructorParameters(img, vMargin, hMargin);
setAttributesValuesFor(img, vMargin, hMargin);
}

public Image createFrameFor (int width, int height) {
validateWidthAndHeight(width, height);

int middleHeight = getMiddleHeight(height);
int middleWidth = getMiddleWidth(width);
Image frame = Image.createImage(middleWidth + (2 * this.horizontalMargin),
middleHeight + (2 * this.verticalMargin));
Graphics g = frame.getGraphics();

this.paintFrameAround(g, horizontalMargin, verticalMargin, middleWidth, middleHeight);

return frame;
}

public void paintFrameAround (Graphics g, int x, int y, int width, int height) {
validateWidthAndHeight(width, height);

int middleWidth = getMiddleWidth(width);
int middleHeight = getMiddleWidth(height);
int totalWidth = middleWidth + (2 * this.horizontalMargin);
int totalHeight = middleHeight + (2 * this.verticalMargin);
x = x - ((middleWidth - width) / 2) - this.horizontalMargin;
y = y - ((middleHeight - height) / 2) - this.verticalMargin;

drawFrameCorners(g, x, y, middleWidth, middleHeight, totalWidth, totalHeight);
drawTopAndBottomLines(g, x, y, width, totalHeight);
drawLeftAndRightColumns(g, x, y, height, totalWidth);
}

private void validateConstructorParameters (Image img, int vMargin, int hMargin) {
if (img == null || vMargin <= 0 || hMargin <= 0) {
throw new IllegalArgumentException();
}
if (vMargin >= img.getHeight() / 2) {
throw new IllegalArgumentException();
}
if (hMargin >= img.getWidth() / 2) {
throw new IllegalArgumentException();
}
}

private void setAttributesValuesFor (Image img, int vMargin, int hMargin) {
this.originalImage = img;
this.verticalMargin = vMargin;
this.horizontalMargin = hMargin;
this.middlePieceHeight = this.originalImage.getHeight()
- (2 * this.verticalMargin);
this.middlePieceWidth = this.originalImage.getWidth()
- (2 * this.horizontalMargin);
}

private void validateWidthAndHeight (int width, int height) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException();
}
}

private int getMiddleWidth (int width) {
return getHorizontalRepetitions(width) * middlePieceWidth;
}

private int getHorizontalRepetitions (int width) {
int horizontalRepetitions = width / middlePieceWidth;

if (width % middlePieceWidth > 0) {
horizontalRepetitions++;
}

return horizontalRepetitions;
}

private int getMiddleHeight (int height) {
return getVerticalRepetitions(height) * middlePieceHeight;
}

private int getVerticalRepetitions (int height) {
int verticalRepetitions = height / middlePieceHeight;

if (height % middlePieceHeight > 0) {
verticalRepetitions++;
}

return verticalRepetitions;
}

private void drawFrameCorners (Graphics g, int x, int y, int middleWidth,
int middleHeight, int totalWidth, int totalHeight) {
g.setClip(x, y,
this.horizontalMargin, this.verticalMargin);
g.drawImage(this.originalImage, x, y,
Graphics.LEFT | Graphics.TOP);
g.setClip(x, y + this.verticalMargin + middleHeight,
this.horizontalMargin, this.verticalMargin);
g.drawImage(this.originalImage, x, y + totalHeight,
Graphics.LEFT | Graphics.BOTTOM);
g.setClip(x + this.horizontalMargin + middleWidth, y,
this.horizontalMargin, this.verticalMargin);
g.drawImage(this.originalImage, x + totalWidth, y,
Graphics.RIGHT | Graphics.TOP);
g.setClip(x + this.horizontalMargin + middleWidth, y + this.verticalMargin + middleHeight,
this.horizontalMargin, this.verticalMargin);
g.drawImage(this.originalImage, x + totalWidth, y + totalHeight,
Graphics.RIGHT | Graphics.BOTTOM);
}

private void drawTopAndBottomLines (Graphics g, int x, int y, int width, int totalHeight) {
int horizontalRepetitions = getHorizontalRepetitions(width);
x += horizontalMargin;
for (int i = 0; i < horizontalRepetitions; i++) {
g.setClip(x, y, middlePieceWidth, this.verticalMargin);
g.drawImage(this.originalImage, x - this.horizontalMargin, y,
Graphics.LEFT | Graphics.TOP);
g.setClip(x, y + totalHeight - this.verticalMargin,
middlePieceWidth, this.verticalMargin);
g.drawImage(this.originalImage, x - this.horizontalMargin,
y + totalHeight - this.verticalMargin,
Graphics.LEFT | Graphics.TOP);
x += middlePieceWidth;
}
}

private void drawLeftAndRightColumns (Graphics g, int x, int y, int height, int totalWidth) {
int verticalRepetitions = getVerticalRepetitions(height);

y += this.verticalMargin;
for (int i = 0; i < verticalRepetitions; i++) {
g.setClip(x, y, this.horizontalMargin, middlePieceHeight);
g.drawImage(this.originalImage, x, y - this.verticalMargin,
Graphics.LEFT | Graphics.TOP);
g.setClip(x + totalWidth - this.horizontalMargin, y,
this.horizontalMargin, middlePieceHeight);
g.drawImage(this.originalImage, x + totalWidth - this.horizontalMargin,
y - this.verticalMargin,
Graphics.LEFT | Graphics.TOP);
y += middlePieceHeight;
}
}

}

Sunday, August 22, 2010

Manipulate PNG palette

I am not a graphic designer, thus I don't know how to create fancy icons/images
for my apps and Microsoft
Paint
is just enough for my skills.

For all my apps I've used black-and-white PNG files without transparency and that is ok, but... black and white?! I should be
adding colors.

Image class does not have a method to easily change colors. A workaround could be to call getRGB method and iterate the rgbData array.
A better way is to read the file content, change the palette bytes and create an image from the resulting data. First lets create a helper
method to read a whole InputStream and return a byte array with its content:


private byte [] readStream (InputStream in)
throws java.io.IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte [] buff = new byte [1024];
int size = in.read(buff);

while (size >= 0) {
baos.write(buff, 0, size);
size = in.read(buff);
}

return baos.toByteArray();
}

Next we have to find where is the palette chunk inside the byte array. Here is another helper method:


// return index where P of PLTE is found at buff array or -1
private int getPLTEIndex (byte [] buff) {
int i = -1;
// 4 == "PLTE".size()
if (buff != null && buff.length >= 4) {
boolean foundPalete = false;
boolean endOfBuff = false;
do {
i++;
foundPalete = buff[i] == 'P'
&& buff[i +1] == 'L'
&& buff[i +2] == 'T'
&& buff[i +3] == 'E';
endOfBuff = (i +4 >= buff.length);
} while (!foundPalete && !endOfBuff);
if (endOfBuff) {
i = -1;
}
}
return i;
}

And, finally, a method to change a color from the palette of PNG files with color type 3:


private byte [] setRGBColor (byte [] buff, int colorIndex, int colorNewValue) {
int i = getPLTEIndex(buff);
if (i >= 0) {
i += 4; // 4 == "PLTE".size()
i += (colorIndex * 3); // 3 == RGB bytes
if (i + 3 <= buff.length) {
buff[i] = (byte) (((colorNewValue & 0x00ff0000) >> 16) & 0xff);
buff[i +1] = (byte) (((colorNewValue & 0x0000ff00) >> 8) & 0xff);
buff[i +2] = (byte) ((colorNewValue & 0x000000ff) & 0xff);
}
}
return buff;
}

Below is a sample on how to use all the methods:

InputStream in = getClass().getResourceAsStream("/e.png");
try {
byte [] buff = readStream(in);
Image original = Image.createImage(buff, 0, buff.length);
buff = setRGBColor(buff, 0, 0x00ff0000); // set 1st color to red
buff = setRGBColor(buff, 1, 0x0000ff00); // set 2nd color to green
Image updated = Image.createImage(buff, 0, buff.length);
} catch (IOException ex) {
ex.printStackTrace();
}

Related topics:

Saturday, July 10, 2010

Alert setTimeout FOREVER

So you are showing error messages, but no emails are coming in. Is your application faultless? Or all users do not bother to write down some lines and click send?

Maybe your users do not have a chance to see the error message because it was automatically dismissed before they could read it. How can it happen? - you ask.

Not calling setTimeout method on an Alert object will leave the timeout as the default value. Which value? The Virtual Machine implementation will decide, but the more important part: it is not Alert.FOREVER!

From Alert documentation (the very first line): "An alert is a screen that shows data to the user and waits for a certain period of time before proceeding to the next Displayable."

See? If you, sloppy developer, does not set a timeout the Alert will be automatically dismissed and your user will blink and ask "What was that?".

To the rescue an updated version of showExceptionAlert method below:


public void showExceptionAlert (Exception e) {
StringBuffer msg = new StringBuffer(getErrorMessage());
Display d = Display.getDisplay(this);

msg.append(e.getClass().getName());
if (e.getMessage() != null) {
msg.append("\n").append(e.getMessage());
}

Alert a = new Alert(getErrorTitle(), msg.toString(),
null /*alertImage*/, AlertType.ERROR);
a.setTimeout(Alert.FOREVER);
d.setCurrent(a, d.getCurrent());
}


getErrorMessage() will load the message prefix and getErrorTitle() will load the Alert title based on current locale (i18n).

Related topics:

Saturday, April 24, 2010

Avoiding OutOfMemoryError

Some Java ME applications load all their resources from its own jar file, but other apps load resources dinamically, for example,
from a network connection.

My Books application can open text files using an
Open File Dialog I have created.
But what happens if a file is too big to open?

If the Java Virtual Machine raises an Exception current version (1.3)
will show an error message requesting the user to send me an email with the details: exception type and message (if available).
Because of this I have already received many emails with IOException details.

But I was not treating Java Errors, only Exceptions. Because of this some users might see "Unhandled Error" messages shown by Java
Virtual Machine and will not know what to do. Next version will fix this and show a better message.
That is a good fix but does not solve the problem.

One thing I have learned is that reacting to Exceptions and Errors is not a good approach. The best thing to do is to antecipate
such situations and a situation we can antecipate is the OutOfMemoryError.

First, we need to know how much free memory is available. This can be done with
Runtime class.
Before trying to open a file I check if its size fits the current available memory with the following method:


boolean isThereEnoughMemoryToOpen (FileConnection file)
throws IOException
{
return Runtime.getRuntime().freeMemory() < file.fileSize();
}


If the method returns false (or throw an Exception) I do not open the file and show an error message to the user.

Related topics:

Saturday, April 17, 2010

Using pointerDragged method

Some cell phones do not have a keyboard. They only have a touch screen.
In this case, if you have a Canvas
screen on your application you will have to rely only on pointer methods.

This fact came to me because a user of my Books
application have such a phone and sent me an email informing that he could not go back to a previous page.
I checked the application source code and found out the reason. The code used pointer methods only to go forward, never backwards.
I have developed (but not released) a solution using pointerDragged and here are the details.

For functional backward compatibility I had to differ from two user actions:


  • touch and release - methods call order is: first pointerPressed and then pointerReleased.
  • touch, dragg and release - methods call order is: first pointerPressed, then a lot of pointerDragged calls and then pointerReleased.

A new boolean attribute was added: pointerDragging. This attribute receives true at pointerDragged and is checked on pointerReleased.
If it is false I go forward to next book page. Below is how I use this attribute:

protected void pointerDragged(int x, int y) {
pointerDragging = true;
// ... more code. I will show this in a while
}
protected void pointerReleased(int x, int y) {
if (pointerDragging == false) {
// move the text down. Torwards the end of the file
this.keyPressed(Canvas.KEY_NUM8);
}
pointerDragging = false;
}


Now I had to add the new user action: text dragging. To help know where the dragging is moving a new class was created: Point.

class Point {

int x, y;

void set (int x, int y) {
this.x = x;
this.y = y;
}

boolean rightOf (int x) { return x < this.x; }

boolean leftOf (int x) { return x > this.x; }

boolean above (int y) { return y > this.y; }

boolean below (int y) { return y < this.y; }
}

A new Point attribute was added: lastPoint. It is initiated on constructor and used on pointerPressed to store the first point the user touched the screen.

protected void pointerPressed(int x, int y) {
this.lastPoint.set(x, y);
}

As pointerDragged method is called a lot of times I could not move to previous/next page at each call to this method.
I had to check if current x,y point was far enough from the lastPoint stored on pointerPressed.
And "far enough" came to me as font.getHeight(), where font is the Font instance used to draw the text on paint method.

Below is my final source code for pointerDragged method:


protected void pointerDragged(int x, int y) {
pointerDragging = true;

if (this.lastPoint.rightOf(x + font.getHeight())
|| this.lastPoint.above(y - font.getHeight())) {
// move the text up. Torwards the beginning of the file
this.keyPressed(Canvas.KEY_NUM2);
this.lastPoint.set(x, y);
}
else if (this.lastPoint.leftOf(x - font.getHeight())
|| this.lastPoint.below(y + font.getHeight())){
// move the text down. Torwards the end of the file
this.keyPressed(Canvas.KEY_NUM8);
this.lastPoint.set(x, y);
}
}

I check both x and y values because the user will be able to move between pages using up/down or left/right touch movements.
Next release of Books is planned for this week. Stay tuned!

Related topics:

Saturday, March 13, 2010

Adapting to sizeChanged

Cell phones with accelerometers may automatically change the screen orientation from portrait to landscape.
If you use LCDUI screens on your application there is nothing to worry.
But if you use Canvas these changes are notified by sizeChanged method.

At my Books application I show text in fullscreen landscape.
The user will need to rotate the handset to read properly.
If the cell phone automatically change the screen orientation the user will not be able to read.

What I had to do is to implement sizeChanged method to check if Sprite rotation is needed or not:


public void sizeChanged (int w, int h) {
if (sprite == null) return;
if (super.getWidth() < super.getHeight()) { // portrait screen
sprite.setTransform(Sprite.TRANS_ROT90);
} else {
sprite.setTransform(Sprite.TRANS_NONE);
}
sprite.setPosition(0, 0);
}

Related Topics:

Thursday, December 17, 2009

Single Jar theory

This month there was a Sun Tech Days event at São Paulo, Brazil.
I could not be there, but I am thankful they shared the slides.

The only presentation about Java ME was JavaME: Building Cool Interfaces with the Lightweight UI Toolkit (PDF document). I think there are also other cool things on Java ME, but the event is a lot smaller than Java ONE and there was more interesting stuff on Java Standard and Enterprise. A Java ME event? Lets hope there will be a new Java Mobile, Media & Embedded Developer Days - Latin America.

Back to the presentation. At slide 26 there is this interesting suggestion about Easy deployment with One JAR.
I think this is the right way of thinking, but the presentation is about LWUIT being the answer.

I advocate the Adaptive User Interface for some time now and agree it is hard to achieve with only LCDUI and Canvas.
But I do not like the LWUIT overhead. To me 50K is still too much (slide 78).

My applications are small ones (below 15k) and I will keep them this way.
They focus on one feature and try to do it right: with good user experience.

Will I ever use LWUIT? Maybe. I might need an specially difficult UI - very hard to implement from scratch - and end up using this toolkit.
But I will first try doing it with Canvas (slide 19). If I can not do it this way I will still have learned something from it.

Related Topics:

Friday, December 11, 2009

Reading UTF-8 streams

If you have a byte array with Unicode characters encoded with UTF-8 you can create a String from it with the following constructors:

String(byte[] bytes, int off, int len, String enc) 
String(byte[] bytes, String enc)


This is useful when reading from local files. But what can you use if you are reading an UTF-8 stream?

You can use class java.io.InputStreamReader with the following constructor:

InputStreamReader(InputStream is, String enc)


After you have the InputStreamReader instance you can create a char array buffer and use the following method:

public int read(char[] cbuf, int off, int len)


For example:

InputStreamReader in = new InputStreamReader(
inputConnection.openInputStream(), "UTF-8");
char [] buff = new char[1024];
int len = in.read(buff, 0, buff.length);

while (len > 0) {
// use buff characters, like
// String s = new String(buff, 0, len)

len = in.read(buff, 0, buff.length);
}


But I have used only in.read(buff) and did not have a problem with it.

This also applies for Java Standard and Enterprise editions.

Thursday, December 10, 2009

Showing error messages

Exceptions are a powerful feature of Java.
Applications may recover from expected error situations and present meaningful messages to the user.

But when you catch an unexpected exception? What should you present to the user?

I propose to show a generic error message prefix requesting user feedback like:

"There was an unexpected situation, please send an email to a@b.c with the following details: "

The details are the Exception class name and, if available, the message.
Below is a method to handle exceptions this way, it should be placed in a class that extends MIDlet:


public void showExceptionAlert (Exception e) {
StringBuffer msg = new StringBuffer(getErrorMessage());
Display d = Display.getDisplay(this);

msg.append(e.getClass().getName());
if (e.getMessage() != null) {
msg.append("\n").append(e.getMessage());
}

d.setCurrent(
new Alert(getErrorTitle(), msg.toString(),
null /*alertImage*/, AlertType.ERROR),
d.getCurrent());
}


getErrorMessage() and will load the prefix getErrorTitle() the Alert title based on current locale (i18n).

Related topics:

Tuesday, December 8, 2009

Color Distance

We can represent a color in Java ME using integer literals. The four bytes represent different aspects of the color:

  • Transparency: also known as Alpha channel. Indicates if the color should we drawn as solid or translucid

  • Red: the red component

  • Green: the green component

  • Blue: the blue component



In a previous post I pointed how to get the handset theme color dinamically with Display.getColor.
But I came to a situation where I could not use COLOR_HIGHLIGHTED_BACKGROUND with COLOR_HIGHLIGHTED_FOREGROUND.
The colors were too "close". Like dark gray over black.

My work around was to add a color distance verification. Lets imagine that the three color components are the axis of a 3D system.
To calculate the distance between two colors (two points) I just need to apply the below method:


public static int colorDistance (int c1, int c2) {
int r1 = (c1 & 0xff0000) >> 16;
int g1 = (c1 & 0x00ff00) >> 8;
int b1 = c1 & 0x0000ff;
int r2 = (c2 & 0xff0000) >> 16;
int g2 = (c2 & 0x00ff00) >> 8;
int b2 = c2 & 0x0000ff;

// http://en.wikipedia.org/wiki/Euclidean_metric
return sqrtRound(((r2 - r1) * (r2 - r1)) +
((g2 - g1) * (g2 - g1)) +
((b2 - b1) * (b2 - b1)));
}


I chose that if color distance if below or equal to 40 I should use black color for foreground and white color for background.

Related topics:

Monday, December 7, 2009

Square Root Rounding

Since CLDC 1.1 Java ME applications can use floating point operations like sin, cos, tan, sqrt, etc. All these methods are available at java.lang.Math class.

Some applications needs square root floating point precision and will have to use double variables. Other applications need only an approximation and this is what I present here.

Lets first take a look at how I create my square pyramid. To start we draw two colums with the base number and its square:








BaseSquare
00
11
24
39
416


Now we place all other numbers between the squares.








BaseSquare and other numbers
0
      values <= 0   
1
          1  2         
2
       3  4  5  6      
3
    7  8  9 10 11 12   
4
13 14 15 16 17 18 19 20


The square root approximation of these numbers is the same as the square root of the center value. For example, sqrtRound(7) == 3, sqrtRound(6) == 2.

Below is the source code:

public static int sqrtRound (int value) {
if (value <= 0) {
return 0;
}

int i = 1;
int j = 2;

// search for range where value is between two square values
while ((i * i) <= value && value > (j * j)) {
i++;
j++;
}

int d = (j * j) - (i * i) + 1;

if ((value - (i * i)) < (d / 2)) { // round down
return i;
} else { // round up
return j;
}
}