Tuesday, November 20, 2012

Using NULL constants

I think the Null Object pattern is a good approach to not have null checks in the source code, but I do it a little differently from what is shown at Wikipedia.
First thing - I do not use an interface nor a Null class. Second thing - I use a constant to hold the Null object.
class MyClass {
  public static final MyClass NULL = new MyClass();
}

If I add a method to MyClass that changes the object state I change the NULL initialization to use an inner class.
class MyClass {
  public static final MyClass NULL = new MyClass() {
    public void setAttribute(int newValue) {}

  };

  private int attribute;

  public void setAttribute(int newValue) {
    this.attribute = newValue;

  }
}


Tuesday, July 17, 2012

One million downloads

It took five years, but it finally hapenned! July 2012 its the month when my chess board reached ONE MILLION downloads!!
First version went live on July 16, 2007. With it I learned how to create a graphical interface that can adapt to the screen size and wrote my findings at this blog.
Up until now I did now earn a dime with this app, but the pride is big nonetheless!
If you've got a phone that runs Java, install and see what other MILLION people has seen. ;)
http://www.getjar.com/mobile/11226/Chess

Tuesday, June 26, 2012

Single Canvas Theory

When you decide to create a custom UI based on low level graphics you might be tempted to fall into one of the following: have a [Game]Canvas child for each screen or have a single [Game]Canvas child to render all the UI.

I advocate to use a single [Game]Canvas child because we do not how how much memory each [Game]Canvas instance might need. Different JVMs will use more or less memory for [Game]Canvas and Displayable classes.

But it is a bad idea to keep all UI logic in a sigle file. We could easily get above a thousand lines of code.

A simple way to use a single Canvas child and split UI logic in different classes is to use a Paintable class. It will have all event delivery methods from Canvas ([show|hide]Notify, key[Press|Repeat|Releas]ed, pointer[Press|Dragg|Releas]ed and paint) so it can render itself and react to visibility transiction and user input. If we were to use a single GameCanvas instance the approach would be different.

The differences to Canvas are:

  • key[Press|Repeat|Releas]ed methods will receive an extra parameter: action, which is initiated with getGameAction(keyCode).
  • key[Press|Repeat|Releas]ed and pointer[Press|Dragg|Releas]ed methods will return boolean: true if the screen needs to be repainted.
  • from all other Canvas methods only get[Width|Height] are really necessary
The Paintable instance will have a CommandListener attribute to fire specific events to the Canvas: repaint, exit or change screen.

Bellow is the code for the Paintable class:

abstract class Paintable {

  private int width, height;
  private CommandListener listener;

  final void init(CommandListener listener, int w, int h) {
    this.listener = listener;
    this.width = w;
    this.height = h;
  }

  final public int getWidth() {
    return width;
  }

  final public int getHeight() {
    return height;
  }

  final void repaint() {
    listener.commandAction(MainCanvas.CMD_REPAINT, null);
  }

  final void exit() {
    listener.commandAction(MainCanvas.CMD_EXIT, null);
  }

  /**
   * @param id one of SCR constants from MainCanvas
   */
  final void show(int id) {
    Command change = new Command("", Command.SCREEN, id);

    listener.commandAction(change, null);
  }

  boolean keyPressed(int keyCode, int gameAction) {
    return false;
  }

  boolean pointerPressed(int x, int y) {
    return false;
  }

  void showNotify() {
  }

  void hideNotify() {
  }

  abstract void paint (Graphics g);
}
Below is my MainCanvas class:
public class MainCanvas extends Canvas 
  implements CommandListener 
{

  static final Command CMD_REPAINT = new Command("",
    Command.SCREEN, 1);
  static final Command CMD_EXIT = new Command("", 
    Command.EXIT, 1);
  
  static final int SCR_SPLASH = 1;
  static final int SCR_MENU = 2;
  static final int SCR_PLAY = 3;
  
  private Paintable paintable;
  private int paintableId;
  private MIDlet midlet;
  
  public MainCanvas(MIDlet midlet) {
    this.midlet = midlet;
  }
  
  public int getGameAction(int keyCode) {
    int action = 0;

    try {
      action = super.getGameAction(keyCode);
    } catch (Exception e) {}

    return action;
  }
  
  protected void keyPressed(int keyCode) {
    if (paintable.keyPressed(keyCode, getGameAction(keyCode)))
      repaint();
  }
  
  protected void pointerPressed(int x, int y) {
    if (paintable.pointerPressed(x, y))
      repaint();
  }
  
  protected void paint(Graphics g) {
    paintable.paint(g);
  }

  public void commandAction(Command c, Displayable d) {
    if (c == CMD_REPAINT) {
      repaint();
    }
    else if (c == CMD_EXIT) {
      midlet.notifyDestroyed();
    }
    else if (c.getCommandType() == Command.SCREEN) {
      changeScreen(c.getPriority());
    }
  }
  
  public void changeScreen(int id) {
    if (paintableId == id) {
      return;
    }

    Paintable nextPaintable = newPaintableFromId(id);

    if (nextPaintable != null) {
      if (paintable != null) {
        paintable.hideNotify();
      }
      nextPaintable.init(this, getWidth(), getHeight());
      paintable = nextPaintable;
      paintable.showNotify();
      paintableId = id;
      repaint();
    }
  }

  private Paintable newPaintableFromId(int id) {
    Paintable nextPaintable = null;

    switch (id) {
      case SCR_SPLASH:
        nextPaintable = new SplashScreen();
        break;
      case SCR_MENU:
        nextPaintable = new MainMenuScreen();
        break;
      case SCR_PLAY:
        nextPaintable = new BoardScreen();
        break;
    }

    return nextPaintable;
  }
}
Below is the MIDlet that initiates and show MainCanvas:
public class C extends MIDlet {

  private MainCanvas mainCanvas;
  
  public C() {
    mainCanvas = new MainCanvas(this);
    mainCanvas.changeScreen(MainCanvas.SCR_SPLASH);
  }

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

  protected void pauseApp() {
  }

  protected void destroyApp(boolean arg0) {
  }
}
When SplashScreen needs to show MainMenuScreen it will call show(MainCanvas.SCR_MENU).

Related topics:

Tuesday, July 19, 2011

Don't use RecordComparator

Even if you try to keep the number of classes to a minimum you must create some classes to represent your entities. More so if you use these entities on IO operations: save on recordStore; send on httpRequest; etc.



This tip is for those who do not use/know Floggy and create methods to [de]serialize these entity classes from/to byte arrays:
Do not use RecordComparator



Why? Because this interface defines a compare method that receives two byte arrays, you should know how your entities are represented as byte arrays and compare them only with array indexing. If you deserialize a class only to compare it you will be wasting a load of memory only to sort your RecordEnumeration.



I consider a better approach to load your RecordStore without a RecordComparator, deserialize your class and use an insertion sort algorithm on a Vector.



Related topics:

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:

Friday, October 22, 2010

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:

Sunday, July 11, 2010

Blog audience 2T10

From 2010 March, 15 to June, 14 this blog had 215 visits from 54 countries.

The top 10 countries are:


  • Brazil: 34

  • Indonesia: 25

  • India: 15

  • United States: 11

  • Italy: 7

  • Russia: 7

  • Germany: 6

  • Portugual: 6

  • Poland: 4



Romania, United Kingdom, Sweden and Canada has left the top 10 list. New to the list are Italy, Russia and Poland.

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:

Monday, May 31, 2010

Books: version 1.4

Yesterday I shared Books version 1.4, new features are listed below on Related Topics.
I also updated my blog entry on Books.

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: