Saturday, October 23, 2010

Tips About Compatibility Issues With My Latest Commits

I just committed some major refactoring to the LWUIT code base the other day, I changed the way Dialogs work so the style of the Dialog UIID actually surrounds the "dialog" as one would expect... This works really well but is highly incompatible since the change required quite a bit of hacks. I also moved the alignment attribute into the Style class which doesn't seem like something that would break compatibility but it does.

If you are running into problems with your existing code try the following:

You can use the old Dialog logic by invoking Dialog.setDialogTitleCompatibilityMode(true);
This is mostly intended as an intermediate solution until you can fix your theme/code.
If you used Dialog style manipulations by changing the Dialog's content pane this will no longer work as expected! You will need to use the Dialog.setDialogStyle/UIID() methods instead.

If you find that alignment is broken for some use cases its usually triggered by changing the style after setting alignment. The solutions for this are to either define the alignment in the theme or just move the alignment setting code to a point after you change the style e.g.:
cmp.setAlignment(LEFT);
cmp.setUIID("SomethingElse");

Will replace the style component in the component and break its alignment, you need to make sure this doesn't happen.

Thursday, October 21, 2010

The UI Builder Class: How to actually use the LWUIT GUI builder

In my previous GUI Builder post I was a bit vague mostly because I was doing it in a rush in a day of some serious major commits to LWUIT's SVN, I didn't have that much time to post and when I do get a chance to write something there is so much new stuff I'm just overwhelmed with what to write about... (FYI allot more is coming really soon!).

Please notice that when you use the LWUIT GUI builder and its related API's that its alpha level software likely to break and fail in interesting ways. I did my best to prevent a resource file corruption but I strongly suggest to constantly backup the resource file while working (and please file issues if you run into them!).
The UIBuilder API is also in alpha form and might change significantly although I tend to believe we have the right direction there...

The premise is this: the designer creates a UI version and names GUI components, he can create commands as well including navigation commands, exit, minimize. He can also define a long running command which will by default trigger a thread to execute...

All UI elements can be loaded using the UIBuilder class. Why not just use the Resources API?
Since Resources is essential for using LWUIT, adding the UIBuilder as an import would cause any application (even those that don't use the UIBuilder) to increase in size! We don't want people who aren't using the feature to pay the penalty for its existence!

The UI Builder is designed for use as a state machine carrying out the current state of the application so when any event occurs a subclass of the UIBuilder can just process it. The simplest way and most robust way for changes is to use the resource editor to generate some code for you (yes I know its not a code generation tool but there is a hack...). The code you need to write to process the UI includes some boilerplate code which is pretty mundane for processing commands based on id's or processing an event from a component, to simplify this process I added to the resource editor two menu items, the firs is simpler: "Help->Show Source For Using". The second is far more elaborate and I will try to go over it and its output bellow: "MIDlet->Generate UI State Machine".

When running the Generate UI state machine menn option in a project with UI builder content it prompts you for a directory/file name for the output. Just give it a new file name in your project directory and it will create a UIBuilder subclass containing most of what you need...
The trick is not to touch that code! DO NOT CHANGE THAT CODE!

Sure you can change it and everything will be just fine, however it you will make changes to the GUI regenerating that file will obviously loose all those changes which is not something you want!
To solve it you need to subclass the generated class rather than subclass UIBuilder and just override the appropriate methods (some might even be abstract for your convenience), then if a UI changes you can safely overwrite the base class since you didn't change anything there...

Just so we are clear on the subject, after generating the code I can run the code by just using this and nothing else:
import javax.microedition.midlet.*;
import com.sun.lwuit.*;

public class RatioMIDlet extends MIDlet {
public void startApp() {
Display.init(this);
new StateMachine("/ratios.res");
}

public void pauseApp() {
}

public void destroyApp(boolean unconditional) {
}
}


Obviously for a real world example that actually does something I would want to subclass and override methods in StateMachine.java. Lets look at the generated code which will help us understand both how to use the UIBuilder ourselves and how to subclass the code:


import com.sun.lwuit.*;
import com.sun.lwuit.util.*;
import com.sun.lwuit.events.*;
import com.sun.lwuit.plaf.*;

public class StateMachine extends UIBuilder {
public StateMachine(Resources res, String resPath, boolean loadTheme) {
UIBuilder.registerCustomComponent("HTMLComponent", com.sun.lwuit.html.HTMLComponent.class);
if(res != null) {
setResourceFilePath(resPath);
Form f = (Form)createContainer(res, "Splash");
f.show();
} else {
Form f = (Form)createContainer(resPath, "Splash");
f.show();
}
if(loadTheme) {
if(res == null) {
try {
res = Resources.open(resPath);
} catch(java.io.IOException err) { err.printStackTrace(); }
}
UIManager.getInstance().setThemeProps(res.getTheme(res.getThemeResourceNames()[0]));
}
}

public StateMachine(String resPath) {
this(null, resPath, true);
}

public StateMachine(Resources res) {
this(res, null, true);
}

public StateMachine(String resPath, boolean loadTheme) {
this(null, resPath, loadTheme);
}

public StateMachine(Resources res, boolean loadTheme) {
this(res, null, loadTheme);
}

public static final int COMMAND_BREAD_BACK = 5;
public static final int COMMAND_DOUGHS_BREAD = 4;
public static final int COMMAND_DOUGHS_BACK = 3;
public static final int COMMAND_MAINSCREEN_DOUGHS = 2;

protected boolean onBreadBack() {
return false;
}

protected boolean onDoughsBread() {
return false;
}

protected boolean onDoughsBack() {
return false;
}

protected boolean onMainscreenDoughs() {
return false;
}

protected void processCommand(ActionEvent ev, Command cmd) {
switch(cmd.getId()) {
case COMMAND_BREAD_BACK:
if(onBreadBack()) {
ev.consume();
}
return;

case COMMAND_DOUGHS_BREAD:
if(onDoughsBread()) {
ev.consume();
}
return;

case COMMAND_DOUGHS_BACK:
if(onDoughsBack()) {
ev.consume();
}
return;

case COMMAND_MAINSCREEN_DOUGHS:
if(onMainscreenDoughs()) {
ev.consume();
}
return;

}
}

protected void handleComponentAction(Component c, ActionEvent event) {
if(c.getComponentForm().getName().equals("Bread")) {
if("TextField1".equals(c.getName())) {
onTextfield1Action(c, event);
return;
}
if("ComboBox1".equals(c.getName())) {
onCombobox1Action(c, event);
return;
}
if("TextField11".equals(c.getName())) {
onTextfield11Action(c, event);
return;
}
if("ComboBox11".equals(c.getName())) {
onCombobox11Action(c, event);
return;
}
if("TextField111".equals(c.getName())) {
onTextfield111Action(c, event);
return;
}
if("ComboBox111".equals(c.getName())) {
onCombobox111Action(c, event);
return;
}
if("TextField12".equals(c.getName())) {
onTextfield12Action(c, event);
return;
}
if("ComboBox12".equals(c.getName())) {
onCombobox12Action(c, event);
return;
}
}
if(c.getComponentForm().getName().equals("MainScreen")) {
if("Doughs".equals(c.getName())) {
onDoughsAction(c, event);
return;
}
if("Batters".equals(c.getName())) {
onBattersAction(c, event);
return;
}
if("Custards".equals(c.getName())) {
onCustardsAction(c, event);
return;
}
if("Fat-Based Sauces".equals(c.getName())) {
onFatBasedSaucesAction(c, event);
return;
}
if("Stocks & Thickeners".equals(c.getName())) {
onStocksThickenersAction(c, event);
return;
}
if("Meat Related Ratios".equals(c.getName())) {
onMeatRelatedRatiosAction(c, event);
return;
}
if("Desert Sauces".equals(c.getName())) {
onDesertSaucesAction(c, event);
return;
}
}
if(c.getComponentForm().getName().equals("Doughs")) {
if("Bread".equals(c.getName())) {
onBreadAction(c, event);
return;
}
if("Pasta Dough".equals(c.getName())) {
onPastaDoughAction(c, event);
return;
}
if("Pie Dough".equals(c.getName())) {
onPieDoughAction(c, event);
return;
}
if("Biscuit".equals(c.getName())) {
onBiscuitAction(c, event);
return;
}
if("Cookie Dough".equals(c.getName())) {
onCookieDoughAction(c, event);
return;
}
if("Pâte à Choux".equals(c.getName())) {
onPTeChouxAction(c, event);
return;
}
}
}

protected void onTextfield1Action(Component c, ActionEvent event) {
}

protected void onCombobox1Action(Component c, ActionEvent event) {
}

protected void onTextfield11Action(Component c, ActionEvent event) {
}

protected void onCombobox11Action(Component c, ActionEvent event) {
}

protected void onTextfield111Action(Component c, ActionEvent event) {
}

protected void onCombobox111Action(Component c, ActionEvent event) {
}

protected void onTextfield12Action(Component c, ActionEvent event) {
}

protected void onCombobox12Action(Component c, ActionEvent event) {
}

protected void onDoughsAction(Component c, ActionEvent event) {
}

protected void onBattersAction(Component c, ActionEvent event) {
}

protected void onCustardsAction(Component c, ActionEvent event) {
}

protected void onFatBasedSaucesAction(Component c, ActionEvent event) {
}

protected void onStocksThickenersAction(Component c, ActionEvent event) {
}

protected void onMeatRelatedRatiosAction(Component c, ActionEvent event) {
}

protected void onDesertSaucesAction(Component c, ActionEvent event) {
}

protected void onBreadAction(Component c, ActionEvent event) {
}

protected void onPastaDoughAction(Component c, ActionEvent event) {
}

protected void onPieDoughAction(Component c, ActionEvent event) {
}

protected void onBiscuitAction(Component c, ActionEvent event) {
}

protected void onCookieDoughAction(Component c, ActionEvent event) {
}

protected void onPTeChouxAction(Component c, ActionEvent event) {
}

} 
 
 
Like most generated code it is rather on the verbose side and methods/variables don't have the best naming logic, this is to be expected since the tool generates methods whether needed or not and provides names based on the names given by the designer (who in this case often chose not to change the default names given by the tool...).
The code starts with several constructors to create the builder they all lead to one central constructor which I would like to focus on:

public StateMachine(Resources res, String resPath, boolean loadTheme) {
UIBuilder.registerCustomComponent("HTMLComponent", com.sun.lwuit.html.HTMLComponent.class);
if(res != null) {
setResourceFilePath(resPath);
Form f = (Form)createContainer(res, "Splash");
f.show();
} else {
Form f = (Form)createContainer(resPath, "Splash");
f.show();
}
if(loadTheme) {
if(res == null) {
try {
res = Resources.open(resPath);
} catch(java.io.IOException err) { err.printStackTrace(); }
}
UIManager.getInstance().setThemeProps(res.getTheme(res.getThemeResourceNames()[0]));
}
}


Notice the first line: registerCustomComponent(). Its crucial.
We need this line to let the GUI builder "know" how to locate a component by mapping the UIID to the component class. A Java SE developer might think "why not use Class.forName()". The problem is that Class.forName() will not work with obfuscated code, so we have to explicitly declare which classes we are using beyond the "core" LWUIT classes (even if they are within the LWUIT sub packages!).
This is a good thing since it means that you won't be importing Table, Tree & HTMLComponent unless you explicitly want them in your code. (BTW You will get an exception from the UIBuilder if you miss such an register call).
Notice that this also allows you to import your own arbitrary components and even use them in the resource editor's GUI builder (just by using pickMIDlet, but more on that at a later date).

To create the first form (which I picked from a combo box in the UI builder when generating the code) the UI builder needs the resource file and just invokes createContainer (which in this case returns a Form). Notice the call to setResourceFilePath, its important.
Once a form was created the UI builder tries to discard the resource file from RAM, however the resulting UI might include a Command for navigation to another form (declared within the resource file). How would the resource file be opened?
The solution is quite simple, if we have the name of the resource file we just open it (so creating the UI using a path name from the JAR is the simplest way possible to work with the UI builder). However, for more elaborate use cases such as storing downloaded resources in the RMS etc. one can just override the fetchResources() method and do pretty much anything to load a resources file on demand.


Next in the class we have constants for the command id's declared in the GUI builder, the GUI builder tries to push for them to be unique but doesn't force it (since you might want two commands with the same id). The generated code automatically adds a switch case and methods to process the commands which return true if the triggering event should be consumed.

And finally we have the exact same thing for every component that triggers an actionEvent. You can override handleComponentAction directly or let it do its magic and override your component name method in the code.

There are quite allot of additional methods in UIBuilder designed to enable you to use it in many varied ways (e.g. without subclassing and by binding listeners), I would like to mention a few methods of interest:
protected void processBackground(Form f)

This method is designed for the Splash Screen/wait dialog use case and is invoked on a separate thread to allow a long running task to take place. The default implementation is a sleep of 3000 ms allowing for easy debugging when running an application (e.g. I used this for the splash screen of the Ratio demo and it waited slightly even though the application doesn't load anything). To enable this in the GUI define the next form property which will trigger this method upon showing the form and seamlessly move to the next form on completion.

Similarly we have asyncCommandProcess(Command cmd, ActionEvent sourceEvent) and postAsyncCommand(Command cmd, ActionEvent sourceEvent).
A navigation command in  the builder can be defined as asynchronous which will trigger these methods allowing the user to write background logic in the asyncCommandProcess() method and update the UI with the changes in the postAsyncCommand() method. Currently the UI builder doesn't generate code for async commands using these methods but it probably should do that for the next iteration.

I have allot more to write about and this was supposed to be a really short post so I'll try to keep some energy for these posts.

Monday, October 18, 2010

New LWUIT GUI Builder Now Live!

Its been a while since we last discussed GUI builders and LWUIT, we spent some time thinking of the whole "gui builder issue". The biggest issue with GUI builders IMO is the code generation aspect, so when creating the new LWUIT GUI builder we just removed that aspect completely opting to make use of our existing resource file and resource editor architecture to allow a GUI builder that doesn't need to generate any code.
You can see a quick demo video on the right and you can try the webstart version right now to see how this works for you.
There are allot of things in the new GUI builder and I will try to keep you posted on how to use them in the best ways possible. The demo I'm showing off is a clone of Ruhlman's Ratio application which is based on his great cook book.

Monday, October 4, 2010

JavaOne 2010 - by Chen

It was a very interesting J1 this year (1st time under Oracle).
I felt it was better in previous years, but I believe Oracle will improve next year.
Anyway, our traditional LWUIT session went well and I had the opportunity to meet some lwuit developers.

Our session slides can be downloaded from here:

and the demo sources can be accessed from here (svn link):


(if you don't have a java.net user simply type in 'guest' user with an empty password)

Below I added a short video of the demo - a simple client that connects to Yahoo's Answers web service by using LWUIT4IO for the network communication