Skip to content

The Components Of Codename One

Shai Almog edited this page Sep 15, 2019 · 54 revisions

The Components of Codename One

This chapter covers the components of Codename One. Not all components are covered, but it tries to go deeper than the JavaDocs.

Container

The Codename One container is a base class for many high level components; a container is a component that can contain other components.

Every component has a parent container that can be null if it isn’t within a container at the moment or is a top-level container. A container can have many children.

Component-Container relationship expressed as UML
Figure 1. Component-Container relationship expressed as UML

Components are arranged in containers using layout managers which are algorithms that determine the arrangement of components within the container.

You can read more about layout managers in the basics section.

Composite Components

Codename One components share a very generic hierarchy of inheritance e.g. Button derives from Label and thus receives all its abilities.

However, some components are composites and derive from the Container class. E.g. the MultiButton is a composite button that derives from Container but acts/looks like a Button. Normally this is pretty seamless for the developer, with a few things to keep in mind.

  • You should not use the Container derived methods on such a composite component (e.g. add/remove etc.).

  • You can’t cast it to the type that it relates to e.g. you can’t cast MultiButton to Button.

  • Events might be more nuanced. E.g. if you rely on ActionEvent.getSource() or ActionEvent.getComponent() notice that they might not behave the way you would expect. For a MultiButton they will return the underlying Button. To workaround this we have ActionEvent.getActualComponent().

Lead Component

Codename One has a rather unique feature for creating composite components: "lead components". This feature effectively allows components like MultiButton to act as if they are a single component while really being comprised of multiple components.

Lead components work by setting a single component within as the "leader" it determines the style state for all the components in the hierarchy so if we have a Container that is lead by a Button the button will determine if the selected/pressed state is returned for the entire container hierarchy.

This creates a case where a single Component has multiple nested UIID’s e.g. `MultiButton has UIID’s such as `MultiLine1 that can be customized via API’s such as setUIIDLine1.

The lead component also handles the events from a single source so clicking in one of the other components within the hierarchy will send the event to the leading Button resulting in action events that behave "oddly" (hence the need for getActualComponent);

You can learn more about lead components in here.

Form

Form is the top-level container of Codename One, Form derives from Container and is the element we “show”. Only one form can be visible at any given time. We can get the currently visible Form using the code:

Form currentForm = Display.getInstance().getCurrent();

A form is a unique container in the sense that it has a title, a content area and optionally a menu/menu bar area. When invoking methods such as add/remove on a form, you are in fact invoking something that maps to this:

myForm.getContentPane().add(...);

Form is in effect just a Container that has a border layout, its north section is occupied by the title area and its south section by the optional menu bar. The center (which stretches) is the content pane. The content pane is where you place all your components.

Form layout graphic
Figure 2. Form layout graphic

You can see that every Form has space allocated for the title area. If you don’t set the title it won’t show up (its size will be zero), but it will still be there. The same isn’t always true for the case of the menu bar, which can vary significantly. Effectively, the section that matters is the content pane, so the form tries to do the “right thing” by pretending to be the content pane. However, this isn’t always seamless and sometimes code needs to just invoke getContentPane() in order to work directly with the container.

Tip
A good example for such a case is with layout animations. Animating the form might not produce the right results. When in doubt its pretty easy to just use getContentPane instead of working with the Form directly.

As you can see from the graphic, Form has two layers that reside on top of the content pane/title. The first is the layered pane which allows you to place "always on top" components. The layered pane added implicitly when you invoke getLayeredPane().

Note
You still need to place components using layout managers in order to get them to appear in the right place when using the layered pane.

The second layer is the glass pane which allows you to draw arbitrary things on top of everything. The order in the image is indeed accurate:

  1. ContentPane is lowest

  2. LayeredPane is second

  3. GlassPane is painted last

Note
Its important to notice that a layered pane is on top of the ContentPane only and doesn’t stretch to the title. A GlassPane usually stretches all the way but only with a "lightweight" title area e.g. the Toolbar API.

The GlassPane allows developers to overlay UI on top of existing UI and paint as they see fit. This is useful for things that provide notification but don’t want to intrude with application functionality.

Note
In earlier versions of Codename One (pre-3.6), LayeredPane & GlassPane didn’t work with "native" peer components such as media, browser, native maps etc, because peer components were always rendered "in front" of the Codename One UI canvas. However, current versions now allow for proper layering of peer components and light-weight components so that LayeredPane and GlassPane can be used seamlessly with peer components.

Dialog

A Dialog is a special kind of Form that can occupy only a portion of the screen, it also has the additional functionality of the modal show method.

When showing a dialog we have two basic options: modeless and modal:

  • Modal dialogs (the default) block the current EDT thread until the dialog is dismissed (to understand how they do it, read about invokeAndBlock).
    Modal dialogs are an extremely useful way to prompt the user since the code can assume the user responded in the next line of execution. This promotes a linear & intuitive way of writing code.

  • Modless dialogs return immediately so a call to show such a dialog can’t assume anything in the next line of execution. This is useful for features such as progress indicators where we aren’t waiting for user input.

E.g. a modal dialog can be expressed as such:

if(Dialog.show("Click Yes Or No", "Select one", "Yes", "No")) {
    // user clicked yes
} else {
    // user clicked no
}

Notice that during the show call above the execution of the next line was "paused" until we got a response from the user and once the response was returned we could proceed directly.

Important
All usage of Dialog must be within the Event Dispatch Thread (the default thread of Codename One). This is especially true for modal dialogs. The Dialog class knows how to "block the EDT" without blocking it.

To learn more about invokeAndBlock which is the workhorse behind the modal dialog functionality check out the EDT section.

The Dialog class contains multiple static helper methods to quickly show user notifications, but also allows a developer to create a Dialog instance, add information to its content pane and show the dialog.

Tip
Dialogs contain a ContentPane just like Form.

When showing a dialog in this way, you can either ask Codename One to position the dialog in a specific general location (taken from the BorderLayout concept for locations) or position it by spacing it (in pixels) from the 4 edges of the screen.

E.g. you could do something like this to show a simple modal Dialog:

Dialog d = new Dialog("Title");
d.setLayout(new BorderLayout());
d.add(BorderLayout.CENTER, new SpanLabel("Dialog Body", "DialogBody"));
d.showPacked(BorderLayout.SOUTH, true);
Custom Dialog in the south position
Figure 3. Custom modal Dialog in the south position
Tip
You can turn the code above to a modless Dialog by flipping the boolean true argument to false.

We can position a Dialog absolutely by determining the space from the edges e.g. with this code we can occupy the bottom portion of the screen:

Dialog d = new Dialog("Title");
d.setLayout(new BorderLayout());
d.add(BorderLayout.CENTER, new SpanLabel("Dialog Body", "DialogBody"));
d.show(hi.getHeight() / 2, 0, 0, 0);
Custom Dialog positioned absolutely
Figure 4. Custom Dialog positioned absolutely
Note
hi is the name of the parent Form in the sample above.

Styling Dialogs

It’s important to style a Dialog using getDialogStyle() or setDialogUIID methods rather than styling the dialog object directly.

The reason for this is that the Dialog is really a Form that takes up the whole screen. The Form that is visible behind the Dialog is rendered as a screenshot. So customizing the actual UIID of the Dialog won’t produce the desired results.

Tint and Blurring

By default a Dialog uses a platform specific tint color when it is showing e.g. notice the background in the image below is tinted:

Form hi = new Form("Tint Dialog", new BoxLayout(BoxLayout.Y_AXIS));
Button showDialog = new Button("Tint");
showDialog.addActionListener((e) -> Dialog.show("Tint", "Is On....", "OK", null));
hi.add(showDialog);
hi.show();
Dialog with tinted background
Figure 5. Dialog with tinted background

The tint color can be manipulated on the parent form, you can set it to any AARRGGBB value to set any color using the setTintColor method. Notice that this is invoked on the parent form and not on the Dialog!

Important
This is an AARRGGBB value and not an RRGGBB value! This means that 0 will be transparent.

You can also manipulate this default value globally using the theme constant tintColor. The sample below tints the background in green:

Form hi = new Form("Tint Dialog", new BoxLayout(BoxLayout.Y_AXIS));
hi.setTintColor(0x7700ff00);
Button showDialog = new Button("Tint");
showDialog.addActionListener((e) -> Dialog.show("Tint", "Is On....", "OK", null));
hi.add(showDialog);
hi.show();
Dialog with green tinted background
Figure 6. Dialog with green tinted background

We can apply Gaussian blur to the background of a dialog to highlight the foreground further and produce a very attractive effect. We can use the setDefaultBlurBackgroundRadius to apply this globally, we can use the theme constant dialogBlurRadiusInt to do the same or we can do this on a per Dialog basis using setBlurBackgroundRadius.

Note
Not all device types support blur you can test if your device supports it using Display.getInstnace().isGaussianBlurSupported(). If blur isn’t supported the blur setting will be ignored.
Form hi = new Form("Blur Dialog", new BoxLayout(BoxLayout.Y_AXIS));
Dialog.setDefaultBlurBackgroundRadius(8);
Button showDialog = new Button("Blur");
showDialog.addActionListener((e) -> Dialog.show("Blur", "Is On....", "OK", null));
hi.add(showDialog);
hi.show();
The blur effect coupled with the OS default tint
Figure 7. The blur effect coupled with the OS default tint

It might be a bit hard to notice the blur effect with the tinting so here is the same code with tinting disabled:

hi.setTintColor(0);
The blur effect is more pronounced when the tint is disabled
Figure 8. The blur effect is more pronounced when the tint is disabled

Popup Dialog

A popup dialog is a common mobile paradigm showing a Dialog that points at a specific component. It’s just a standard Dialog that is shown in a unique way:

Dialog d = new Dialog("Title");
d.setLayout(new BorderLayout());
d.add(BorderLayout.CENTER, new SpanLabel("Dialog Body", "DialogBody"));
d.showPopupDialog(showDialog);
Popup Dialog
Figure 9. Popup Dialog

The popup dialog accepts a Component or Rectangle to point at and handles the rest.

Styling The Arrow Of The Popup Dialog

When Codename One was young we needed a popup arrow implementation but our low level graphics API was pretty basic. As a workaround we created a version of the 9-piece image border that supported pointing arrows at a component.

Today Codename One supports pointing an arrow from the RoundRectBorder class. This is implicit for the PopupDialog UI. This allows for better customization of the border (color etc.) and it looks better on newer displays. It also works on all OSs. Right now only the iOS theme has the old image border approach.

Note
This will change with a future update where all OS’s will align and iOS will use the lightweight popup too
Tip
You can make all OS’s act the same way by overriding the PopupDialog UIID and defining its style to RoundRectBorder

The new RoundRectBorder support works by setting the track component property on border. When that’s done the border implicitly points to the right location.

If you still need deeper customization of the arrow you can still use the old 9-piece border functionality illustrated below.

Legacy 9-Piece Border Arrow

One of the harder aspects of a popup dialog is the construction of the theme elements required for arrow styling. To get that sort of behavior you will need a custom image border and 4 arrows pointing in each direction that will be overlaid with the border.

Note
The sizes of the arrow images should be similarly proportioned and fit within the image borders whitespace. The block image of the dialog should have empty pixels in the sides to reserve space for the arrow. E.g. if the arrows are all 32x32 pixels then the PopupDialog image should have 32 pixels of transparent pixels around it.

You will need to define the following theme constants for the arrow to work:

PopupDialogArrowBool=true
PopupDialogArrowTopImage=arrow up image
PopupDialogArrowBottomImage=arrow down image
PopupDialogArrowLeftImage=arrow left image
PopupDialogArrowRightImage=arrow right image

Then style the PopupDialog UIID with the image for the Dialog itself.

InteractionDialog

Dialogs in Codename One can be modal or modeless, the former blocks the calling thread and the latter does not. However, there is another definition to those terms: A modal dialog blocks access to the rest of the UI while a modeless dialog "floats" on top of the UI.

In that sense, all dialogs in Codename One are modal; they block the parent form since they are effectively just forms that show the "parent" in their background. InteractionDialog has an API that is very similar to the Dialog API but, unlike dialog, it never blocks anything. Neither the calling thread nor the UI.

Note
InteractionDialog isn’t a Dialog since it doesn’t share the same inheritance hierarchy. However, it acts and "feels" like a Dialog despite the fact that it’s just a Container in the LayeredPane.

InteractionDialog is really just a container that is positioned within the layered pane. Notice that because of that design, you can have only one such dialog at the moment and, if you add something else to the layered pane, you might run into trouble.

Using the interaction dialog is pretty trivial and very similar to dialog:

InteractionDialog dlg = new InteractionDialog("Hello");
dlg.setLayout(new BorderLayout());
dlg.add(BorderLayout.CENTER, new Label("Hello Dialog"));
Button close = new Button("Close");
close.addActionListener((ee) -> dlg.dispose());
dlg.addComponent(BorderLayout.SOUTH, close);
Dimension pre = dlg.getContentPane().getPreferredSize();
dlg.show(0, 0, Display.getInstance().getDisplayWidth() - (pre.getWidth() + pre.getWidth() / 6), 0);
Interaction Dialog
Figure 10. Interaction Dialog

This will show the dialog on the right hand side of the screen, which is pretty useful for a floating in place dialog.

Note
The InteractionDialog can only be shown at absolute or popup locations. This is inherent to its use case which is "non-blocking". When using this component you need to be very aware of its location.

Label

Label represents a text, icon or both. Label is also the base class of Button which in turn is the base class for RadioButton & CheckBox. Thus the functionality of the Label class extends to all of these components.

Label text can be positioned in one of 4 locations as such:

Label left = new Label("Left", icon);
left.setTextPosition(Component.LEFT);
Label right = new Label("Right", icon);
right.setTextPosition(Component.RIGHT);
Label bottom = new Label("Bottom", icon);
bottom.setTextPosition(Component.BOTTOM);
Label top = new Label("Top", icon);
top.setTextPosition(Component.TOP);
hi.add(left).add(right).add(bottom).add(top);
Label positions
Figure 11. Label positions

Label allows only a single line of text, line breaking is a very expensive operation on mobile devices [1] and so the Label class doesn’t support it.

Tip
SpanLabel supports multiple lines with a single label, notice that it does carry a performance penalty for this functionality.

Labels support tickering and the ability to end with “…​” if there isn’t enough space to render the label. Developers can determine the placement of the label relatively to its icon in quite a few powerful ways.

Label Gap

The gap between the label text & the icon defaults to 2 pixels due to legacy settings. The setGap method of Label accepts a gap size in pixels.

Two pixels is low for most cases & it’s hard to customize for each Label.

You can use the theme constant labelGap which is a floating point value you can specify in millimeters that will allow you to determine the default gap for a label. You can also customize this manually using the method Label.setDefaultGap(int) which determines the default gap in pixels.

Autosizing Labels

One of the common requests we received over the years is a way to let text "fit" into the allocated space so the font will match almost exactly the width available. In some designs this is very important but it’s also very tricky. Measuring the width of a String is a surprisingly expensive operation on some OS’s. Unfortunately, there is no other way other than trial & error to find the "best size".

Still despite the fact that something is "slow" we might still want to use it for some cases, this isn’t something you should use in a renderer, infinite scroll etc. and we recommend minimizing the usage of this feature as much as possible.

This feature is only applicable to Label and its subclasses (e.g. Button), with components such as TextArea (e.g. SpanButton) the choice between shrinking and line break would require some complex logic.

To activate this feature just use setAutoSizeMode(true) e.g.:

Form hi = new Form("AutoSize", BoxLayout.y());

Label a = new Label("Short Text");
a.setAutoSizeMode(true);
Label b = new Label("Much Longer Text than the previous line...");
b.setAutoSizeMode(true);
Label c = new Label("MUCH MUCH MUCH Much Longer Text than the previous line by a pretty big margin...");
c.setAutoSizeMode(true);

Label a1 = new Button("Short Text");
a1.setAutoSizeMode(true);
Label b1 = new Button("Much Longer Text than the previous line...");
b1.setAutoSizeMode(true);
Label c1 = new Button("MUCH MUCH MUCH Much Longer Text than the previous line by a pretty big margin...");
c1.setAutoSizeMode(true);
hi.addAll(a, b, c, a1, b1, c1);

hi.show();
Automatically sizes the fonts of the buttons/labels based on text and available space
Figure 12. Automatically sizes the fonts of the buttons/labels based on text and available space

TextField and TextArea

The TextField class derives from the TextArea class, and both are used for text input in Codename One.

TextArea defaults to multi-line input and TextField defaults to single line input but both can be used in both cases. The main differences between TextField and TextArea are:

  • Blinking cursor is rendered on TextField only

  • DataChangeListener is only available in TextField. This is crucial for character by character input event tracking

  • Done listener is only available in the TextField

  • Different UIID

Note
The semantic difference between TextField & TextArea dates back to the ancestor of Codename One: LWUIT. Feature phones don’t have “proper” in-place editing capabilities & thus TextField was introduced to allow such input.

Because it lacks the blinking cursor capability TextArea is often used as a multi-line label and is used internally in SpanLabel, SpanButton etc.

Tip
A common use case is to have an important text component in edit mode immediately as we enter a Form. Codename One forms support this exact use case thru the Form.setEditOnShow(TextArea) method.

TextField & TextArea support constraints for various types of input such as NUMERIC, EMAIL, URL, etc. Those usually affect the virtual keyboard used, but might not limit input in some platforms. E.g. on iOS even with NUMERIC constraint you would still be able to input characters.

Tip
If you need to prevent specific types of input check out the validation section.

The following sample shows off simple text field usage:

TableLayout tl;
int spanButton = 2;
if(Display.getInstance().isTablet()) {
    tl = new TableLayout(7, 2);
} else {
    tl = new TableLayout(14, 1);
    spanButton = 1;
}
tl.setGrowHorizontally(true);
hi.setLayout(tl);

TextField firstName = new TextField("", "First Name", 20, TextArea.ANY);
TextField surname = new TextField("", "Surname", 20, TextArea.ANY);
TextField email = new TextField("", "E-Mail", 20, TextArea.EMAILADDR);
TextField url = new TextField("", "URL", 20, TextArea.URL);
TextField phone = new TextField("", "Phone", 20, TextArea.PHONENUMBER);

TextField num1 = new TextField("", "1234", 4, TextArea.NUMERIC);
TextField num2 = new TextField("", "1234", 4, TextArea.NUMERIC);
TextField num3 = new TextField("", "1234", 4, TextArea.NUMERIC);
TextField num4 = new TextField("", "1234", 4, TextArea.NUMERIC);

Button submit = new Button("Submit");
TableLayout.Constraint cn = tl.createConstraint();
cn.setHorizontalSpan(spanButton);
cn.setHorizontalAlign(Component.RIGHT);
hi.add("First Name").add(firstName).
        add("Surname").add(surname).
        add("E-Mail").add(email).
        add("URL").add(url).
        add("Phone").add(phone).
        add("Credit Card").
                add(GridLayout.encloseIn(4, num1, num2, num3, num4)).
        add(cn, submit);
Simple text component sample
Figure 13. Simple text component sample
Tip
The Toolbar section contains a very elaborate TextField search sample with DataChangeListener and rather unique styling.

Masking

A common use case when working with text components is the ability to "mask" input e.g. in the credit card number above we would want 4 digits for each text field and don’t want the user to tap Next 3 times.

Masking allows us to accept partial input in one field and implicitly move to the next, this can be used to all types of complex input thanks to the text component API. E.g with the code above we can mask the credit card input so the cursor jumps to the next field implicitly using this code:

automoveToNext(num1, num2);
automoveToNext(num2, num3);
automoveToNext(num3, num4);

Then implement the method automoveToNext as:

private void automoveToNext(final TextField current, final TextField next) {
    current.addDataChangedListener((type, index) -> {
        if(current.getText().length() == 5) {
            current.stopEditing();
            current.setText(val.substring(0, 4));
            next.setText(val.substring(4));
            next.startEditingAsync();
        }
    });
}

Notice we can invoke stopEditing(Runnable) where we receive a callback as editing is stopped.

The Virtual Keyboard

A common misconception for developers is assuming the virtual keyboard represents "keys". E.g. developers often override the "keyEvent" callbacks which are invoked for physical keyboard typing and expect those to occur with a virtual keyboard.

This isn’t the case since a virtual keyboard is a very different beast. With a virtual keyboard characters typed might produce a completely different output due to autocorrect. Some keyboards don’t even have "keys" in the traditional sense or don’t type them in the traditional sense (e.g. swiping).

Tip
The constraint property for the TextField/TextArea is crucial for a virtual keyboard.
Tip
When working with a virtual keyboard it’s important that the parent Container for the TextField/TextArea is scrollable. Otherwise the component won’t be reachable or the UI might be distorted when the keyboard appears.
Action Button Client Property

By default, the virtual keyboard on Android has a "Done" button, you can customize it to be a search icon, a send icon, or a go icon using a hint such as this:

searchTextField.putClientProperty("searchField", Boolean.TRUE);
sendTextField.putClientProperty("sendButton", Boolean.TRUE);
goTextField.putClientProperty("goButton", Boolean.TRUE);

This will adapt the icon for the action on the keys.

Next and Done on iOS

We try to hide a lot of the platform differences in Codename One, input is very different between OS’s. A common reliance is the ability to send the "Done" event when the user presses the Done button. Unfortunately this button doesn’t always exist e.g. if there is an Enter button (due to multiline input) or if there is a Next button in that place.

To make the behavior more uniform we slightly customized the iOS keyboard as such:

Next virtual keyboard with toolbar
Figure 14. Next virtual keyboard with toolbar
Done virtual keyboard without toolbar
Figure 15. Done virtual keyboard without toolbar
Note
This works with 3rd party keyboards too…​

However, this behavior might not be desired so to block that we can do:

tf.putClientProperty("iosHideToolbar", Boolean.TRUE);

This will hide the toolbar for that given field.

Note
You can customize the color of the Done button in the toolbar by setting the ios.doneButtonColor display property. E.g. To change the color to red, you could do Display.getInstance().setProperty("ios.doneButtonColor", String.valueOf(0xff0000)). @since 5.0

Clearable Text Field

iOS has a convention where an X can be placed after the text field to clear it. Some Android apps have it but there is no native support for that as of this writing.

You can wrap a TextField with a clearable wrapper to get this effect on all platforms. E.g. replace this:

cnt.add(myTextField);

With this:

cnt.add(ClearableTextField.wrap(myTextField));

You can also specify the size of the clear icon if you wish. This is technically just a Container with the text field style and a button to clear the text at the edge.

TextComponent

When building input forms we sometimes want to adapt to the native OS behavior and create a UI that’s a bit more distinct to the native OS. TextField and TextArea are very low level, you can create an Android style UI with such components but it might look out of place in iOS.

E.g. this is how most of us would expect the UI to look on iOS and Android respectively:

TextModeLayout on iOS
Figure 16. Text Input on iOS
TextModeLayout on Android with the same code
Figure 17. Text Input on Android

Doing this with text fields is possible but would require code that looks a bit different and jumps through hoops. TextComponent allows this exact UI without forcing developers to write OS specific code:

TextModeLayout tl = new TextModeLayout(3, 2);
Form f = new Form("Pixel Perfect", tl);
TextComponent title = new TextComponent().label("Title");
TextComponent price = new TextComponent().label("Price");
TextComponent location = new TextComponent().label("Location");
TextComponent description = new TextComponent().label("Description").multiline(true);

f.add(tl.createConstraint().horizontalSpan(2), title);
f.add(tl.createConstraint().widthPercentage(30), price);
f.add(tl.createConstraint().widthPercentage(70), location);
f.add(tl.createConstraint().horizontalSpan(2), description);
f.setEditOnShow(title.getField());
f.show();
Tip
This code uses the TextModeLayout which is discussed in the layouts section

The text component uses a builder approach to set various values e.g.:

TextComponent t = new TextComponent().
    text("This appears in the text field").
    hint("This is the hint").
    label("This is the label").
    multiline(true);

The code is pretty self explanatory and more convenient than typical setters/getters. It automatically handles the floating hint style of animation when running on Android.

Error Handling

The validator class supports text component and it should "just work". But the cool thing is that it uses the material design convention for error handling!

So if we add to the sample above a Validator:

Validator val = new Validator();
val.addConstraint(title, new LengthConstraint(2));
val.addConstraint(price, new NumericConstraint(true));

You would see something that looks like this on Android:

Error handling when the text is blank
Figure 18. Error handling when the text is blank
Error handling when there is some input (notice red title label)
Figure 19. Error handling when there is some input (notice red title label)
On iOS the situation hasn’t changed much yet
Figure 20. On iOS the situation hasn’t changed much yet

The underlying system is the errorMessage method which you can chain like the other methods on TextComponent as such:

TextComponent tc = new TextComponent().
    label("Input Required").
    errorMessage("Input is essential in this field");

InputComponent and PickerComponent

To keep the code common and generic we use the InputComponent abstract base class and derive the other classes from that. PickerComponent is currently the only other option.

A picker can work with our existing sample using code like this:

TextModeLayout tl = new TextModeLayout(3, 2);
Form f = new Form("Pixel Perfect", tl);
TextComponent title = new TextComponent().label("Title");
TextComponent price = new TextComponent().label("Price");
TextComponent location = new TextComponent().label("Location");
PickerComponent date = PickerComponent.createDate(new Date()).label("Date");
TextComponent description = new TextComponent().label("Description").multiline(true);
Validator val = new Validator();
val.addConstraint(title, new LengthConstraint(2));
val.addConstraint(price, new NumericConstraint(true));
f.add(tl.createConstraint().widthPercentage(60), title);
f.add(tl.createConstraint().widthPercentage(40), date);
f.add(location);
f.add(price);
f.add(tl.createConstraint().horizontalSpan(2), description);
f.setEditOnShow(title.getField());
f.show();

This produces the following which looks pretty standard:

Picker component taking place in iOS
Figure 21. Picker component taking place in iOS
And in Android
Figure 22. And in Android

The one tiny thing you should notice with the PickerComponent is that we don’t construct the picker component using new PickerComponent(). Instead we use create methods such as PickerComponent.createDate(new Date()). The reason for that is that we have many types of pickers and it wouldn’t make sense to have one constructor.

Underlying Theme Constants and UIID’s

These varying looks are implemented via a combination of layouts, theme constants and UIID’s. The most important UIID’s are: TextComponent, FloatingHint & TextHint.

There are several theme constants related that can manipulate some pieces of this functionality:

  • textComponentErrorColor a hex RGB color which defaults to null in which case this has no effect. When defined this will change the color of the border and label to the given color to match the material design styling. This implements the red border underline in cases of error and the label text color change

  • textComponentOnTopBool toggles the on top mode which makes things look like they do on Android. This defaults to true on Android and false on other OS’s. This can also be manipulated via the onTopMode(boolean) method in InputComponent however the layout will only use the theme constant

  • textComponentAnimBool toggles the animation mode which again can be manipulated by a method in InputComponent. If you want to keep the UI static without the floating hint effect set this to false. Notice this defaults to true only on Android

  • textComponentFieldUIID sets the UIID of the text field to something other than TextField this is useful for platforms such as iOS where the look of the text field is different within the text component. This allows us to make the background of the text field transparent when it’s within the TextComponent and make it different from the regular text field

Button

Button is a subclass of Label and as a result it inherits all of its functionality, specifically icon placement, tickering, etc.

Button adds to the mix some additional states such as a pressed UIID state and pressed icon.

Note
There are additional icon states in Button such as rollover and disabled icon.

Button also exposes some functionality for subclasses specifically the setToggle method call which has no meaning when invoked on a Button but has a lot of implications for CheckBox & RadioButton.

Button event handling can be performed via an ActionListener or via a Command.

Important
Changes in a Command won’t be reflected into the Button after the command was set to the Button.

Here is a trivial hello world style Button:

Form hi = new Form("Button");
Button b = new Button("My Button");
hi.add(b);
b.addActionListener((e) -> Log.p("Clicked"));
Simple button in the iOS styling
Figure 23. Simple button in the iOS styling, notice iOS doesn’t have borders on buttons…​

Such a button can be styled to look like a link using code like this or simply by making these settings in the theme and using code such as btn.setUIID("Hyperlink").

Form hi = new Form("Button");
Button b = new Button("Link Button");
b.getAllStyles().setBorder(Border.createEmpty());
b.getAllStyles().setTextDecoration(Style.TEXT_DECORATION_UNDERLINE);
hi.add(b);
b.addActionListener((e) -> Log.p("Clicked"));
Button styled to look like a link
Figure 24. Button styled to look like a link

Uppercase Buttons

Buttons on Android’s material design UI use upper case styling which isn’t the case for iOS. To solve this we have the method setCapsText(boolean) in Button which has the corresponding isCapsText, isCapsTextDefault & setCapsTextDefault(boolean). This is pretty core to Codename One so to prevent this from impacting everything unless you explicitly invoke setCapsText(boolean) the default value of true will only apply when the UIID is Button, RaisedButton or for the builtin Dialog buttons.

We also have a theme constant: capsButtonTextBool. This constant controls caps text behavior from the theme and is set to true in the Android native theme.

Raised Button

Raised button is a style of button that’s available on Android and used to highlight an important action within a form. To confirm with the material design UI guidelines you might want to leverage a raised button UI element on Android but use a regular button everywhere else.

First we need to know whether a raised button exists in the theme. So on Android this will return true but on other OS’s it will return false. A potential future update might make another platform true based on UI guidelines in other OS’s.

For this purpose we’ve got the theme constant hasRaisedButtonBool which will return true on Android but will be false elsewhere. You can use it like this:

if(UIManager.getInstance().isThemeConstant("hasRaisedButtonBool", false)) {
    // that means we can use a raised button
}

To enable this we have the RaisedButton UIID that derives from Button and will act like it except for the places where hasRaisedButtonBool is true in which case it will look like this:

Raised and flat button in simulator
Figure 25. Raised and flat button in simulator

Notice that you can easily customize the colors of these buttons now since the border respects user colors…​

In this case I just set the background color to purple and the foreground to white:

Purple raised button
Figure 26. Purple raised button
Form f = new Form("Pixel Perfect", BoxLayout.y());
Button b = new Button("Raised Button", "RaisedButton");
Button r = new Button("Flat Button");
f.add(b);
f.add(r);
f.show();

Ripple Effect

The ripple effect in material design highlights the location of the finger and grows as a circle to occupy the full area of the component as the user presses the button.

We have the ability to perform a ripple effect by darkening the touched area and growing that in a quick animation.

Ripple effect can be applied to any component but we currently only have it turned on for buttons on Android which also applies to things like title commands, side menu elements etc. This might not apply at this moment to lead components like multi-buttons but that might change in the future.

Component has a property to enable the ripple effect setRippleEffect(boolean) and the corresponding isRippleEffect(). You can turn it on or off individually in the component level. However, Button has static setButtonRippleEffectDefault(boolean) and isButtonRippleEffectDefault(). These allow us to define the default behavior for all the buttons and that can be configured via the theme constant buttonRippleBool which is currently on by default on the native Android theme.

CheckBox/RadioButton

CheckBox & RadioButton are subclasses of button that allow for either a toggle state or exclusive selection state.

Both CheckBox & RadioButton have a selected state that allows us to determine their selection.

Tip
RadioButton doesn’t allow us to "deselect" it, the only way to "deselect" a RadioButton is by selecting another RadioButton.

The CheckBox can be added to a Container like any other Component but the RadioButton must be associated with a ButtonGroup otherwise if we have more than one set of RadioButton’s in the form we might have an issue.

Notice in the sample below that we associate all the radio buttons with a group but don’t do anything with the group as the radio buttons keep the reference internally. We also show the opposite side functionality and icon behavior:

CheckBox cb1 = new CheckBox("CheckBox No Icon");
cb1.setSelected(true);
CheckBox cb2 = new CheckBox("CheckBox With Icon", icon);
CheckBox cb3 = new CheckBox("CheckBox Opposite True", icon);
CheckBox cb4 = new CheckBox("CheckBox Opposite False", icon);
cb3.setOppositeSide(true);
cb4.setOppositeSide(false);
RadioButton rb1 = new RadioButton("Radio 1");
RadioButton rb2 = new RadioButton("Radio 2");
RadioButton rb3 = new RadioButton("Radio 3", icon);
new ButtonGroup(rb1, rb2, rb3);
rb2.setSelected(true);
hi.add(cb1).add(cb2).add(cb3).add(cb4).add(rb1).add(rb2).add(rb3);
RadioButton & CheckBox usage
Figure 27. RadioButton & CheckBox usage

Both of these components can be displayed as toggle buttons (see the toggle button section below), or just use the default check mark/filled circle appearance based on the type/OS.

Toggle Button

A toggle button is a button that is pressed and stays pressed. When a toggle button is pressed again it’s released from the pressed state. Hence the button has a selected state to indicate if it’s pressed or not exactly like the CheckBox/RadioButton components in Codename One.

To turn any CheckBox or RadioButton to a toggle button just use the setToggle(true) method. Alternatively you can use the static createToggle method on both CheckBox and RadioButton to create a toggle button directly.

Important
Invoking setToggle(true) implicitly converts the UIID to ToggleButton unless it was changed by the user from its original default value.

We can easily convert the sample above to use toggle buttons as such:

CheckBox cb1 = CheckBox.createToggle("CheckBox No Icon");
cb1.setSelected(true);
CheckBox cb2 = CheckBox.createToggle("CheckBox With Icon", icon);
CheckBox cb3 = CheckBox.createToggle("CheckBox Opposite True", icon);
CheckBox cb4 = CheckBox.createToggle("CheckBox Opposite False", icon);
cb3.setOppositeSide(true);
cb4.setOppositeSide(false);
ButtonGroup bg = new ButtonGroup();
RadioButton rb1 = RadioButton.createToggle("Radio 1", bg);
RadioButton rb2 = RadioButton.createToggle("Radio 2", bg);
RadioButton rb3 = RadioButton.createToggle("Radio 3", icon, bg);
rb2.setSelected(true);
hi.add(cb1).add(cb2).add(cb3).add(cb4).add(rb1).add(rb2).add(rb3);
Toggle button converted sample
Figure 28. Toggle button converted sample

That’s half the story though, to get the full effect of some cool toggle button UI’s we can use a ComponentGroup. This allows us to create a button bar effect with the toggle buttons.

E.g. lets enclose the CheckBox components in a vertical ComponentGroup and the RadioButton’s in a horizontal group. We can do this by changing the last line of the code above as such:

hi.add(ComponentGroup.enclose(cb1, cb2, cb3, cb4)).
        add(ComponentGroup.encloseHorizontal(rb1, rb2, rb3));
Toggle button converted sample wrapped in ComponentGroup
Figure 29. Toggle button converted sample wrapped in ComponentGroup

ComponentGroup

ComponentGroup is a special container that can be either horizontal or vertical (BoxLayout X_AXIS or Y_AXIS respectively).

ComponentGroup "restyles" the elements within the group to have a UIID that allows us to create a "round border" effect that groups elements together.

The following code adds 4 component groups to a Container to demonstrate the various UIID changes:

hi.add("Three Labels").
        add(ComponentGroup.enclose(new Label("GroupElementFirst UIID"), new Label("GroupElement UIID"), new Label("GroupElementLast UIID"))).
        add("One Label").
        add(ComponentGroup.enclose(new Label("GroupElementOnly UIID"))).
        add("Three Buttons").
        add(ComponentGroup.enclose(new Button("ButtonGroupFirst UIID"), new Button("ButtonGroup UIID"), new Button("ButtonGroupLast UIID"))).
        add("One Button").
        add(ComponentGroup.enclose(new Button("ButtonGroupOnly UIID")));
ComponentGroup adapts the UIID’s of the components added so we can style them
Figure 30. ComponentGroup adapts the UIID’s of the components added so we can style them

Notice the following about the code above and the resulting image:

  • Buttons have a different UIID than other element types. Their styling is slightly different in such UI’s so you need to pay attention to that.

  • When an element is placed alone within a ComponentGroup its a special case UIID.

Important
By default, ComponentGroup does nothing. You need to explicitly activate it in the theme by setting a theme property to true. Specifically you need to set ComponentGroupBool to true for ComponentGroup to do something otherwise its just a box layout container! The ComponentGroupBool flag is true by default in the iOS native theme.

When ComponentGroupBool is set to true, the component group will modify the styles of all components placed within it to match the element UIID given to it (GroupElement by default) with special caveats to the first/last/only elements. E.g.

  1. If I have one element within a component group it will have the UIID: GroupElementOnly

  2. If I have two elements within a component group they will have the UIID’s GroupElementFirst, GroupElementLast

  3. If I have three elements within a component group they will have the UIID’s GroupElementFirst, GroupElement, GroupElementLast

  4. If I have four elements within a component group they will have the UIID’s GroupElementFirst, GroupElement, GroupElement, GroupElementLast

This allows you to define special styles for the edges.

You can customize the UIID set by the component group by calling setElementUIID in the component group e.g. setElementUIID("ToggleButton") for three elements result in the following UIID’s:

ToggleButtonFirst, ToggleButton, ToggleButtonLast

MultiButton

MultiButton is a composite component (lead component) that acts like a versatile Button. It supports up to 4 lines of text (it doesn’t automatically wrap the text), an emblem (usually navigational arrow, or check box) and an icon.

MultiButton can be used as a button, a CheckBox or a RadioButton for creating rich UI’s.

Note
The MultiButton was inspired by the aesthetics of the UITableView iOS component.

A common source of confusion in the MultiButton is the difference between the icon and the emblem, since both may have an icon image associated with them. The icon is an image representing the entry while the emblem is an optional visual representation of the action that will be undertaken when the element is pressed. Both may be used simultaneously or individually of one another.

MultiButton twoLinesNoIcon = new MultiButton("MultiButton");
twoLinesNoIcon.setTextLine2("Line 2");
MultiButton oneLineIconEmblem = new MultiButton("Icon + Emblem");
oneLineIconEmblem.setIcon(icon);
oneLineIconEmblem.setEmblem(emblem);
MultiButton twoLinesIconEmblem = new MultiButton("Icon + Emblem");
twoLinesIconEmblem.setIcon(icon);
twoLinesIconEmblem.setEmblem(emblem);
twoLinesIconEmblem.setTextLine2("Line 2");

MultiButton twoLinesIconEmblemHorizontal = new MultiButton("Icon + Emblem");
twoLinesIconEmblemHorizontal.setIcon(icon);
twoLinesIconEmblemHorizontal.setEmblem(emblem);
twoLinesIconEmblemHorizontal.setTextLine2("Line 2 Horizontal");
twoLinesIconEmblemHorizontal.setHorizontalLayout(true);

MultiButton twoLinesIconCheckBox = new MultiButton("CheckBox");
twoLinesIconCheckBox.setIcon(icon);
twoLinesIconCheckBox.setCheckBox(true);
twoLinesIconCheckBox.setTextLine2("Line 2");

MultiButton fourLinesIcon = new MultiButton("With Icon");
fourLinesIcon.setIcon(icon);
fourLinesIcon.setTextLine2("Line 2");
fourLinesIcon.setTextLine3("Line 3");
fourLinesIcon.setTextLine4("Line 4");

hi.add(oneLineIconEmblem).
        add(twoLinesNoIcon).
        add(twoLinesIconEmblem).
        add(twoLinesIconEmblemHorizontal).
        add(twoLinesIconCheckBox).
        add(fourLinesIcon);
Multiple usage scenarios for the MultiButton
Figure 31. Multiple usage scenarios for the MultiButton

Styling The MultiButton

Since the MultiButton is a composite component setting its UIID will only impact the top level UI.

To customize everything you need to customize the UIID’s for MultiLine1, MultiLine2, MultiLine3, MultiLine4 & Emblem.

You can customize the individual UIID’s thru the API directly using the setIconUIID, setUIIDLine1, setUIIDLine2, setUIIDLine3, setUIIDLine4 & setEmblemUIID.

SpanButton

SpanButton is a composite component (lead component) that looks/acts like a Button but can break lines rather than crop them when the text is very long.

Unlike the MultiButton it uses the TextArea internally to break lines seamlessly. The SpanButton is far simpler than the MultiButton and as a result isn’t as configurable.

SpanButton sb = new SpanButton("SpanButton is a composite component (lead component) that looks/acts like a Button but can break lines rather than crop them when the text is very long.");
sb.setIcon(icon);
hi.add(sb);
The SpanButton Component
Figure 32. The SpanButton Component
Tip
SpanButton is slower than both Button and MultiButton. We recommend using it only when there is a genuine need for its functionality.

SpanLabel

SpanLabel is a composite component (lead component) that looks/acts like a Label but can break lines rather than crop them when the text is very long.

SpanLabel uses the TextArea internally to break lines seamlessly and so doesn’t provide all the elaborate configuration options of Label.

One of the features of label that moved into SpanLabel to some extent is the ability to position the icon. However, unlike a Label the icon position is determined by the layout manager of the composite so setIconPosition accepts a BorderLayout constraint.

SpanLabel d = new SpanLabel("Default SpanLabel that can seamlessly line break when the text is really long.");
d.setIcon(icon);
SpanLabel l = new SpanLabel("NORTH Positioned Icon SpanLabel that can seamlessly line break when the text is really long.");
l.setIcon(icon);
l.setIconPosition(BorderLayout.NORTH);
SpanLabel r = new SpanLabel("SOUTH Positioned Icon SpanLabel that can seamlessly line break when the text is really long.");
r.setIcon(icon);
r.setIconPosition(BorderLayout.SOUTH);
SpanLabel c = new SpanLabel("EAST Positioned Icon SpanLabel that can seamlessly line break when the text is really long.");
c.setIcon(icon);
c.setIconPosition(BorderLayout.EAST);
hi.add(d).add(l).add(r).add(c);
The SpanLabel Component
Figure 33. The SpanLabel Component
Tip
SpanLabel is significantly slower than Label. We recommend using it only when there is a genuine need for its functionality.

OnOffSwitch

The OnOffSwitch allows you to write an application where the user can swipe a switch between two states (on/off). This is a common UI paradigm in Android and iOS, although it’s implemented in a radically different way in both platforms.

This is a rather elaborate component because of its very unique design on iOS, but we we’re able to accommodate most of the small behaviors of the component into our version, and it seamlessly adapts between the Android style and the iOS style.

The image below was generated based on the default use of the OnOffSwitch:

OnOffSwitch onOff = new OnOffSwitch();
hi.add(onOff);
The OnOffSwitch component as it appears on/off on iOS (top) and on Android (bottom)
Figure 34. The OnOffSwitch component as it appears on/off on iOS (top) and on Android (bottom)

As you can understand the difference between the way iOS and Android render this component has triggered two very different implementations within a single component. The Android implementation just uses standard buttons and is the default for non-iOS platforms.

Tip
You can force the Android or iOS mode by using the theme constant onOffIOSModeBool.

Validation

Validation is an inherent part of text input, and the Validator class allows just that. You can enable validation thru the Validator class to add constraints for a specific component. It’s also possible to define components that would be enabled/disabled based on validation state and the way in which validation errors are rendered (change the components UIID, paint an emblem on top, etc.). A Constraint is an interface that represents validation requirements. You can define a constraint in Java or use some of the builtin constraints such as LengthConstraint, RegexConstraint, etc.

This sample below continues from the place where the TextField sample above stopped by adding validation to that code.

Validator v = new Validator();
v.addConstraint(firstName, new LengthConstraint(2)).
        addConstraint(surname, new LengthConstraint(2)).
        addConstraint(url, RegexConstraint.validURL()).
        addConstraint(email, RegexConstraint.validEmail()).
        addConstraint(phone, new RegexConstraint(phoneRegex, "Must be valid phone number")).
        addConstraint(num1, new LengthConstraint(4)).
        addConstraint(num2, new LengthConstraint(4)).
        addConstraint(num3, new LengthConstraint(4)).
        addConstraint(num4, new LengthConstraint(4));

v.addSubmitButtons(submit);
Validation and Regular Expressions
Figure 35. Validation & Regular Expressions

InfiniteProgress

The InfiniteProgress indicator spins an image infinitely to indicate that a background process is still working.

Tip
This style of animation is often nicknamed "washing machine" as it spins endlessly.

InfiniteProgress can be used in one of two ways either by embedding the component into the UI thru something like this:

myContainer.add(new InfiniteProgress());

InfiniteProgress can also appear over the entire screen, thus blocking all input. This tints the background while the infinite progress rotates:

Dialog ip = new InfiniteProgress().showInifiniteBlocking();

// do some long operation here using invokeAndBlock or do something in a separate thread and callback later
// when you are done just call

ip.dispose();
Infinite progress
Figure 36. Infinite progress

The image used in the InfiniteProgress animation is defined by the native theme. You can override that definition either by defining the theme constant infiniteImage or by invoking the setAnimation method.

Note
Despite the name of the method setAnimation expects a static image that will be rotated internally. Don’t use an animated image.

InfiniteScrollAdapter and InfiniteContainer

InfiniteScrollAdapter & InfiniteContainer allow us to create a scrolling effect that "never" ends with the typical Container/Component paradigm.

The motivation behind these classes is simple, say we have a lot of data to fetch from storage or from the internet. We can fetch the data in batches and show progress indication while we do this.

Infinite scroll fetches the next batch of data dynamically as we reach the end of the Container. InfiniteScrollAdapter & InfiniteContainer represent two similar ways to accomplish that task relatively easily.

Let start by exploring how we can achieve this UI that fetches data from a webservice:

InfiniteScrollAdapter demo code fetching property cross data
Figure 37. InfiniteScrollAdapter demo code fetching property cross data

The first step is creating the webservice call, we won’t go into too much detail here as webservices & IO are discussed later in the guide:

int pageNumber = 1;
java.util.List<Map<String, Object>> fetchPropertyData(String text) {
    try {
        ConnectionRequest r = new ConnectionRequest();
        r.setPost(false);
        r.setUrl("http://api.nestoria.co.uk/api");
        r.addArgument("pretty", "0");
        r.addArgument("action", "search_listings");
        r.addArgument("encoding", "json");
        r.addArgument("listing_type", "buy");
        r.addArgument("page", "" + pageNumber);
        pageNumber++;
        r.addArgument("country", "uk");
        r.addArgument("place_name", text);
        NetworkManager.getInstance().addToQueueAndWait(r);
        Map<String,Object> result = new JSONParser().parseJSON(new InputStreamReader(new ByteArrayInputStream(r.getResponseData()), "UTF-8"));
        Map<String, Object> response = (Map<String, Object>)result.get("response");
        return (java.util.List<Map<String, Object>>)response.get("listings");
    } catch(Exception err) {
        Log.e(err);
        return null;
    }
}
Important
The demo code here doesn’t do any error handling! This is a very bad practice and it is taken here to keep the code short and readable. Proper error handling is used in the Property Cross demo.

The fetchPropertyData is a very simplistic tool that just fetches the next page of listings for the nestoria webservice. Notice that this method is synchronous and will block the calling thread (legally) until the network operation completes.

Now that we have a webservice lets proceed to create the UI. Check out the code annotations below:

Form hi = new Form("InfiniteScrollAdapter", new BoxLayout(BoxLayout.Y_AXIS));

Style s = UIManager.getInstance().getComponentStyle("MultiLine1");
FontImage p = FontImage.createMaterial(FontImage.MATERIAL_PORTRAIT, s);
EncodedImage placeholder = EncodedImage.createFromImage(p.scaled(p.getWidth() * 3, p.getHeight() * 3), false); // (1)

InfiniteScrollAdapter.createInfiniteScroll(hi.getContentPane(), () -> { // (2)
    java.util.List<Map<String, Object>> data = fetchPropertyData("Leeds"); // (3)
    MultiButton[] cmps = new MultiButton[data.size()];
    for(int iter = 0 ; iter < cmps.length ; iter++) {
        Map<String, Object> currentListing = data.get(iter);
        if(currentListing == null) { // (4)
            InfiniteScrollAdapter.addMoreComponents(hi.getContentPane(), new Component[0], false);
            return;
        }
        String thumb_url = (String)currentListing.get("thumb_url");
        String guid = (String)currentListing.get("guid");
        String summary = (String)currentListing.get("summary");
        cmps[iter] = new MultiButton(summary);
        cmps[iter].setIcon(URLImage.createToStorage(placeholder, guid, thumb_url));
    }
    InfiniteScrollAdapter.addMoreComponents(hi.getContentPane(), cmps, true); // (5)
}, true); // (6)
  1. Placeholder is essential for the URLImage class which we will discuss at a different place.

  2. The InfiniteScrollAdapter accepts a runnable which is invoked every time we reach the edge of the scrolling. We used a closure instead of the typical run() method override.

  3. This is a blocking call, after the method completes we’ll have all the data we need. Notice that this method doesn’t block the EDT illegally.

  4. If there is no more data we call the addMoreComponents method with a false argument. This indicates that there is no additional data to fetch.

  5. Here we add the actual components to the end of the form. Notice that we must not invoke the add/remove method of Container. Those might conflict with the work of the InfiniteScrollAdapter.

  6. We pass true to indicate that the data isn’t "prefilled" so the method should be invoked immediately when the Form is first shown

Important
Do not violate the EDT in the callback. It is invoked on the event dispatch thread and it is crucial

The InfiniteContainer

InfiniteContainer was introduced to simplify and remove some of the boilerplate of the InfiniteScrollAdapter. It takes a more traditional approach of inheriting the Container class to provide its functionality.

Unlike the InfiniteScrollAdapter the InfiniteContainer accepts an index and amount to fetch. This is useful for tracking your position but also important since the InfiniteContainer also implements Pull To Refresh as part of its functionality.

Converting the code above to an InfiniteContainer is pretty simple we just moved all the code into the callback fetchComponents method and returned the array of Component’s as a response.

Unlike the InfiniteScrollAdapter we can’t use the ContentPane directly so we have to use a BorderLayout and place the InfiniteContainer there:

Form hi = new Form("InfiniteContainer", new BorderLayout());

Style s = UIManager.getInstance().getComponentStyle("MultiLine1");
FontImage p = FontImage.createMaterial(FontImage.MATERIAL_PORTRAIT, s);
EncodedImage placeholder = EncodedImage.createFromImage(p.scaled(p.getWidth() * 3, p.getHeight() * 3), false);

InfiniteContainer ic = new InfiniteContainer() {
    @Override
    public Component[] fetchComponents(int index, int amount) {
        java.util.List<Map<String, Object>> data = fetchPropertyData("Leeds");
        MultiButton[] cmps = new MultiButton[data.size()];
        for(int iter = 0 ; iter < cmps.length ; iter++) {
            Map<String, Object> currentListing = data.get(iter);
            if(currentListing == null) {
                return null;
            }
            String thumb_url = (String)currentListing.get("thumb_url");
            String guid = (String)currentListing.get("guid");
            String summary = (String)currentListing.get("summary");
            cmps[iter] = new MultiButton(summary);
            cmps[iter].setIcon(URLImage.createToStorage(placeholder, guid, thumb_url));
        }
        return cmps;
    }
};
hi.add(BorderLayout.CENTER, ic);

List, MultiList, Renderers & Models

InfiniteContainer/InfiniteScrollAdapter vs. List/ContainerList

Our recommendation is to always go with Container, InfiniteContainer or InfiniteScrollAdapter.

We recommend avoiding List or its subclasses/related classes specifically ContainerList & MultiList.

Note
We recommend replacing ComboBox with Picker but that’s a completely different discussion.

A Container with ~5000 nested containers within it can perform on par with a List and probably exceed its performance when used correctly.

Larger sets of data are rarely manageable on phones or tablets so the benefits for lists are dubious.

In terms of API we found that even experienced developers experienced a great deal of pain when wrangling the Swing styled lists and their stateless approach.

Since animation, swiping and other capabilities that are so common in mobile are so hard to accomplish with lists we see no actual reason to use them.

Why Isn’t List Deprecated?

We deprecated ContainerList which performs really badly and has some inherent complexity issues. List has some unique use cases and is still used all over Codename One.

MultiList is a reasonable version of List that is far easier to use without most of the pains related to renderer configuration.

There are cases where using List or MultiList is justified, they are just rarer than usual hence our recommendation.

MVC In Lists

A Codename One List doesn’t contain components, but rather arbitrary data; this seems odd at first but makes sense. If you want a list to contain components, just use a Container.

The advantage of using a List in this way is that we can display it in many ways (e.g. fixed focus positions, horizontally, etc.), and that we can have more than a million entries without performance overhead. We can also do some pretty nifty things, like filtering the list on the fly or fetching it dynamically from the Internet as the user scrolls down the list. To achieve these things the list uses two interfaces: ListModel and ListCellRenderer. List model represents the data; its responsibility is to return the arbitrary object within the list at a given offset. Its second responsibility is to notify the list when the data changes, so the list can refresh.

Tip
Think of the model as an array of objects that can notify you when it changes.

The list renderer is like a rubber stamp that knows how to draw an object from the model, it’s called many times per entry in an animated list and must be very fast. Unlike standard Codename One components, it is only used to draw the entry in the model and is immediately discarded, hence it has no memory overhead, but if it takes too long to process a model value it can be a big bottleneck!

Tip
Think of the render as a translation layer that takes the "data" from the model and translates it to a visual representation.

This is all very generic, but a bit too much for most, doing a list "properly" requires some understanding. The main source of confusion for developers is the stateless nature of the list and the transfer of state to the model (e.g. a checkbox list needs to listen to action events on the list and update the model, in order for the renderer to display that state). Once you understand that it’s easy.

Understanding MVC

Let’s recap, what is MVC:

  • Model - Represents the data for the component (list), the model can tell us exactly how many items are in it and which item resides at a given offset within the model. This differs from a simple Vector (or array), since all access to the model is controlled (the interface is simpler), and unlike a Vector/Array, the model can notify us of changes that occur within it.

  • View - The view draws the content of the model. It is a "dumb" layer that has no notion of what is displayed and only knows how to draw. It tracks changes in the model (the model sends events) and redraws itself when it changes.

  • Controller - The controller accepts user input and performs changes to the model, which in turn cause the view to refresh.

Image by RegisFrey - Own work Public Domain https://commons.wikimedia.org/w/index.php?curid=10298177
Figure 38. Typical MVC Diagram [2]

Codename One’s List component uses the MVC paradigm in its implementation. List itself is the Controller (with a bit of the View mixed in). The ListCellRenderer interface is the rest of the View and the ListModel is (you guessed it by now) the Model.

When the list is painted, it iterates over the visible elements in the model and asks the model for the data, it then draws them using the renderer. Notice that because of this both the model and the renderer must be REALLY fast and that’s hard.

Why is this useful?

Since the model is a lightweight interface, it can be implemented by you and replaced in runtime if so desired, this allows several use cases:

  1. A list can contain thousands of entries but only load the portion visible to the user. Since the model will only be queried for the elements that are visible to the user, it won’t need to load the large data set into memory until the user starts scrolling down (at which point other elements may be offloaded from memory).

  2. A list can cache efficiently. E.g. a list can mirror data from the server into local RAM without actually downloading all the data. Data can also be mirrored from storage for better performance and discarded for better memory utilization.

  3. The is no need for state copying. Since renderers allow us to display any object type, the list model interface can be implemented by the application’s data structures (e.g. persistence/network engine), which would return internal application data structures saving you the need of copying application state into a list specific data structure. Note that this advantage only applies with a custom renderer which is pretty difficult to get right.

  4. Using the proxy pattern we can layer logic such as filtering, sorting, caching, etc. on top of existing models without changing the model source code.

  5. We can reuse generic models for several views, e.g. a model that fetches data from the server can be initialized with different arguments, to fetch different data for different views. View objects in different Forms can display the same model instance in different view instances, thus they would update automatically when we change one global model.

Most of these use cases work best for lists that grow to a larger size, or represent complex data, which is what the list object is designed to do.

Important - Lists & Layout Managers

Usually when working with lists, you want the list to handle the scrolling (otherwise it will perform badly). This means you should place the list in a non-scrollable container (no parent can be scrollable), notice that the content pane is scrollable by default, so you should disable that.

It is also recommended to place the list in the CENTER location of a BorderLayout to produce the most effective results. e.g.:

form.setScrollable(false);
form.setLayout(new BorderLayout());
form.add(BorderLayout.CENTER, myList);

MultiList & DefaultListModel

So after this long start lets show the first sample of creating a list using the MultiList.

The MultiList is a preconfigured list that contains a ready made renderer with defaults that make sense for the most common use cases. It still retains most of the power available to the List component but reduces the complexity of one of the hardest things to grasp for most developers: rendering.

The full power of the ListModel is still available and allows you to create a million entry list with just a few lines of code. However the objects that the model returns should always be in the form of Map objects and not an arbitrary object like the standard List allows.

Here is a simple example of a MultiList containing a highly popular subject matter:

Form hi = new Form("MultiList", new BorderLayout());

ArrayList<Map<String, Object>> data = new ArrayList<>();

data.add(createListEntry("A Game of Thrones", "1996"));
data.add(createListEntry("A Clash Of Kings", "1998"));
data.add(createListEntry("A Storm Of Swords", "2000"));
data.add(createListEntry("A Feast For Crows", "2005"));
data.add(createListEntry("A Dance With Dragons", "2011"));
data.add(createListEntry("The Winds of Winter", "2016 (please, please, please)"));
data.add(createListEntry("A Dream of Spring", "Ugh"));

DefaultListModel<Map<String, Object>> model = new DefaultListModel<>(data);
MultiList ml = new MultiList(model);
hi.add(BorderLayout.CENTER, ml);
Basic usage of the MultiList and DefaultListModel
Figure 39. Basic usage of the MultiList & DefaultListModel]

createListEntry is relatively trivial:

private Map<String, Object> createListEntry(String name, String date) {
    Map<String, Object> entry = new HashMap<>();
    entry.put("Line1", name);
    entry.put("Line2", date);
    return entry;
}

There is one major piece missing here and that is the cover images for the books. A simple approach would be to just place the image objects into the entries using the "icon" property as such:

private Map<String, Object> createListEntry(String name, String date, Image cover) {
    Map<String, Object> entry = new HashMap<>();
    entry.put("Line1", name);
    entry.put("Line2", date);
    entry.put("icon", cover);
    return entry;
}
With cover images in place
Figure 40. With cover images in place
Tip
Since the MultiList uses the GenericListCellRenderer internally we can use URLImage to dynamically fetch the data. This is discussed in the graphics section of this guide.
Going Further With the ListModel

Lets assume that GRRM was really prolific and wrote 1 million books. The default list model won’t make much sense in that case but we would still be able to render everything in a list model.

We’ll fake it a bit but notice that 1M components won’t be created even if we somehow scroll all the way down…​

The ListModel interface can be implemented by anyone in this case we just did a really stupid simple implementation:

class GRMMModel implements ListModel<Map<String,Object>> {
    @Override
    public Map<String, Object> getItemAt(int index) {
        int idx = index % 7;
        switch(idx) {
            case 0:
                return createListEntry("A Game of Thrones " + index, "1996");
            case 1:
                return createListEntry("A Clash Of Kings " + index, "1998");
            case 2:
                return createListEntry("A Storm Of Swords " + index, "2000");
            case 3:
                return createListEntry("A Feast For Crows " + index, "2005");
            case 4:
                return createListEntry("A Dance With Dragons " + index, "2011");
            case 5:
                return createListEntry("The Winds of Winter " + index, "2016 (please, please, please)");
            default:
                return createListEntry("A Dream of Spring " + index, "Ugh");
        }
    }

    @Override
    public int getSize() {
        return 1000000;
    }

    @Override
    public int getSelectedIndex() {
        return 0;
    }

    @Override
    public void setSelectedIndex(int index) {
    }

    @Override
    public void addDataChangedListener(DataChangedListener l) {
    }

    @Override
    public void removeDataChangedListener(DataChangedListener l) {
    }

    @Override
    public void addSelectionListener(SelectionListener l) {
    }

    @Override
    public void removeSelectionListener(SelectionListener l) {
    }

    @Override
    public void addItem(Map<String, Object> item) {
    }

    @Override
    public void removeItem(int index) {
    }
}

We can now replace the existing model by removing all the model related logic and changing the constructor call as such:

MultiList ml = new MultiList(new GRMMModel());
It took ages to scroll this far…​ This goes to a million…​
Figure 41. It took ages to scroll this far…​ This goes to a million…​

List Cell Renderer

The Renderer is a simple interface with 2 methods:

public interface ListCellRenderer {
   //This method is called by the List for each item, when the List paints itself.
   public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected);

   //This method returns the List animated focus which is animated when list selection changes
   public Component getListFocusComponent(List list);
}

The most simple/naive implementation may choose to implement the renderer as follows:

public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected){
     return new Label(value.toString());
}

public Component getListFocusComponent(List list){
     return null;
}

This will compile and work, but won’t give you much, notice that you won’t see the List selection move on the List, this is just because the renderer returns a Label with the same style regardless if it’s selected or not.

Now Let’s try to make it a bit more useful.

public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected){
      Label l = new Label(value.toString());
if (isSelected) {
             l.setFocus(true);
             l.getAllStyles().setBgTransparency(100);
         } else {
             l.setFocus(false);
             l.getAllStyles().setBgTransparency(0);
        }
        return l;
}   public Component getListFocusComponent(List list){
      return null;
}

In this renderer we set the Label.setFocus(true) if it’s selected, calling to this method doesn’t really give the focus to the Label, it simply renders the label as selected.

Then we invoke Label.getAllStyles().setBgTransparency(100) to give the selection semi transparency, and 0 for full transparency if not selected.

That is still not very efficient because we create a new Label each time the method is invoked.

To make the code tighter, keep a reference to the Component or extend it as DefaultListCellRenderer does.

class MyRenderer extends Label implements ListCellRenderer {
    public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected){
        setText(value.toString());
        if (isSelected) {
            setFocus(true);
            getAllStyles().setBgTransparency(100);
        } else {
            setFocus(false);
            getAllStyles().setBgTransparency(0);
        }
        return this;
        }
    }
}

Now Let’s have a look at a more advanced Renderer.

class ContactsRenderer extends Container implements ListCellRenderer {

 private Label name = new Label("");
 private Label email = new Label("");
 private Label pic = new Label("");

 private Label focus = new Label("");

 public ContactsRenderer() {
     setLayout(new BorderLayout());
     addComponent(BorderLayout.WEST, pic);
     Container cnt = new Container(new BoxLayout(BoxLayout.Y_AXIS));
     name.getAllStyles().setBgTransparency(0);
     name.getAllStyles().setFont(Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
     email.getAllStyles().setBgTransparency(0);
     cnt.addComponent(name);
     cnt.addComponent(email);
     addComponent(BorderLayout.CENTER, cnt);

     focus.getStyle().setBgTransparency(100);
 }

 public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected) {

     Contact person = (Contact) value;
     name.setText(person.getName());
     email.setText(person.getEmail());
     pic.setIcon(person.getPic());
     return this;
 }

 public Component getListFocusComponent(List list) {
     return focus;
 }
}

In this renderer we want to render a Contact object to the Screen, we build the Component in the constructor and in the getListCellRendererComponent we simply update the Labels' texts according to the Contact object.

Notice that in this renderer we return a focus Label with semi transparency, as mentioned before, the focus component can be modified within this method.

For example, I can modify the focus Component to have an icon.

focus.getAllStyles().setBgTransparency(100);
try {
    focus.setIcon(Image.createImage("/duke.png"));
    focus.setAlignment(Component.RIGHT);
} catch (IOException ex) {
    ex.printStackTrace();
}

Generic List Cell Renderer

As part of the GUI builder work, we needed a way to customize rendering for a List, but the renderer/model approach seemed impossible to adapt to a GUI builder (it seems the Swing GUI builders had a similar issue). Our solution was to introduce the GenericListCellRenderer, which while introducing limitations and implementation requirements still manages to make life easier, both in the GUI builder and outside of it.

GenericListCellRenderer is a renderer designed to be as simple to use as a Component-Container hierarchy, we effectively crammed most of the common renderer use cases into one class. To enable that, we need to know the content of the objects within the model, so the GenericListCellRenderer assumes the model contains only Map objects. Since Maps can contain arbitrary data the list model is still quite generic and allows storing application specific data. Furthermore a Map can still be derived and extended to provide domain specific business logic.

The GenericListCellRenderer accepts two container instances (more later on why at least two, and not one), which it maps to individual Map entries within the model, by finding the appropriate components within the given container hierarchy. Components are mapped to the Map entries based on the name property of the component (getName/setName) and the key/value within the Map, e.g.:

For a model that contains a Map entry like this:

"Foo": "Bar"
"X": "Y"
"Not": "Applicable"
"Number": Integer(1)

A renderer will loop over the component hierarchy in the container, searching for components whose name matches Foo, X, Not, and Number, and assigning the appropriate value to them.

Tip
You can also use image objects as values, and they will be assigned to labels as expected. However, you can’t assign both an image and a text to a single label, since the key will be taken. That isn’t a big problem, since two labels can be used quite easily in such a renderer.

To make matters even more attractive the renderer seamlessly supports list tickering when appropriate, and if a CheckBox appears within the renderer, it will toggle a boolean flag within the Map seamlessly.

One issue that crops up with this approach is that, if a value is missing from the Map, it is treated as empty and the component is reset.

This can pose an issue if we hardcode an image or text within the renderer and we don’t want them replaced (e.g. an arrow graphic on a Label within the renderer). The solution for this is to name the component with Fixed in the end of the name, e.g. HardcodedIconFixed.

Naming a component within the renderer with $number will automatically set it as a counter component for the offset of the component within the list.

Styling the GenericListCellRenderer is slightly different, the renderer uses the UIID of the Container passed to the generic list cell renderer, and the background focus uses that same UIID with the word "Focus" appended to it.

It is important to notice that the generic list cell renderer will grant focus to the child components of the selected entry if they are focusable, thus changing the style of said entries. E.g. a Container might have a child Label that has one style when the parent container is unselected and another when it’s selected (focused), this can be easily achieved by defining the label as focusable. Notice that the component will never receive direct focus, since it is still part of a renderer.

Last but not least, the generic list cell renderer accepts two or four instances of a Container, rather than the obvious choice of accepting only one instance. This allows the renderer to treat the selected entry differently, which is especially important to tickering, although it’s also useful for the fisheye effect [3]. Since it might not be practical to seamlessly clone the Container for the renderer’s needs, Codename One expects the developer to provide two separate instances, they can be identical in all respects, but they must be separate instances for tickering to work. The renderer also allows for a fisheye effect, where the selected entry is actually different from the unselected entry in its structure, it also allows for a pinstripe effect, where odd/even rows have different styles (this is accomplished by providing 4 instances of the containers selected/unselected for odd/even).

The best way to learn about the generic list cell renderer and the Map model is by playing with them in the old GUI builder. Notice they can be used in code without any dependency on the GUI builder and can be quite useful at that.

Here is a simple example of a list with checkboxes that gets updated automatically:

com.codename1.ui.List list = new com.codename1.ui.List(createGenericListCellRendererModelData());
list.setRenderer(new GenericListCellRenderer(createGenericRendererContainer(), createGenericRendererContainer()));


private Container createGenericRendererContainer() {
    Label name = new Label();
    name.setFocusable(true);
    name.setName("Name");
    Label surname = new Label();
    surname.setFocusable(true);
    surname.setName("Surname");
    CheckBox selected = new CheckBox();
    selected.setName("Selected");
    selected.setFocusable(true);
    Container c = BorderLayout.center(name).
            add(BorderLayout.SOUTH, surname).
            add(BorderLayout.WEST, selected);
    c.setUIID("ListRenderer");
    return c;
}

private Object[] createGenericListCellRendererModelData() {
    Map<String,Object>[] data = new HashMap[5];
    data[0] = new HashMap<>();
    data[0].put("Name", "Shai");
    data[0].put("Surname", "Almog");
    data[0].put("Selected", Boolean.TRUE);
    data[1] = new HashMap<>();
    data[1].put("Name", "Chen");
    data[1].put("Surname", "Fishbein");
    data[1].put("Selected", Boolean.TRUE);
    data[2] = new HashMap<>();
    data[2].put("Name", "Ofir");
    data[2].put("Surname", "Leitner");
    data[3] = new HashMap<>();
    data[3].put("Name", "Yaniv");
    data[3].put("Surname", "Vakarat");
    data[4] = new HashMap<>();
    data[4].put("Name", "Meirav");
    data[4].put("Surname", "Nachmanovitch");
    return data;
}
GenericListCellRenderer demo code
Figure 42. GenericListCellRenderer demo code
Custom UIID Of Entry in GenenricListCellRenderer/MultiList

With MultiList/GenenricListCellRenderer one of the common issues is making a UI where a specific component within the list renderer has a different UIID style based on data. E.g. this can be helpful to mark a label within the list as red, for instance, in the case of a list of monetary transactions.

This can be achieved with a custom renderer, but that is a pretty difficult task.
GenericListCellRenderer (MultiList uses GenericListCellRenderer internally) has another option.

Normally, to build the model for a renderer of this type, we use something like:

map.put("componentName", "Component Value");

What if we want componentName to be red? Just use:

map.put("componentName_uiid", "red");

This will apply the UIID "red" to the component, which you can then style in the theme. Notice that once you start doing this, you need to define this entry for all entries, e.g.:

map.put("componentName_uiid", "blue");

Otherwise the component will stay red for the next entry (since the renderer acts like a rubber stamp).

Rendering Prototype

Because of the rendering architecture of a List its pretty hard to calculate the right preferred size for such a component. The default behavior includes querying a few entries from the model then constructing their renderers to get a "sample" of the preferred size value.

As you might guess this triggers a performance penalty that is paid with every reflow of the UI. The solution is to use setRenderingPrototype.

setRenderingPrototype accepts a "fake" value that represents a reasonably large amount of data and it will be used to calculate the preferred size. E.g. for a multiList that should render 2 lines of text with 20 characters and a 5mm square icon I can do something like this:

Map<String, Object> proto = new HashMap<>();
map.put("Line1", "WWWWWWWWWWWWWWWWWWWW");
map.put("Line2", "WWWWWWWWWWWWWWWWWWWW");
int mm5 = Display.getInstance().convertToPixels(5, true);
map.put("icon", Image.create(mm5, mm5));
myMultiList.setRenderingPrototype(map);

ComboBox

The ComboBox is a specialization of List that displays a single selected entry. When clicking that entry a popup is presented allowing the user to pick an entry from the full list of entries.

Tip
The ComboBox UI paradigm isn’t as common on OS’s such as iOS where there is no native equivalent to it. We recommend using either the Picker class or the AutoCompleteTextField.

ComboBox is notoriously hard to style properly as it relies on a complex dynamic of popup renderer and instantly visible renderer. The UIID for the ComboBox is ComboBox however if you set it to something else all the other UIID’s will also change their prefix. E.g. the ComboBoxPopup UIID will become MyNewUIIDPopup.

The combo box defines the following UIID’s by default:

  • ComboBox

  • ComboBoxItem

  • ComboBoxFocus

  • PopupContentPane

  • PopupItem

  • PopupFocus

The ComboBox also defines theme constants that allow some native themes to manipulate its behavior e.g.:

  • popupTitleBool - shows the "label for" value as the title of the popup dialog

  • popupCancelBodyBool - Adds a cancel button into the popup dialog

  • centeredPopupBool - shows the popup dialog in the center of the screen instead of under the popup

  • otherPopupRendererBool - Uses a different list cell render for the popup than the one used for the ComboBox itself. When this is false PopupItem & PopupFocus become irrelevant. Notice that the Android native theme defines this to true.

Since a ComboBox is really a List you can use everything we learned about a List to build a ComboBox including models, GenericListCellRenderer etc.

E.g. the demo below uses the GRRM demo data from above to build a ComboBox:

Form hi = new Form("ComboBox", new BoxLayout(BoxLayout.Y_AXIS));
ComboBox<Map<String, Object>> combo = new ComboBox<> (
        createListEntry("A Game of Thrones", "1996"),
        createListEntry("A Clash Of Kings", "1998"),
        createListEntry("A Storm Of Swords", "2000"),
        createListEntry("A Feast For Crows", "2005"),
        createListEntry("A Dance With Dragons", "2011"),
        createListEntry("The Winds of Winter", "2016 (please, please, please)"),
        createListEntry("A Dream of Spring", "Ugh"));

combo.setRenderer(new GenericListCellRenderer<>(new MultiButton(), new MultiButton()));
GRRM ComboBox
Figure 43. GRRM ComboBox

Slider

A Slider is an empty component that can be filled horizontally or vertically to allow indicating progress, setting volume etc. It can be editable to allow the user to determine its value or none editable to just relay that information to the user. It can have a thumb on top to show its current position.

Slider
Figure 44. Slider

The interesting part about the slider is that it has two separate style UIID’s, Slider & SliderFull. The Slider UIID is always painted and SliderFull is rendered on top based on the amount the Slider should be filled.

Slider is highly customizable e.g. a slider can be used to replicate a 5 star rating widget as such. Notice that this slider will only work when its given its preferred size otherwise additional stars will appear. That’s why we place it within a FlowLayout:

Form hi = new Form("Star Slider", new BoxLayout(BoxLayout.Y_AXIS));
hi.add(FlowLayout.encloseCenter(createStarRankSlider()));
hi.show();

The slider itself is initialized in the code below. Notice that you can achieve almost the same result using a theme by setting the Slider & SliderFull UIID’s (both in selected & unselected states).

In fact doing this in the theme might be superior as you could use one image that contains 5 stars already and that way you won’t need the preferred size hack below:

private void initStarRankStyle(Style s, Image star) {
    s.setBackgroundType(Style.BACKGROUND_IMAGE_TILE_BOTH);
    s.setBorder(Border.createEmpty());
    s.setBgImage(star);
    s.setBgTransparency(0);
}

private Slider createStarRankSlider() {
    Slider starRank = new Slider();
    starRank.setEditable(true);
    starRank.setMinValue(0);
    starRank.setMaxValue(10);
    Font fnt = Font.createTrueTypeFont("native:MainLight", "native:MainLight").
            derive(Display.getInstance().convertToPixels(5, true), Font.STYLE_PLAIN);
    Style s = new Style(0xffff33, 0, fnt, (byte)0);
    Image fullStar = FontImage.createMaterial(FontImage.MATERIAL_STAR, s).toImage();
    s.setOpacity(100);
    s.setFgColor(0);
    Image emptyStar = FontImage.createMaterial(FontImage.MATERIAL_STAR, s).toImage();
    initStarRankStyle(starRank.getSliderEmptySelectedStyle(), emptyStar);
    initStarRankStyle(starRank.getSliderEmptyUnselectedStyle(), emptyStar);
    initStarRankStyle(starRank.getSliderFullSelectedStyle(), fullStar);
    initStarRankStyle(starRank.getSliderFullUnselectedStyle(), fullStar);
    starRank.setPreferredSize(new Dimension(fullStar.getWidth() * 5, fullStar.getHeight()));
    return starRank;
}
private void showStarPickingForm() {
  Form hi = new Form("Star Slider", new BoxLayout(BoxLayout.Y_AXIS));
  hi.add(FlowLayout.encloseCenter(createStarRankSlider()));
  hi.show();
}
Star Slider set to 5 (its between 0 - 10)
Figure 45. Star Slider set to 5 (its between 0 - 10)
Tip
This slider goes all the way to 0 stars which is less common. You can use a Label to represent the first star and have the slider work between 0 - 8 values to provide 4 additional stars.

Table

Table is a composite component (but it isn’t a lead component), this means it is a subclass of Container. It’s effectively built from multiple components.

Tip
Table is heavily based on the TableLayout class. It’s important to be familiar with that layout manager when working with Table.

Here is a trivial sample of using the standard table component:

Form hi = new Form("Table", new BorderLayout());
TableModel model = new DefaultTableModel(new String[] {"Col 1", "Col 2", "Col 3"}, new Object[][] {
    {"Row 1", "Row A", "Row X"},
    {"Row 2", "Row B", "Row Y"},
    {"Row 3", "Row C", "Row Z"},
    {"Row 4", "Row D", "Row K"},
    }) {
        public boolean isCellEditable(int row, int col) {
            return col != 0;
        }
    };
Table table = new Table(model);
hi.add(BorderLayout.CENTER, table);
hi.show();
Simple Table usage
Figure 46. Simple Table usage
Note
In the sample above the title area and first column aren’t editable. The other two columns are editable.

The more "interesting" capabilities of the Table class can be utilized via the TableLayout. You can use the layout constraints (also exposed in the table class) to create spanning and elaborate UI’s.

E.g.:

Form hi = new Form("Table", new BorderLayout());
TableModel model = new DefaultTableModel(new String[] {"Col 1", "Col 2", "Col 3"}, new Object[][] {
    {"Row 1", "Row A", "Row X"},
    {"Row 2", "Row B can now stretch", null},
    {"Row 3", "Row C", "Row Z"},
    {"Row 4", "Row D", "Row K"},
    }) {
        public boolean isCellEditable(int row, int col) {
            return col != 0;
        }
    };
Table table = new Table(model) {
    @Override
    protected TableLayout.Constraint createCellConstraint(Object value, int row, int column) {
        TableLayout.Constraint con =  super.createCellConstraint(value, row, column);
        if(row == 1 && column == 1) {
            con.setHorizontalSpan(2);
        }
        con.setWidthPercentage(33);
        return con;
    }
};
hi.add(BorderLayout.CENTER, table);
Table with spanning and fixed widths to 33%
Figure 47. Table with spanning & fixed widths to 33%

In order to customize the table cell behavior you can derive the Table to create a "renderer like" widget, however unlike the list this component is "kept" and used as is. This means you can bind listeners to this component and work with it as you would with any other component in Codename One.

So lets fix the example above to include far more capabilities:

Table table = new Table(model) {
    @Override
    protected Component createCell(Object value, int row, int column, boolean editable) { // (1)
        Component cell;
        if(row == 1 && column == 1) {  // (2)
            Picker p = new Picker();
            p.setType(Display.PICKER_TYPE_STRINGS);
            p.setStrings("Row B can now stretch", "This is a good value", "So Is This", "Better than text field");
            p.setSelectedString((String)value);  // (3)
            p.setUIID("TableCell");
            p.addActionListener((e) -> getModel().setValueAt(row, column, p.getSelectedString()));  // (4)
            cell = p;
        } else {
            cell = super.createCell(value, row, column, editable);
        }
        if(row > -1 && row % 2 == 0) {  // (5)
            // pinstripe effect
            cell.getAllStyles().setBgColor(0xeeeeee);
            cell.getAllStyles().setBgTransparency(255);
        }
        return cell;
    }

    @Override
    protected TableLayout.Constraint createCellConstraint(Object value, int row, int column) {
        TableLayout.Constraint con =  super.createCellConstraint(value, row, column);
        if(row == 1 && column == 1) {
            con.setHorizontalSpan(2);
        }
        con.setWidthPercentage(33);
        return con;
    }
};
  1. The createCell method is invoked once per component but is similar conceptually to the List renderer. Notice that it doesn’t return a "rubber stamp" though, it returns a full component.

  2. We only apply the picker to one cell for simplicities sake.

  3. We need to set the value of the component manually, this is crucial since the Table doesn’t "see" this.

  4. We need to track the event and update the model in this case as the Table isn’t aware of the data change.

  5. We set the "pinstripe" effect by coloring even rows. Notice that unlike renderers we only need to apply the coloring once as the Components are stateful.

Table with customize cells using the pinstripe effect
Figure 48. Table with customize cells using the pinstripe effect
Picker table cell during edit
Figure 49. Picker table cell during edit

To line wrap table cells we can just override the createCell method and return a TextArea instead of a TextField since the TextArea defaults to the multi-line behavior this should work seamlessly. E.g.:

Form hi = new Form("Table", new BorderLayout());
TableModel model = new DefaultTableModel(new String[] {"Col 1", "Col 2", "Col 3"}, new Object[][] {
    {"Row 1", "Row A", "Row X"},
    {"Row 2", "Row B can now stretch very long line that should span multiple rows as much as possible", "Row Y"},
    {"Row 3", "Row C", "Row Z"},
    {"Row 4", "Row D", "Row K"},
    }) {
        public boolean isCellEditable(int row, int col) {
            return col != 0;
        }
    };
Table table = new Table(model) {
    @Override
    protected Component createCell(Object value, int row, int column, boolean editable) {
        TextArea ta = new TextArea((String)value);
        ta.setUIID("TableCell");
        return ta;
    }

    @Override
    protected TableLayout.Constraint createCellConstraint(Object value, int row, int column) {
        TableLayout.Constraint con =  super.createCellConstraint(value, row, column);
        con.setWidthPercentage(33);
        return con;
    }
};
hi.add(BorderLayout.CENTER, table);
hi.show();
Tip
Notice that we don’t really need to do anything else as binding to the TextArea is builtin to the Table.
Important
We must set the column width constraint when we want multi-line to work. Otherwise the preferred size of the column might be too wide and the remaining columns might not have space left.
Multiline table cell in portrait mode
Figure 50. Multiline table cell in portrait mode
Multiline table cell in landscape mode. Notice the cell row count adapts seamlessly
Figure 51. Multiline table cell in landscape mode. Notice the cell row count adapts seamlessly

Sorting Tables

Sorting tables by clicking the titles is something that should generally work out of the box by using an API like setSortSupported(true).

Form hi = new Form("Table", new BorderLayout());
TableModel model = new DefaultTableModel(new String[] {"Col 1", "Col 2", "Col 3"}, new Object[][] {
    {"Row 1", "Row A", 1},
    {"Row 2", "Row B", 4},
    {"Row 3", "Row C", 7.5},
    {"Row 4", "Row D", 2.24},
    });
Table table = new Table(model);
table.setSortSupported(true);
hi.add(BorderLayout.CENTER, table);
hi.add(NORTH, new Button("Button"));
hi.show();

Notice this works with numbers, Strings and might work with dates but you can generally support any object type by overriding the method protected Comparator createColumnSortComparator(int column) which should return a comparator for your custom object type in the column.

Tree

Tree allows displaying hierarchical data such as folders and files in a collapsible/expandable UI. Like the Table it is a composite component (but it isn’t a lead component). Like the Table it works in consort with a model to construct its user interface on the fly but doesn’t use a stateless renderer (as List does).

The data of the Tree arrives from a model model e.g. this:

class StringArrayTreeModel implements TreeModel {
    String[][] arr = new String[][] {
            {"Colors", "Letters", "Numbers"},
            {"Red", "Green", "Blue"},
            {"A", "B", "C"},
            {"1", "2", "3"}
        };

    public Vector getChildren(Object parent) {
        if(parent == null) {
            Vector v = new Vector();
            for(int iter = 0 ; iter < arr[0].length ; iter++) {
                v.addElement(arr[0][iter]);
            }
            return v;
        }
        Vector v = new Vector();
        for(int iter = 0 ; iter < arr[0].length ; iter++) {
            if(parent == arr[0][iter]) {
                if(arr.length > iter + 1 && arr[iter + 1] != null) {
                    for(int i = 0 ; i < arr[iter + 1].length ; i++) {
                        v.addElement(arr[iter + 1][i]);
                    }
                }
            }
        }
        return v;
    }

    public boolean isLeaf(Object node) {
        Vector v = getChildren(node);
        return v == null || v.size() == 0;
    }
}

Tree dt = new Tree(new StringArrayTreeModel());

Will result in this:

Tree
Figure 52. Tree
Note
Since Tree is hierarchy based we can’t have a simple model like we have for the Table as deep hierarchy is harder to represent with arrays.

A more practical "real world" example would be working with XML data. We can use something like this to show an XML Tree:

Form hi = new Form("XML Tree", new BorderLayout());
InputStream is = Display.getInstance().getResourceAsStream(getClass(), "/build.xml");
try(Reader r = new InputStreamReader(is, "UTF-8");) {
    Element e = new XMLParser().parse(r);
    Tree xmlTree = new Tree(new XMLTreeModel(e)) {
        @Override
        protected String childToDisplayLabel(Object child) {
            if(child instanceof Element) {
                return ((Element)child).getTagName();
            }
            return child.toString();
        }
    };
    hi.add(BorderLayout.CENTER, xmlTree);
} catch(IOException err) {
    Log.e(err);
}
Note
The try(Stream) syntax is a try with resources clogic that implicitly closes the stream.
XML Tree
Figure 53. XML Tree

The model for the XML hierarchy is implemented as such:

class XMLTreeModel implements TreeModel {
    private Element root;
    public XMLTreeModel(Element e) {
        root = e;
    }

    public Vector getChildren(Object parent) {
        if(parent == null) {
            Vector c = new Vector();
            c.addElement(root);
            return c;
        }
        Vector result = new Vector();
        Element e = (Element)parent;
        for(int iter = 0 ; iter < e.getNumChildren() ; iter++) {
            result.addElement(e.getChildAt(iter));
        }
        return result;
    }

    public boolean isLeaf(Object node) {
        Element e = (Element)node;
        return e.getNumChildren() == 0;
    }
}

ShareButton

ShareButton is a button you can add into the UI to let a user share an image or block of text.

The ShareButton uses a set of predefined share options on the simulator. On Android & iOS the ShareButton is mapped to the OS native sharing functionality and can share the image/text with the services configured on the device (e.g. Twitter, Facebook etc.).

Tip
Sharing text is trivial but to share an image we need to save it to the FileSystemStorage. Notice that saving to Storage won’t work!

In the sample code below we take a screenshot which is saved to FileSystemStorage for sharing:

Form hi = new Form("ShareButton");
ShareButton sb = new ShareButton();
sb.setText("Share Screenshot");
hi.add(sb);

Image screenshot = Image.createImage(hi.getWidth(), hi.getHeight());
hi.revalidate();
hi.setVisible(true);
hi.paintComponent(screenshot.getGraphics(), true);

String imageFile = FileSystemStorage.getInstance().getAppHomePath() + "screenshot.png";
try(OutputStream os = FileSystemStorage.getInstance().openOutputStream(imageFile);) {
    ImageIO.getImageIO().save(screenshot, os, ImageIO.FORMAT_PNG, 1);
} catch(IOException err) {
    Log.e(err);
}
sb.setImageToShare(imageFile, "image/png");
The share button running on the simulator
Figure 54. The share button running on the simulator
Important
ShareButton behaves very differently on the device…​
The share button running on the Android device and screenshot sent into twitter
Figure 55. The share button running on the Android device and screenshot sent into twitter
Important
The ShareButton features some share service classes to allow plugging in additional share services. However, this functionality is only relevant to devices where native sharing isn’t supported. So this code isn’t used on iOS/Android…​

Tabs

The Tabs Container arranges components into groups within "tabbed" containers. Tabs is a container type that allows leafing through its children using labeled toggle buttons. The tabs can be placed in multiple different ways (top, bottom, left or right) with the default being determined by the platform. This class also allows swiping between components to leaf between said tabs (for this purpose the tabs themselves can also be hidden).

Since Tabs are a Container its a common mistake to try and add a Tab using the add method. That method won’t work since a Tab can have both an Image and text String associated with it.

Form hi = new Form("Tabs", new BorderLayout());

Tabs t = new Tabs();
Style s = UIManager.getInstance().getComponentStyle("Tab");
FontImage icon1 = FontImage.createMaterial(FontImage.MATERIAL_QUESTION_ANSWER, s);

Container container1 = BoxLayout.encloseY(new Label("Label1"), new Label("Label2"));
t.addTab("Tab1", icon1, container1);
t.addTab("Tab2", new SpanLabel("Some text directly in the tab"));

hi.add(BorderLayout.CENTER, t);
Simple usage of Tabs
Figure 56. Simple usage of Tabs

A common usage for Tabs is the the swipe to proceed effect which is very common in iOS applications. In the code below we use RadioButton and LayeredLayout with hidden tabs to produce that effect:

Form hi = new Form("Swipe Tabs", new LayeredLayout());
Tabs t = new Tabs();
t.hideTabs();

Style s = UIManager.getInstance().getComponentStyle("Button");
FontImage radioEmptyImage = FontImage.createMaterial(FontImage.MATERIAL_RADIO_BUTTON_UNCHECKED, s);
FontImage radioFullImage = FontImage.createMaterial(FontImage.MATERIAL_RADIO_BUTTON_CHECKED, s);
((DefaultLookAndFeel)UIManager.getInstance().getLookAndFeel()).setRadioButtonImages(radioFullImage, radioEmptyImage, radioFullImage, radioEmptyImage);

Container container1 = BoxLayout.encloseY(new Label("Swipe the tab to see more"),
        new Label("You can put anything here"));
t.addTab("Tab1", container1);
t.addTab("Tab2", new SpanLabel("Some text directly in the tab"));

RadioButton firstTab = new RadioButton("");
RadioButton secondTab = new RadioButton("");
firstTab.setUIID("Container");
secondTab.setUIID("Container");
new ButtonGroup(firstTab, secondTab);
firstTab.setSelected(true);
Container tabsFlow = FlowLayout.encloseCenter(firstTab, secondTab);

hi.add(t);
hi.add(BorderLayout.south(tabsFlow));

t.addSelectionListener((i1, i2) -> {
    switch(i2) {
        case 0:
            if(!firstTab.isSelected()) {
                firstTab.setSelected(true);
            }
            break;
        case 1:
            if(!secondTab.isSelected()) {
                secondTab.setSelected(true);
            }
            break;
     }
});
Swipeable Tabs with an iOS carousel effect page 1
Figure 57. Swipeable Tabs with an iOS carousel effect page 1
Swipeable Tabs with an iOS carousel effect page 2
Figure 58. Swipeable Tabs with an iOS carousel effect page 2
Note
Notice that we used setRadioButtonImages to explicitly set the radio button images to the look we want for the carousel.

MediaManager & MediaPlayer

Important
MediaPlayer is a peer component, understanding this is crucial if your application depends on such a component. You can learn about peer components and their issues here.

The MediaPlayer allows you to control video playback. To use the MediaPlayer we need to first load the Media object from the MediaManager.

The MediaManager is the core class responsible for media interaction in Codename One.

Tip
You should also check out the Capture class for things that aren’t covered by the MediaManager.

In the demo code below we use the gallery functionality to pick a video from the device’s video gallery.

final Form hi = new Form("MediaPlayer", new BorderLayout());
hi.setToolbar(new Toolbar());
Style s = UIManager.getInstance().getComponentStyle("Title");
FontImage icon = FontImage.createMaterial(FontImage.MATERIAL_VIDEO_LIBRARY, s);
hi.getToolbar().addCommandToRightBar("", icon, (evt) -> {
    Display.getInstance().openGallery((e) -> {
        if(e != null && e.getSource() != null) {
            String file = (String)e.getSource();
            try {
                Media video = MediaManager.createMedia(file, true);
                hi.removeAll();
                hi.add(BorderLayout.CENTER, new MediaPlayer(video));
                hi.revalidate();
            } catch(IOException err) {
                Log.e(err);
            }
        }
    }, Display.GALLERY_VIDEO);
});
hi.show();
Video playback running on the simulator
Figure 59. Video playback running on the simulator
Video playback running on an Android device. Notice the native playback controls that appear when the video is tapped
Figure 60. Video playback running on an Android device. Notice the native playback controls that appear when the video is tapped
Important
Video playback in the simulator will only work with JavaFX enabled. This is the default for Java 8 or newer so we recommend using that.

ImageViewer

The ImageViewer allows us to inspect, zoom and pan into an image. It also allows swiping between images if you have a set of images (using an image list model).

Important
The ImageViewer is a complex rich component designed for user interaction. If you just want to display an image use Label if you want the image to scale seamlessly use ScaleImageLabel.

You can use the ImageViewer as a tool to view a single image which allows you to zoom in/out to that image as such:

Form hi = new Form("ImageViewer", new BorderLayout());
ImageViewer iv = new ImageViewer(duke);
hi.add(BorderLayout.CENTER, iv);
Tip
You can simulate pinch to zoom on the simulator by dragging the right button away from the top left corner to zoom in and towards the top left corner to zoom out. On Mac touchpads you can drag two fingers to achieve that.
ImageViewer as the demo loads with the image from the default icon
Figure 61. ImageViewer as the demo loads with the image from the default icon
ImageViewer zoomed in
Figure 62. ImageViewer zoomed in

We can work with a list of images to produce a swiping effect for the image viewer where you can swipe from one image to the next and also zoom in/out on a specific image:

Form hi = new Form("ImageViewer", new BorderLayout());

Image red = Image.createImage(100, 100, 0xffff0000);
Image green = Image.createImage(100, 100, 0xff00ff00);
Image blue = Image.createImage(100, 100, 0xff0000ff);
Image gray = Image.createImage(100, 100, 0xffcccccc);

ImageViewer iv = new ImageViewer(red);
iv.setImageList(new DefaultListModel<>(red, green, blue, gray));
hi.add(BorderLayout.CENTER, iv);
An ImageViewer with multiple elements is indistinguishable from a single ImageViewer with the exception of swipe
Figure 63. An ImageViewer with multiple elements is indistinguishable from a single ImageViewer with the exception of swipe

Notice that we use a ListModel to allow swiping between images.

Tip
EncodedImage’s aren’t always fully loaded and so when you swipe if the images are really large you might see delays!

You can dynamically download images directly into the ImageViewer with a custom list model like this:

Form hi = new Form("ImageViewer", new BorderLayout());
final EncodedImage placeholder = EncodedImage.createFromImage(
        FontImage.createMaterial(FontImage.MATERIAL_SYNC, s).
                scaled(300, 300), false);

class ImageList implements ListModel<Image> {
    private int selection;
    private String[] imageURLs = {
        "http://awoiaf.westeros.org/images/thumb/9/93/AGameOfThrones.jpg/300px-AGameOfThrones.jpg",
        "http://awoiaf.westeros.org/images/thumb/3/39/AClashOfKings.jpg/300px-AClashOfKings.jpg",
        "http://awoiaf.westeros.org/images/thumb/2/24/AStormOfSwords.jpg/300px-AStormOfSwords.jpg",
        "http://awoiaf.westeros.org/images/thumb/a/a3/AFeastForCrows.jpg/300px-AFeastForCrows.jpg",
        "http://awoiaf.westeros.org/images/7/79/ADanceWithDragons.jpg"
    };
    private Image[] images;
    private EventDispatcher listeners = new EventDispatcher();

    public ImageList() {
        this.images = new EncodedImage[imageURLs.length];
    }

    public Image getItemAt(final int index) {
        if(images[index] == null) {
            images[index] = placeholder;
            Util.downloadUrlToStorageInBackground(imageURLs[index], "list" + index, (e) -> {
                    try {
                        images[index] = EncodedImage.create(Storage.getInstance().createInputStream("list" + index));
                        listeners.fireDataChangeEvent(index, DataChangedListener.CHANGED);
                    } catch(IOException err) {
                        err.printStackTrace();
                    }
            });
        }
        return images[index];
    }

    public int getSize() {
        return imageURLs.length;
    }

    public int getSelectedIndex() {
        return selection;
    }

    public void setSelectedIndex(int index) {
        selection = index;
    }

    public void addDataChangedListener(DataChangedListener l) {
        listeners.addListener(l);
    }

    public void removeDataChangedListener(DataChangedListener l) {
        listeners.removeListener(l);
    }

    public void addSelectionListener(SelectionListener l) {
    }

    public void removeSelectionListener(SelectionListener l) {
    }

    public void addItem(Image item) {
    }

    public void removeItem(int index) {
    }
};

ImageList imodel = new ImageList();

ImageViewer iv = new ImageViewer(imodel.getItemAt(0));
iv.setImageList(imodel);
hi.add(BorderLayout.CENTER, iv);
Dynamically fetching an image URL from the internet
Figure 64. Dynamically fetching an image URL from the internet [4]

This fetches the images in the URL asynchronously and fires a data change event when the data arrives to automatically refresh the ImageViewer when that happens.

ScaleImageLabel & ScaleImageButton

ScaleImageLabel & ScaleImageButton allow us to position an image that will grow/shrink to fit available space. In that sense they differ from Label & Button which keeps the image at the same size.

Tip
The default UIID of ScaleImageLabel is “Label”, however the default UIID of ScaleImageButton is “ScaleImageButton”. The reasoning for the difference is that the “Button” UIID includes a border and a lot of legacy.

You can use ScaleImageLabel/ScaleImageButton interchangeably. The only major difference between these components is the buttons ability to handle click events/focus.

Here is a simple example that also shows the difference between the scale to fill and scale to fit modes:

TableLayout tl = new TableLayout(2, 2);
Form hi = new Form("ScaleImageButton/Label", tl);
Style s = UIManager.getInstance().getComponentStyle("Button");
Image icon = FontImage.createMaterial(FontImage.MATERIAL_WARNING, s);
ScaleImageLabel fillLabel = new ScaleImageLabel(icon);
fillLabel.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
ScaleImageButton fillButton = new ScaleImageButton(icon);
fillButton.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
hi.add(tl.createConstraint().widthPercentage(20), new ScaleImageButton(icon)).
        add(tl.createConstraint().widthPercentage(80), new ScaleImageLabel(icon)).
        add(fillLabel).
        add(fillButton);
hi.show();
ScaleImageLabel/Button the top row includes scale to fit versions (the default) whereas the bottom row includes the scale to fill versions
Figure 65. ScaleImageLabel/Button, the top row includes scale to fit versions (the default) whereas the bottom row includes the scale to fill versions
Warning
When styling these components keep in mind that changing attributes such as background behavior might cause an issue with their functionality or might not work.

Toolbar

The Toolbar API provides deep customization of the title bar area with more flexibility e.g. placing a TextField for search or buttons in arbitrary title area positions. The Toolbar API replicates some of the native functionality available on Android/iOS and integrates with features such as the side menu to provide very fine grained control over the title area behavior.

The Toolbar needs to be installed into the Form in order for it to work. You can setup the Toolbar in one of these three ways:

  1. form.setToolbar(new Toolbar()); - allows you to activate the Toolbar to a specific Form and not for the entire application

  2. Toolbar.setGlobalToolbar(true); - enables the Toolbar for all the forms in the app

  3. Theme constant globalToobarBool - this is equivalent to Toolbar.setGlobalToolbar(true);

The basic functionality of the Toolbar includes the ability to add a command to the following 4 places:

  • Left side of the title - addCommandToLeftBar

  • Right side of the title - addCommandToRightBar

  • Side menu bar (the drawer that opens when you click the icon on the top left or swipe the screen from left to right) - addCommandToSideMenu

  • Overflow menu (the menu that opens when you tap the 3 vertical dots in the top right corner) - addCommandToOverflowMenu

The code below provides a brief overview of these options:

Toolbar.setGlobalToolbar(true);

Form hi = new Form("Toolbar", new BoxLayout(BoxLayout.Y_AXIS));
hi.getToolbar().addCommandToLeftBar("Left", icon, (e) -> Log.p("Clicked"));
hi.getToolbar().addCommandToRightBar("Right", icon, (e) -> Log.p("Clicked"));
hi.getToolbar().addCommandToOverflowMenu("Overflow", icon, (e) -> Log.p("Clicked"));
hi.getToolbar().addCommandToSideMenu("Sidemenu", icon, (e) -> Log.p("Clicked"));
hi.show();
The Toolbar
Figure 66. The Toolbar
The default sidemenu of the Toolbar
Figure 67. The default sidemenu of the Toolbar
The overflow menu of the Toolbar
Figure 68. The overflow menu of the Toolbar

Normally you can just set a title with a String but if you would want the component to be a text field or a multi line label you can use setTitleComponent(Component) which allows you to install any component into the title area.

Tip
The code below demonstrates searching using custom code however the Toolbar also has builtin support for search covered in the next section

The customization of the title area allows for some pretty powerful UI effects e.g. the code below allows searching dynamically within a set of entries and uses some very neat tricks:

Toolbar.setGlobalToolbar(true);
Style s = UIManager.getInstance().getComponentStyle("Title");

Form hi = new Form("Toolbar", new BoxLayout(BoxLayout.Y_AXIS));
TextField searchField = new TextField("", "Toolbar Search"); (1)
searchField.getHintLabel().setUIID("Title");
searchField.setUIID("Title");
searchField.getAllStyles().setAlignment(Component.LEFT);
hi.getToolbar().setTitleComponent(searchField);
FontImage searchIcon = FontImage.createMaterial(FontImage.MATERIAL_SEARCH, s);
searchField.addDataChangeListener((i1, i2) -> { (2)
    String t = searchField.getText();
    if(t.length() < 1) {
        for(Component cmp : hi.getContentPane()) {
            cmp.setHidden(false);
            cmp.setVisible(true);
        }
    } else {
        t = t.toLowerCase();
        for(Component cmp : hi.getContentPane()) {
            String val = null;
            if(cmp instanceof Label) {
                val = ((Label)cmp).getText();
            } else {
                if(cmp instanceof TextArea) {
                    val = ((TextArea)cmp).getText();
                } else {
                    val = (String)cmp.getPropertyValue("text");
                }
            }
            boolean show = val != null && val.toLowerCase().indexOf(t) > -1;
            cmp.setHidden(!show); (3)
            cmp.setVisible(show);
        }
    }
    hi.getContentPane().animateLayout(250);
});
hi.getToolbar().addCommandToRightBar("", searchIcon, (e) -> {
    searchField.startEditingAsync(); (4)
});

hi.add("A Game of Thrones").
        add("A Clash Of Kings").
        add("A Storm Of Swords").
        add("A Feast For Crows").
        add("A Dance With Dragons").
        add("The Winds of Winter").
        add("A Dream of Spring");
hi.show();
  1. We use a TextField the whole time and just style it to make it (and its hint) look like a regular title. An alternative way is to replace the title component dynamically.

  2. We use the DataChangeListener to update the search results as we type them.

  3. Hidden & Visible use the opposite flag values to say similar things (e.g. when hidden is set to false you would want to set visible to true).
    Visible indicates whether a component can be seen. It will still occupy the physical space on the screen even when it isn’t visible. Hidden will remove the space occupied by the component from the screen, but some code might still try to paint it. Normally, visible is redundant but we use it with hidden for good measure.

  4. The search button is totally unnecessary here. We can just click the TextField!
    However, that isn’t intuitive to most users so we added the button to start editing.

Search field within the toolbar
Figure 69. Search field within the toolbar
Search field after typing a couple of letters
Figure 70. Search field after typing a couple of letters

Search Mode

While you can implement search manually using the builtin search offers a simpler and more uniform UI.

Builtin toolbar search functionality
Figure 71. Builtin toolbar search functionality

You can customize the appearance of the search bar by using the UIID’s: ToolbarSearch, TextFieldSearch & TextHintSearch.

In the sample below we fetch all the contacts from the device and enable search thru them, notice it expects and image called duke.png which is really just the default Codename One icon renamed and placed in the src folder:

Image duke = null;
try {
    duke = Image.createImage("/duke.png");
} catch(IOException err) {
    Log.e(err);
}
int fiveMM = Display.getInstance().convertToPixels(5);
final Image finalDuke = duke.scaledWidth(fiveMM);
Toolbar.setGlobalToolbar(true);
Form hi = new Form("Search", BoxLayout.y());
hi.add(new InfiniteProgress());
Display.getInstance().scheduleBackgroundTask(()-> {
    // this will take a while...
    Contact[] cnts = Display.getInstance().getAllContacts(true, true, true, true, false, false);
    Display.getInstance().callSerially(() -> {
        hi.removeAll();
        for(Contact c : cnts) {
            MultiButton m = new MultiButton();
            m.setTextLine1(c.getDisplayName());
            m.setTextLine2(c.getPrimaryPhoneNumber());
            Image pic = c.getPhoto();
            if(pic != null) {
                m.setIcon(fill(pic, finalDuke.getWidth(), finalDuke.getHeight()));
            } else {
                m.setIcon(finalDuke);
            }
            hi.add(m);
        }
        hi.revalidate();
    });
});

hi.getToolbar().addSearchCommand(e -> {
    String text = (String)e.getSource();
    if(text == null || text.length() == 0) {
        // clear search
        for(Component cmp : hi.getContentPane()) {
            cmp.setHidden(false);
            cmp.setVisible(true);
        }
        hi.getContentPane().animateLayout(150);
    } else {
        text = text.toLowerCase();
        for(Component cmp : hi.getContentPane()) {
            MultiButton mb = (MultiButton)cmp;
            String line1 = mb.getTextLine1();
            String line2 = mb.getTextLine2();
            boolean show = line1 != null && line1.toLowerCase().indexOf(text) > -1 ||
                    line2 != null && line2.toLowerCase().indexOf(text) > -1;
            mb.setHidden(!show);
            mb.setVisible(show);
        }
        hi.getContentPane().animateLayout(150);
    }
}, 4);

hi.show();

South Component

A common feature in side menu bar is the ability to add a component to the "south" part of the side menu.

Notice that this feature only works with the on-top and permanent versions of the side menu and not with the legacy versions:

toolbar.setComponentToSideMenuSouth(myComponent);

This places the component below the side menu bar. Notice that this component controls its entire UIID & is separate from the SideNavigationPanel UIID so if you set that component you might want to place it within a container that has the SideNavigationPanel UIID so it will blend with the rest of the UI.

Title Animations

Modern UI’s often animate the title upon scrolling to balance the highly functional smaller title advantage with the gorgeous large image based title. This is pretty easy to do with the Toolbar API thru the Title animation API.

The code below shows off an attractive title based on a book by GRRM on top of text [5] that is scrollable. As the text is scrolled the title fades out.

Toolbar.setGlobalToolbar(true);

Form hi = new Form("Toolbar", new BoxLayout(BoxLayout.Y_AXIS));
EncodedImage placeholder = EncodedImage.createFromImage(Image.createImage(hi.getWidth(), hi.getWidth() / 5, 0xffff0000), true);
URLImage background = URLImage.createToStorage(placeholder, "400px-AGameOfThrones.jpg",
        "http://awoiaf.westeros.org/images/thumb/9/93/AGameOfThrones.jpg/400px-AGameOfThrones.jpg");
background.fetch();
Style stitle = hi.getToolbar().getTitleComponent().getUnselectedStyle();
stitle.setBgImage(background);
stitle.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
stitle.setPaddingUnit(Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS);
stitle.setPaddingTop(15);
SpanButton credit = new SpanButton("This excerpt is from A Wiki Of Ice And Fire. Please check it out by clicking here!");
credit.addActionListener((e) -> Display.getInstance().execute("http://awoiaf.westeros.org/index.php/A_Game_of_Thrones"));
hi.add(new SpanLabel("A Game of Thrones is the first of seven planned novels in A Song of Ice and Fire, an epic fantasy series by American author George R. R. Martin. It was first published on 6 August 1996. The novel was nominated for the 1998 Nebula Award and the 1997 World Fantasy Award,[1] and won the 1997 Locus Award.[2] The novella Blood of the Dragon, comprising the Daenerys Targaryen chapters from the novel, won the 1997 Hugo Award for Best Novella. ")).
        add(new Label("Plot introduction", "Heading")).
        add(new SpanLabel("A Game of Thrones is set in the Seven Kingdoms of Westeros, a land reminiscent of Medieval Europe. In Westeros the seasons last for years, sometimes decades, at a time.\n\n" +
            "Fifteen years prior to the novel, the Seven Kingdoms were torn apart by a civil war, known alternately as \"Robert's Rebellion\" and the \"War of the Usurper.\" Prince Rhaegar Targaryen kidnapped Lyanna Stark, arousing the ire of her family and of her betrothed, Lord Robert Baratheon (the war's titular rebel). The Mad King, Aerys II Targaryen, had Lyanna's father and eldest brother executed when they demanded her safe return. Her second brother, Eddard, joined his boyhood friend Robert Baratheon and Jon Arryn, with whom they had been fostered as children, in declaring war against the ruling Targaryen dynasty, securing the allegiances of House Tully and House Arryn through a network of dynastic marriages (Lord Eddard to Catelyn Tully and Lord Arryn to Lysa Tully). The powerful House Tyrell continued to support the King, but House Lannister and House Martell both stalled due to insults against their houses by the Targaryens. The civil war climaxed with the Battle of the Trident, when Prince Rhaegar was killed in battle by Robert Baratheon. The Lannisters finally agreed to support King Aerys, but then brutally... ")).
        add(credit);

ComponentAnimation title = hi.getToolbar().getTitleComponent().createStyleAnimation("Title", 200);
hi.getAnimationManager().onTitleScrollAnimation(title);
hi.show();
The Toolbar starts with the large URLImage fetched from the web
Figure 72. The Toolbar starts with the large URLImage fetched from the web
As we scroll down the image fades and the title shrinks in size returning to the default UIID look
Figure 73. As we scroll down the image fades and the title shrinks in size returning to the default UIID look
As scrolling continues the title reaches standard size
Figure 74. As scrolling continues the title reaches standard size

Almost all of the code above just creates the "look" of the application. The key piece of code above is this:

ComponentAnimation title = hi.getToolbar().getTitleComponent().createStyleAnimation("Title", 200);
hi.getAnimationManager().onTitleScrollAnimation(title);

In the first line we create a style animation that will translate the style from the current settings to the destination UIID (the first argument) within 200 pixels of scrolling. We then bind this animation to the title scrolling animation event.

BrowserComponent & WebBrowser

Important
BrowserComponent is a peer component, understanding this is crucial if your application depends on such a component. You can learn about peer components and their issues here.

The WebBrowser component shows the native device web browser when supported by the device and the HTMLComponent when the web browser isn’t supported on the given device. If you only intend to target smartphones you should use the BrowserComponent directly instead of the WebBrowser.

The BrowserComponent can point at an arbitrary URL to load it:

Form hi = new Form("Browser", new BorderLayout());
BrowserComponent browser = new BrowserComponent();
browser.setURL("https://www.codenameone.com/");
hi.add(BorderLayout.CENTER, browser);
Browser Component showing the Codename One website on the simulator
Figure 75. Browser Component showing the Codename One website on the simulator
Note
The scrollbars only appear in the simulator, device versions of the browser component act differently and support touch scrolling.
Important
A BrowserComponent should be in the center of a BorderLayout. Otherwise its preferred size might be zero before the HTML finishes loading/layout in the native layer and layout might be incorrect as a result.

You can use WebBrowser and BrowserComponent interchangeably for most basic usage. However, if you need access to JavaScript or native browser functionality then there is really no use in going thru the WebBrowser abstraction.

The BrowserComponent has full support for executing local web pages from within the jar. The basic support uses the jar:/// URL as such:

BrowserComponent wb = new BrowserComponent();
wb.setURL("jar:///Page.html");
Important
On Android a native indicator might show up when the web page is loading. This can be disabled using the Display.getInstance().setProperty("WebLoadingHidden", "true"); call. You only need to invoke this once.

BrowserComponent Hierarchy

When Codename One packages applications into native apps it hides a lot of details to make the process simpler. One of the things hidden is the fact that we aren’t dealing with a JAR anymore, so getResource/getResourceAsStream are problematic…​ Both of these API’s support hierarchies and a concept of package relativity both of which might not be supported on all OS’s.

Codename One has its own getResourceAsSteam in the Display class and that works just fine, but it requires that all files be in the src root directory.

Tip
That’s why we recommend that you place files inside res files. A resource file allows you to add arbitrary data files and you can have as many resource files as you need.

For web developers this isn’t enough since hierarchies are used often to represent the various dependencies, this means that many links & references are relative. To work with such hierarchies just place all of your resources in a hierarchy under the html package in the project source directory (src/html). The build server will tar the entire content of that package and add an html.tar file into the native package. This tar is seamlessly extracted on the device when you actually need the resources and only with new application versions (not on every launch). So assuming the resources are under the html root package they can be displayed with code like this:

try {
    browserComponent.setURLHierarchy("/htmlFile.html");
} catch(IOException err) {
    ...
}

Notice that the path is relative to the html directory and starts with / but inside the HTML files you should use relative (not absolute) paths.

Also notice that an IOException can be thrown due to the process of untarring. Its unlikely to happen but is entirely possible.

NavigationCallback

At the core of the BrowserComponent we have the BrowserNavigationCallback. It might not seem like the most important interface within the browser but it is the "glue" that allows the JavaScript code to communicate back into the Java layer.

You can bind a BrowserNavigationCallback by invoking setBrowserNavigationCallback on the BrowserComponent. At that point with every navigation within the browser the callback will get invoked.

Important
The shouldNavigate method from the BrowserNavigationCallback is invoked in a native thread and NOT ON THE EDT!
It is crucial that this method returns immediately and that it won’t do any changes on the UI.

The shouldNavigate indicates to the native code whether navigation should proceed or not. E.g. if a user clicks a specific link we might choose to do something in the Java code so we can just return false and block the navigation. We can invoke callSerially to do the actual task in the Java side.

Form hi = new Form("BrowserComponent", new BorderLayout());
BrowserComponent bc = new BrowserComponent();
bc.setPage( "<html lang=\"en\">\n" +
            "    <head>\n" +
            "        <meta charset=\"utf-8\">\n" +
            "        <script>\n" +
            "          function  fnc(message) {\n" +
            "         document.write(message);\n" +
            "            };\n" +
            "        </script>\n" +
            "    </head>\n" +
            "    <body >\n" +
            "        <p><a href=\"http://click\">Demo</a></p>\n" +
            "    </body>\n" +
            "</html>", null);
hi.add(BorderLayout.CENTER, bc);
bc.setBrowserNavigationCallback((url) -> {
    if(url.startsWith("http://click")) {
        Display.getInstance().callSerially(() -> bc.execute("fnc('<p>You clicked!</p>')"));
        return false;
    }
    return true;
});
Before the link is clicked for the "shouldNavigate" call
Figure 76. Before the link is clicked for the "shouldNavigate" call
After the link is clicked for the "shouldNavigate" call
Figure 77. After the link is clicked for the "shouldNavigate" call
Note
The JavaScript Bridge is implemented on top of the BrowserNavigationCallback.

JavaScript

Tip
The JavaScript bridge is sometimes confused with the JavaScript Port. The JavaScript bridge allows us to communicate with JavaScript from Java (and visa versa). The JavaScript port allows you to compile the Codename One application into a JavaScript application that runs in a standard web browser without code changes (think GWT without source changes and with thread support).+ We discuss the JavaScript port further later in the guide.

Codename One 4.0 introduced a new API for interacting with Javascript in Codename One. This API is part of the BrowserComponent class, and effectively replaces the com.codename1.javascript package, which is now deprecated.

So what was wrong with the old API?

The old API provided a synchronous wrapper around an inherently asynchronous process, and made extensive use of invokeAndBlock() underneath the covers. This resulted in a very nice API with high-level abstractions that played nicely with a synchronous programming model, but it came with a price-tag in terms of performance, complexity, and predictability. Let’s take a simple example, getting a reference to the “window” object:

JSObject window = ctx.get("window");

This code looks harmless enough, but this is actually quite expensive. It issues a command to the BrowserComponent, and uses invokeAndBlock() to wait for the command to go through and send back a response. invokeAndBlock() is a magical tool that allows you to “block” without blocking the EDT, but it has its costs, and shouldn’t be overused. Most of the Codename One APIs that use invokeAndBlock() indicate this in their name. E.g. Component.animateLayoutAndWait(). This gives you the expectation that this call could take some time, and helps to alert you to the underlying cost.

The problem with the ctx.get("window") call is that it looks the same as a call to Map.get(key). There’s no indication that this call is expensive and could take time. One call like this probably isn’t a big deal, but it doesn’t take long before you have dozens or even hundreds of calls like this littered throughout your codebase, and they can be hard to pick out.

The New API

The new API fully embraces the asynchronous nature of Javascript. It uses callbacks instead of return values, and provides convenience wrappers with the appropriate “AndWait()” naming convention to allow for synchronous usage. Let’s look at a simple example:

Note
In all of the sample code below, you can assume that variables named bc represent an instance of BrowserComponent.
bc.execute(
    "callback.onSuccess(3+4)",
    res -> Log.p("The result was "+res.getInt())
);

This code should output “The result was 7” to the console. It is fully asynchronous, so you can include this code anywhere without worrying about it “bogging down” your code. The full signature of this form of the execute() method is:

public void execute(String js, SuccessCallback<JSRef> callback)

The first parameter is just a javascript expression. This javascript MUST call either callback.onSuccess(result) or callback.onError(message, errCode) at some point in order for your callback to be called.

The second parameter is your callback that is executed from the javascript side, when callback.onSuccess(res) is called. The callback takes a single parameter of type JSRef which is a generic wrapper around a javascript variable. JSRef has accessors to retrieve the value as some of the primitive types. E.g. getBoolean(), getDouble(), getInt(), toString(), and it provides some introspection via the getType() method.

Note
It is worth noting that the callback method can only take a single parameter. If you need to pass multiple parameters, you may consider including them in a single string which you parse in your callback.
Synchronous Wrappers

As mentioned before, the new API also provides an executeAndWait() wrapper for execute() that will work synchronously. It, as its name suggests, uses invokeAndBlock under the hood so as not to block the EDT while it is waiting.

E.g.

JSRef res = bc.executeAndWait("callback.onSuccess(3+4)");
Log.p("The result was "+res.Int());

Prints “The result was 7”.

Important
When using the andWait() variant, it is extremely important that your Javascript calls your callback method at some point - otherwise it will block indefinitely. We provide variants of executeAndWait() that include a timeout in case you want to hedge against this possibility.
Multi-use Callbacks

The callbacks you pass to execute() and executeAndWait() are single-use callbacks. You can’t, for example, store the callback variable on the javascript side for later use (e.g. to respond to a button click event). If you need a “multi-use” callback, you should use the addJSCallback() method instead. Its usage looks identical to execute(), the only difference is that the callback will life on after its first use. E.g. Consider the following code:

bc.execute(
    "$('#somebutton').click(function(){callback.onSuccess('Button was clicked')})",
    res -> Log.p(res.toString())
);

The above example, assumes that jQuery is loaded in the webpage that we are interacting with, and we are adding a click handler to a button with ID “somebutton”. The click handler calls our callback.

If you run this example, the first time the button is clicked, you’ll see “Button was clicked” printed to the console as expected. However, the 2nd time, you’ll just get an exception. This is because the callback passed to execute() is only single-use.

We need to modify this code to use the addJSCallback() method as follows:

bc.addJSCallback(
    "$('#somebutton').click(function(){callback.onSuccess('Button was clicked')})",
    res -> Log.p(res.toString())
);

Now it will work no matter how many times the button is clicked.

Passing Parameters to Javascript

In many cases, the javascript expressions that you execute will include parameters from your java code. Properly escaping these parameters is tricky at worst, and annoying at best. E.g. If you’re passing a string, you need to make sure that it escapes quotes and new lines properly or it will cause the javascript to have a syntax error. Luckily we provide variants of execute() and addJSCallback() that allow you to pass your parameters and have them automatically escaped.

For example, suppose we want to pass a string with text to set in a textarea within the webpage. We can do something like:

bc.execute(
    "jQuery('#bio').text(${0}); jQuery('#age').text(${1})",
    new Object[]{
       "A multi-line\n string with \"quotes\"",
       27
    }
);

The gist is that you embed placeholders in the javascript expression that are replaced by the corresponding entry in an array of parameters. The ${0} placeholder is replaced by the first item in the parameters array, the ${1} placeholder is replaced by the 2nd, and so on.

Proxy Objects

The new API also includes a JSProxy class that encapsulates a Javascript object simplify the getting and setting of properties on Javascript objects - and the calling of their methods. It provides essentially three core methods, along with several variants of each to allow for async or synchronous usages, parameters, and timeouts.

E.g. We might want to create a proxy for the window.location object so that we can access its properties more easily from Java.

JSProxy location = bc.createJSProxy("window.location");

Then we can retrieve its properties using the get() method:

location.get("href", res -> Log.p("location.href="+res));

Or synchronously:

JSRef href = location.getAndWait("href");
Log.p("location.href="+href);

We can also set its properties:

location.set("href", "http://www.google.com");

And call its methods:

location.call("replace", new Object[]{"http://www.google.com"},
    res -> Log.p("Return value was "+res)
);
Legacy JSObject Support

This section describes the now deprecated JSObject approach. It’s here for reference by developers working with older code. We suggest using the new API when starting a new project.

BrowserComponent can communicate with the HTML code using JavaScript calls. E.g. we can create HTML like this:

Form hi = new Form("BrowserComponent", new BorderLayout());
BrowserComponent bc = new BrowserComponent();
bc.setPage( "<html lang=\"en\">\n" +
            "    <head>\n" +
            "        <meta charset=\"utf-8\">\n" +
            "        <script>\n" +
            "          function  fnc(message) {\n" +
            "         document.write(message);\n" +
            "            };\n" +
            "        </script>\n" +
            "    </head>\n" +
            "    <body >\n" +
            "        <p>Demo</p>\n" +
            "    </body>\n" +
            "</html>", null);
TextField tf = new TextField();
hi.add(BorderLayout.CENTER, bc).
        add(BorderLayout.SOUTH, tf);
bc.addWebEventListener("onLoad", (e) ->  bc.execute("fnc('<p>Hello World</p>')"));
tf.addActionListener((e) -> bc.execute("fnc('<p>" + tf.getText() +"</p>')"));
hi.show();
JavaScript code was invoked to append text into the browser image above
Figure 78. JavaScript code was invoked to append text into the browser image above
Note
Notice that opening an alert in an embedded native browser might not work

We use the execute method above to execute custom JavaScript code. We also have an executeAndReturnString method that allows us to receive a response value from the JavaScript side.

Coupled with shouldNavigate we can effectively do everything which is exactly what the JavaScript Bridge tries to do.

The JavaScript Bridge

While it’s possible to just build everything on top of execute and shouldNavigate, both of these methods have their limits. That is why we introduced the javascript package, it allows you to communicate with JavaScript using intuitive code/syntax.

The JavascriptContext class lays the foundation by enabling you to call JavaScript code directly from Java. It provides automatic type conversion between Java and JavaScript types as follows:

Table 1. Java to JavaScript
Java Type Javascript Type

String

String

Double/Integer/Float/Long

Number

Boolean

Boolean

JSObject

Object

null

null

Other

Not Allowed

Table 2. JavaScript to Java
Javascript Type Java Type

String

String

Number

Double

Boolean

Boolean

Object

JSObject

Function

JSObject

Array

JSObject

null

null

undefined

null

Note
This conversion table is more verbose than necessary, since JavaScript functions and arrays are, in fact Objects themselves, so those rows are redundant. All JavaScript objects are converted to JSObject.

We can access JavaScript variables easily from the context by using code like this:

Form hi = new Form("BrowserComponent", new BorderLayout());
BrowserComponent bc = new BrowserComponent();
bc.setPage( "<html lang=\"en\">\n" +
            "    <head>\n" +
            "        <meta charset=\"utf-8\">\n" +
            "    </head>\n" +
            "    <body >\n" +
            "        <p>This will appear twice...</p>\n" +
            "    </body>\n" +
            "</html>", null);
hi.add(BorderLayout.CENTER, bc);
bc.addWebEventListener("onLoad", (e) -> {
    // Create a Javascript context for this BrowserComponent
    JavascriptContext ctx = new JavascriptContext(bc);

    String pageContent = (String)ctx.get("document.body.innerHTML");
    hi.add(BorderLayout.SOUTH, pageContent);
    hi.revalidate();
});
hi.show();
The contents was copied from the DOM and placed in the south position of the form
Figure 79. The contents was copied from the DOM and placed in the south position of the form

Notice that when you work with numeric values or anything related to the types mentioned above your code must be aware of the typing. E.g. in this case the type is Double and not String:

Double outerWidth = (Double)ctx.get("window.outerWidth");

You can also query the context for objects and modify their value e.g.

Form hi = new Form("BrowserComponent", new BorderLayout());
BrowserComponent bc = new BrowserComponent();
bc.setPage( "<html lang=\"en\">\n" +
            "    <head>\n" +
            "        <meta charset=\"utf-8\">\n" +
            "    </head>\n" +
            "    <body >\n" +
            "        <p>Please Wait...</p>\n" +
            "    </body>\n" +
            "</html>", null);
hi.add(BorderLayout.CENTER, bc);
bc.addWebEventListener("onLoad", (e) -> {
    // Create a Javascript context for this BrowserComponent
    JavascriptContext ctx = new JavascriptContext(bc);

    JSObject jo = (JSObject)ctx.get("window");
    jo.set("location", "https://www.codenameone.com/");
});

This code effectively navigates to the Codename One home page by fetching the DOM’s window object and setting its location property to https://www.codenameone.com/.

Cordova/PhoneGap Integration

PhoneGap was one of the first web app packager tools in the market. It’s a tool that is effectively a browser component within a native wrapper coupled with native access API’s. Cordova is the open source extension of this popular project.

Codename One supports embedding PhoneGap/Cordova applications directly into Codename One applications. This is relatively easy to do with the BrowserComponent and JavaScript integration. The main aspect that this integration requires is support for Cordova plugins & its JavaScript API’s.

The effort to integrate Cordova/PhoneGap support into Codename One is handled within an open source github project here. The chief benefits of picking Codename One rather than using Cordova directly are:

  • Build Cloud

  • Better Native Code Support

  • Better Protection Of IP

  • IDE Integration Java - JavaScript - HTML

  • Easy, Doesn’t Require A Mac, Automates Certificates/Signing

  • Migration To Java

This is discussed further in the original announcement.

AutoCompleteTextField

The AutoCompleteTextField allows us to write text into a text field and select a completion entry from the list in a similar way to a search engine.

This is really easy to incorporate into your code, just replace your usage of TextField with AutoCompleteTextField and define the data that the autocomplete should work from. There is a default implementation that accepts a String array or a ListModel for completion strings, this can work well for a "small" set of thousands (or tens of thousands) of entries.

E.g. This is a trivial use case that can work well for smaller sample sizes:

Form hi = new Form("Auto Complete", new BoxLayout(BoxLayout.Y_AXIS));
AutoCompleteTextField ac = new AutoCompleteTextField("Short", "Shock", "Sholder", "Shrek");
ac.setMinimumElementsShownInPopup(5);
hi.add(ac);
Autocomplete Text Field
Figure 80. Autocomplete Text Field

However, if you wish to query a database or a web service you will need to derive the class and perform more advanced filtering by overriding the filter method:

public void showForm() {
  final DefaultListModel<String> options = new DefaultListModel<>();
  AutoCompleteTextField ac = new AutoCompleteTextField(options) {
      @Override
      protected boolean filter(String text) {
          if(text.length() == 0) {
              return false;
          }
          String[] l = searchLocations(text);
          if(l == null || l.length == 0) {
              return false;
          }

          options.removeAll();
          for(String s : l) {
              options.addItem(s);
          }
          return true;
      }

  };
  ac.setMinimumElementsShownInPopup(5);
  hi.add(ac);
  hi.add(new SpanLabel("This demo requires a valid google API key to be set below "
           + "you can get this key for the webservice (not the native key) by following the instructions here: "
           + "https://developers.google.com/places/web-service/get-api-key"));
  hi.add(apiKey);
  hi.getToolbar().addCommandToRightBar("Get Key", null, e -> Display.getInstance().execute("https://developers.google.com/places/web-service/get-api-key"));
  hi.show();
}

TextField apiKey = new TextField();

String[] searchLocations(String text) {
    try {
        if(text.length() > 0) {
            ConnectionRequest r = new ConnectionRequest();
            r.setPost(false);
            r.setUrl("https://maps.googleapis.com/maps/api/place/autocomplete/json");
            r.addArgument("key", apiKey.getText());
            r.addArgument("input", text);
            NetworkManager.getInstance().addToQueueAndWait(r);
            Map<String,Object> result = new JSONParser().parseJSON(new InputStreamReader(new ByteArrayInputStream(r.getResponseData()), "UTF-8"));
            String[] res = Result.fromContent(result).getAsStringArray("//description");
            return res;
        }
    } catch(Exception err) {
        Log.e(err);
    }
    return null;
}
Autocomplete Text Field with a webservice
Figure 81. Autocomplete Text Field with a webservice

Using Images In AutoCompleteTextField

One question I got a few times is "How do you customize the results of the auto complete field"?

This sounds difficult to most people as we can only work with Strings so how do we represent additional data or format the date correctly?

The answer is actually pretty simple, we still need to work with Strings because auto-complete is first and foremost a text field. However, that doesn’t preclude our custom renderer from fetching data that might be placed in a different location and associated with the result.

The following source code presents an auto-complete text field with images in the completion popup and two lines for every entry:

final String[] characters = { "Tyrion Lannister", "Jaime Lannister", "Cersei Lannister", "Daenerys Targaryen",
    "Jon Snow", "Petyr Baelish", "Jorah Mormont", "Sansa Stark", "Arya Stark", "Theon Greyjoy"
    // snipped the rest for clarity
};

Form current = new Form("AutoComplete", BoxLayout.y());

AutoCompleteTextField ac = new AutoCompleteTextField(characters);

final int size = Display.getInstance().convertToPixels(7);
final EncodedImage placeholder = EncodedImage.createFromImage(Image.createImage(size, size, 0xffcccccc), true);

final String[] actors = { "Peter Dinklage", "Nikolaj Coster-Waldau", "Lena Headey"}; // (1)
final Image[] pictures = {
    URLImage.createToStorage(placeholder, "tyrion","http://i.lv3.hbo.com/assets/images/series/game-of-thrones/character/s5/tyrion-lannister-512x512.jpg"),
    URLImage.createToStorage(placeholder, "jaime","http://i.lv3.hbo.com/assets/images/series/game-of-thrones/character/s5/jamie-lannister-512x512.jpg"),
    URLImage.createToStorage(placeholder, "cersei","http://i.lv3.hbo.com/assets/images/series/game-of-thrones/character/s5/cersei-lannister-512x512.jpg")
};

ac.setCompletionRenderer(new ListCellRenderer() {
    private final Label focus = new Label(); // (2)
    private final Label line1 = new Label(characters[0]);
    private final Label line2 = new Label(actors[0]);
    private final Label icon = new Label(pictures[0]);
    private final Container selection = BorderLayout.center(
            BoxLayout.encloseY(line1, line2)).add(BorderLayout.EAST, icon);

    @Override
    public Component getListCellRendererComponent(com.codename1.ui.List list, Object value, int index, boolean isSelected) {
        for(int iter = 0 ; iter < characters.length ; iter++) {
            if(characters[iter].equals(value)) {
                line1.setText(characters[iter]);
                if(actors.length > iter) {
                    line2.setText(actors[iter]);
                    icon.setIcon(pictures[iter]);
                } else {
                    line2.setText(""); // (3)
                    icon.setIcon(placeholder);
                }
                break;
            }
        }
        return selection;
    }

    @Override
    public Component getListFocusComponent(com.codename1.ui.List list) {
        return focus;
    }
});
current.add(ac);

current.show();
  1. We have duplicate arrays that are only partial for clarity. This is a separate list of data element but you can fetch the additional data from anywhere

  2. We create the renderer UI instantly in the fields with the helper methods for wrapping elements which is pretty cool & terse

  3. In a renderer it’s important to always set the value especially if you don’t have a value in place

Auto complete with images
Figure 82. Auto complete with images

Picker

Picker occupies the limbo between native widget and lightweight widget. Picker is more like TextField/TextArea in the sense that it’s a Codename One widget that calls the native code only during editing.

The reasoning for this is the highly native UX and functionality related to this widget type which should be quite obvious from the screenshots below.

At this time there are 4 types of pickers:

  • Time

  • Date & Time

  • Date

  • Strings

If a platform doesn’t support native pickers an internal fallback implementation is used. This is the implementation we always use in the simulator so assume different behavior when building for the device.

Tip
While Android supports Date, Time native pickers it doesn’t support the Date & Time native picker UX and will fallback in that case.

The sample below includes al picker types:

Form hi = new Form("Picker", new BoxLayout(BoxLayout.Y_AXIS));
Picker datePicker = new Picker();
datePicker.setType(Display.PICKER_TYPE_DATE);
Picker dateTimePicker = new Picker();
dateTimePicker.setType(Display.PICKER_TYPE_DATE_AND_TIME);
Picker timePicker = new Picker();
timePicker.setType(Display.PICKER_TYPE_TIME);
Picker stringPicker = new Picker();
stringPicker.setType(Display.PICKER_TYPE_STRINGS);
Picker durationPicker = new Picker();
durationPicker.setType(Display.PICKER_TYPE_DURATION);
Picker minuteDurationPicker = new Picker();
minuteDurationPicker.setType(Display.PICKER_TYPE_DURATION_MINUTES);
Picker hourDurationPicker = new Picker();
hourDurationPicker.setType(Display.PICKER_TYPE_DURATION_HOURS);

datePicker.setDate(new Date());
dateTimePicker.setDate(new Date());
timePicker.setTime(10 * 60); // 10:00AM = Minutes since midnight
stringPicker.setStrings("A Game of Thrones", "A Clash Of Kings", "A Storm Of Swords", "A Feast For Crows",
        "A Dance With Dragons", "The Winds of Winter", "A Dream of Spring");
stringPicker.setSelectedString("A Game of Thrones");

hi.add(datePicker).add(dateTimePicker).add(timePicker)
  .add(stringPicker).add(durationPicker)
  .add(minuteDurationPicker).add(hourDurationPicker);
hi.show();
The various picker components
Figure 83. The various picker components
The date & time picker on the simulator
Figure 84. The date & time picker on the simulator
The date picker component on the Android device
Figure 85. The date picker component on the Android device
Date and time picker on Android. Notice it didn’t use a builtin widget since there is none
Figure 86. Date & time picker on Android. Notice it didn’t use a builtin widget since there is none
String picker on the native Android device
Figure 87. String picker on the native Android device
Time picker on the Android device
Figure 88. Time picker on the Android device
Duration picker on Android device
Figure 89. Duration picker on Android device
Hours Duration picker on Android device
Figure 90. Hours Duration picker on Android device
Minutes Duration picker on Android device
Figure 91. Minutes Duration picker on Android device

The text displayed by the picker on selection is generated automatically by the updateValue() method. You can override it to display a custom formatted value and call setText(String) with the correct display string.

A common use case is to format date values based on a specific appearance and Picker has builtin support for a custom display formatter. Just use the setFormatter(SimpleDateFormat) method and set the appearance for the field.

SwipeableContainer

The SwipeableContainer allows us to place a component such as a MultiButton on top of additional "options" that can be exposed by swiping the component to the side.

This swipe gesture is commonly used in touch interfaces to expose features such as delete, edit etc. It’s trivial to use this component by just determining the components placed on top and bottom (the revealed component).

SwipeableContainer swip = new SwipeableContainer(bottom, top);

We can combine some of the demos above including the Slider stars demo to rank GRRM’s books in an interactive way:

Form hi = new Form("Swipe", new BoxLayout(BoxLayout.Y_AXIS));
hi.add(createRankWidget("A Game of Thrones", "1996")).
    add(createRankWidget("A Clash Of Kings", "1998")).
    add(createRankWidget("A Storm Of Swords", "2000")).
    add(createRankWidget("A Feast For Crows", "2005")).
    add(createRankWidget("A Dance With Dragons", "2011")).
    add(createRankWidget("The Winds of Winter", "TBD")).
    add(createRankWidget("A Dream of Spring", "TBD"));
hi.show();

public SwipeableContainer createRankWidget(String title, String year) {
    MultiButton button = new MultiButton(title);
    button.setTextLine2(year);
    return new SwipeableContainer(FlowLayout.encloseCenterMiddle(createStarRankSlider()),
            button);
}
SwipableContainer showing a common use case of ranking on swipe
Figure 92. SwipableContainer showing a common use case of ranking on swipe

EmbeddedContainer

EmbeddedContainer solves a problem that exists only within the GUI builder and the class makes no sense outside of the context of the GUI builder. The necessity for EmbeddedContainer came about due to iPhone inspired designs that relied on tabs (iPhone style tabs at the bottom of the screen) where different features of the application are within a different tab.

This didn’t mesh well with the GUI builder navigation logic and so we needed to rethink some of it. We wanted to reuse GUI as much as possible while still enjoying the advantage of navigation being completely managed for me.

Android does this with Activities and the iPhone itself has a view controller, both approaches are problematic for Codename One. The problem is that you have what is effectively two incompatible hierarchies to mix and match.

The Component/Container hierarchy is powerful enough to represent such a UI but we needed a "marker" to indicate to the UIBuilder where a "root" component exists so navigation occurs only within the given "root". Here EmbeddedContainer comes into play, its a simple container that can only contain another GUI from the GUI builder. Nothing else. So we can place it in any form of UI and effectively have the UI change appropriately and navigation would default to "sensible values".

Navigation replaces the content of the embedded container; it finds the embedded container based on the component that broadcast the event. If you want to navigate manually just use the showContainer() method which accepts a component, you can give any component that is under the EmbeddedContainer you want to replace and Codename One will be smart enough to replace only that component.

The nice part about using the EmbeddedContainer is that the resulting UI can be very easily refactored to provide a more traditional form based UI without duplicating effort and can be easily adapted to a more tablet oriented UI (with a side bar) again without much effort.

MapComponent

Important
The MapComponent uses a somewhat outdated tiling API which is not as rich as modern native maps. We recommend using the GoogleMap’s Native cn1lib to integrate native mapping functionality into the Codename One app.

The MapComponent uses the OpenStreetMap webservice by default to display a navigatable map.

The code was contributed by Roman Kamyk and was originally used for a LWUIT application.

Map Component
Figure 93. Map Component

The screenshot above was produced using the following code:

Form map = new Form("Map");
map.setLayout(new BorderLayout());
map.setScrollable(false);
final MapComponent mc = new MapComponent();

try {
   //get the current location from the Location API
   Location loc = LocationManager.getLocationManager().getCurrentLocation();

  Coord lastLocation = new Coord(loc.getLatitude(), loc.getLongtitude());
   Image i = Image.createImage("/blue_pin.png");
   PointsLayer pl = new PointsLayer();
   pl.setPointIcon(i);
   PointLayer p = new PointLayer(lastLocation, "You Are Here", i);
   p.setDisplayName(true);
   pl.addPoint(p);
   mc.addLayer(pl);
} catch (IOException ex) {
   ex.printStackTrace();
}
mc.zoomToLayers();

map.addComponent(BorderLayout.CENTER, mc);
map.addCommand(new BackCommand());
map.setBackCommand(new BackCommand());
map.show();

The example below shows how to integrate the MapComponent with the Google Location API. make sure to obtain your secret api key from the Google Location data API at: https://developers.google.com/maps/documentation/places/

MapComponent with Google Location API
Figure 94. MapComponent with Google Location API
          final Form map = new Form("Map");
           map.setLayout(new BorderLayout());
           map.setScrollable(false);
           final MapComponent mc = new MapComponent();
           Location loc = LocationManager.getLocationManager().getCurrentLocation();
           //use the code from above to show you on the map
           putMeOnMap(mc);
           map.addComponent(BorderLayout.CENTER, mc);
           map.addCommand(new BackCommand());
           map.setBackCommand(new BackCommand());

           ConnectionRequest req = new ConnectionRequest() {

               protected void readResponse(InputStream input) throws IOException {
                   JSONParser p = new JSONParser();
                   Hashtable h = p.parse(new InputStreamReader(input));
                   // "status" : "REQUEST_DENIED"
                   String response = (String)h.get("status");
                   if(response.equals("REQUEST_DENIED")){
                       System.out.println("make sure to obtain a key from "
                               + "https://developers.google.com/maps/documentation/places/");
                       progress.dispose();
                       Dialog.show("Info", "make sure to obtain an application key from "
                               + "google places api's"
                               , "Ok", null);
                       return;
                   }

                   final Vector v = (Vector) h.get("results");

                   Image im = Image.createImage("/red_pin.png");
                   PointsLayer pl = new PointsLayer();
                   pl.setPointIcon(im);
                   pl.addActionListener(new ActionListener() {

                       public void actionPerformed(ActionEvent evt) {
                           PointLayer p = (PointLayer) evt.getSource();
                           System.out.println("pressed " + p);

                           Dialog.show("Details", "" + p.getName(), "Ok", null);
                       }
                   });

                   for (int i = 0; i < v.size(); i++) {
                       Hashtable entry = (Hashtable) v.elementAt(i);
                       Hashtable geo = (Hashtable) entry.get("geometry");
                       Hashtable loc = (Hashtable) geo.get("location");
                       Double lat = (Double) loc.get("lat");
                       Double lng = (Double) loc.get("lng");
                       PointLayer point = new PointLayer(new Coord(lat.doubleValue(), lng.doubleValue()),
                               (String) entry.get("name"), null);
                       pl.addPoint(point);
                   }
                   progress.dispose();

                   mc.addLayer(pl);
                   map.show();
                   mc.zoomToLayers();

               }
           };
           req.setUrl("https://maps.googleapis.com/maps/api/place/search/json");
           req.setPost(false);
           req.addArgument("location", "" + loc.getLatitude() + "," + loc.getLongtitude());
           req.addArgument("radius", "500");
           req.addArgument("types", "food");
           req.addArgument("sensor", "false");

           //get your own key from https://developers.google.com/maps/documentation/places/
           //and replace it here.
           String key = "yourAPIKey";

           req.addArgument("key", key);

           NetworkManager.getInstance().addToQueue(req);
       }
       catch (IOException ex) {
           ex.printStackTrace();
       }
   }

Chart Component

The charts package enables Codename One developers to add charts and visualizations to their apps without having to include external libraries or embedding web views. We also wanted to harness the new features in the graphics pipeline to maximize performance.

Device Support

Since the charts package makes use of 2D transformations and shapes, it requires some of the graphics features that are not yet available on all platforms. Currently the following platforms are supported:

  1. Simulator

  2. Android

  3. iOS

Features

  1. Built-in support for many common types of charts including bar charts, line charts, stacked charts, scatter charts, pie charts and more.

  2. Pinch Zoom - The ChartComponent class includes optional pinch zoom support.

  3. Panning Support - The ChartComponent class includes optional support for panning.

Chart Types

The com.codename1.charts package includes models and renderers for many different types of charts. It is also extensible so that you can add your own chart types if required. The following screen shots demonstrate a small sampling of the types of charts that can be created.

Line Charts
Figure 95. Line Charts
Cubic Line Charts
Figure 96. Cubic Line Charts
Bar Charts
Figure 97. Bar Charts
Stacked Bar Charts
Figure 98. Stacked Bar Charts
Range Bar Charts
Figure 99. Range Bar Charts
Pie Charts
Figure 100. Pie Charts
Doughnut Charts
Figure 101. Doughnut Charts
Scatter Charts
Figure 102. Scatter Charts
Dial Charts
Figure 103. Dial Charts
Combined Charts
Figure 104. Combined Charts
Bubble Charts
Figure 105. Bubble Charts
Time Charts
Figure 106. Time Charts
Note
The above screenshots were taken from the ChartsDemo app. You can start playing with this app by checking it out from our git repository.

How to Create A Chart

Adding a chart to your app involves four steps:

  1. Build the model. You can construct a model (aka data set) for the chart using one of the existing model classes in the com.codename1.charts.models package. Essentially, this is just where you add the data that you want to display.

  2. Set up a renderer. You can create a renderer for your chart using one of the existing renderer classes in the com.codename1.charts.renderers package. The renderer allows you to specify how the chart should look. E.g. the colors, fonts, styles, to use.

  3. Create the Chart View. Use one of the existing view classes in the com.codename1.charts.views package.

  4. Create a ChartComponent. In order to add your chart to the UI, you need to wrap it in a ChartComponent object.

You can check out the ChartsDemo app for specific examples, but here is a high level view of some code that creates a Pie Chart.

/**
 * Creates a renderer for the specified colors.
 */
private DefaultRenderer buildCategoryRenderer(int[] colors) {
    DefaultRenderer renderer = new DefaultRenderer();
    renderer.setLabelsTextSize(15);
    renderer.setLegendTextSize(15);
    renderer.setMargins(new int[]{20, 30, 15, 0});
    for (int color : colors) {
        SimpleSeriesRenderer r = new SimpleSeriesRenderer();
        r.setColor(color);
        renderer.addSeriesRenderer(r);
    }
    return renderer;
}

/**
 * Builds a category series using the provided values.
 *
 * @param titles the series titles
 * @param values the values
 * @return the category series
 */
protected CategorySeries buildCategoryDataset(String title, double[] values) {
    CategorySeries series = new CategorySeries(title);
    int k = 0;
    for (double value : values) {
        series.add("Project " + ++k, value);
    }

    return series;
}

public Form createPieChartForm() {

    // Generate the values
    double[] values = new double[]{12, 14, 11, 10, 19};

    // Set up the renderer
    int[] colors = new int[]{ColorUtil.BLUE, ColorUtil.GREEN, ColorUtil.MAGENTA, ColorUtil.YELLOW, ColorUtil.CYAN};
    DefaultRenderer renderer = buildCategoryRenderer(colors);
    renderer.setZoomButtonsVisible(true);
    renderer.setZoomEnabled(true);
    renderer.setChartTitleTextSize(20);
    renderer.setDisplayValues(true);
    renderer.setShowLabels(true);
    SimpleSeriesRenderer r = renderer.getSeriesRendererAt(0);
    r.setGradientEnabled(true);
    r.setGradientStart(0, ColorUtil.BLUE);
    r.setGradientStop(0, ColorUtil.GREEN);
    r.setHighlighted(true);

    // Create the chart ... pass the values and renderer to the chart object.
    PieChart chart = new PieChart(buildCategoryDataset("Project budget", values), renderer);

    // Wrap the chart in a Component so we can add it to a form
    ChartComponent c = new ChartComponent(chart);

    // Create a form and show it.
    Form f = new Form("Budget");
    f.setLayout(new BorderLayout());
    f.addComponent(BorderLayout.CENTER, c);
    return f;

}

Calendar

The Calendar class allows us to display a traditional calendar picker and optionally highlight days in various ways.

Note
We normally recommend developers use the Picker UI rather than use the calendar to pick a date. It looks better on the devices.

Simple usage of the Calendar class looks something like this:

Form hi = new Form("Calendar", new BorderLayout());
Calendar cld = new Calendar();
cld.addActionListener((e) -> Log.p("You picked: " + new Date(cld.getSelectedDay())));
hi.add(BorderLayout.CENTER, cld);
The Calendar component
Figure 107. The Calendar component

ToastBar

The ToastBar class allows us to display none-obtrusive status messages to the user at the bottom of the screen. This is useful for such things as informing the user of a long-running task (like downloading a file in the background), or popping up an error message that doesn’t require a response from the user.

Simple usage of the ToastBar class looks something like this:

Status status = ToastBar.getInstance().createStatus();
status.setMessage("Downloading your file...");
status.show();

//  ... Later on when download completes
status.clear();
ToastBar with message
Figure 108. ToastBar with message

We can show a progress indicator in the ToastBar like this:

Status status = ToastBar.getInstance().createStatus();
status.setMessage("Hello world");
status.setShowProgressIndicator(true);
status.show();
Status Message with Progress Bar
Figure 109. Status Message with Progress Bar

We can automatically clear a status message/progress after a timeout using the setExpires method as such:

Status status = ToastBar.getInstance().createStatus();
status.setMessage("Hello world");
status.setExpires(3000);  // only show the status for 3 seconds, then have it automatically clear
status.show();

We can also delay the showing of the status message using showDelayed as such:

Status status = ToastBar.getInstance().createStatus();
status.setMessage("Hello world");
status.showDelayed(300); // Wait 300 ms to show the status

// ... Some time later, clear the status... this may be before it shows at all
status.clear();
ToastBar with a multiline message
Figure 110. ToastBar with a multiline message

Actions In ToastBar

Probably the best usage example for actions in toast is in the gmail style undo. If you are not a gmail user then the gmail app essentially never prompts for confirmation!

It just does whatever you ask and pops a "toast message" with an option to undo. So if you clicked by mistake you have 3-4 seconds to take that back.

This simple example shows you how you can undo any addition to the UI in a similar way to gmail:

Form hi = new Form("Undo", BoxLayout.y());
Button add = new Button("Add");

add.addActionListener(e -> {
    Label l = new Label("Added this");
    hi.add(l);
    hi.revalidate();
    ToastBar.showMessage("Added, click here to undo...", FontImage.MATERIAL_UNDO,
            ee -> {
                l.remove();
                hi.revalidate();
            });
});
hi.add(add);
hi.show();

SignatureComponent

The SignatureComponent provides a widget that allows users to draw their signature in the app.

Simple usage of the SignatureComponent class looks like:

Form hi = new Form("Signature Component");
hi.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
hi.add("Enter Your Name:");
hi.add(new TextField());
hi.add("Signature:");
SignatureComponent sig = new SignatureComponent();
sig.addActionListener((evt)-> {
    System.out.println("The signature was changed");
    Image img = sig.getSignatureImage();
    // Now we can do whatever we want with the image of this signature.
});
hi.addComponent(sig);
hi.show();
The Signature Component
Figure 111. The signature Component

Accordion

The Accordion displays collapsible content panels.

Simple usage of the Accordion class looks like:

Form f = new Form("Accordion", new BoxLayout(BoxLayout.Y_AXIS));
f.setScrollableY(true);
Accordion accr = new Accordion();
accr.addContent("Item1", new SpanLabel("The quick brown fox jumps over the lazy dog\n"
        + "The quick brown fox jumps over the lazy dog"));
accr.addContent("Item2", new SpanLabel("The quick brown fox jumps over the lazy dog\n"
        + "The quick brown fox jumps over the lazy dog\n "
        + "The quick brown fox jumps over the lazy dog\n "
        + "The quick brown fox jumps over the lazy dog\n "
        + ""));

accr.addContent("Item3", BoxLayout.encloseY(new Label("Label"), new TextField(), new Button("Button"), new CheckBox("CheckBox")));

f.add(accr);
f.show();
The Accordion Component
Figure 112. The Accordion Component

Floating Hint

FloatingHint wraps a text component with a special container that can animate the hint label into a title label when the text component is edited or has content within it.

Form hi = new Form("Floating Hint", BoxLayout.y());
TextField first = new TextField("", "First Field");
TextField second = new TextField("", "Second Field");
hi.add(new FloatingHint(first)).
        add(new FloatingHint(second)).
        add(new Button("Go"));
hi.show();
The FloatingHint component with one component that contains text and another that doesn’t
Figure 113. The FloatingHint component with one component that contains text and another that doesn’t

Floating Button

The material design floating action button is a powerful tool for promoting an action within your application.

FloatingActionButton is a round button that resides on top of the UI typically in the bottom right hand side.
It has a drop shadow to distinguish it from the UI underneath and it can hide two or more additional actions under the surface. E.g. we can create a simple single click button such as this:

FloatingActionButton fab = FloatingActionButton.createFAB(FontImage.MATERIAL_ADD);
fab.addActionListener(e -> ToastBar.showErrorMessage("Not implemented yet..."));
fab.bindFabToContainer(form.getContentPane());

Which will place a + sign button that will perform the action. Alternatively we can create a nested action where a click on the button will produce a submenu for users to pick from e.g.:

FloatingActionButton fab = FloatingActionButton.createFAB(FontImage.MATERIAL_ADD);
fab.createSubFAB(FontImage.MATERIAL_PEOPLE, "");
fab.createSubFAB(FontImage.MATERIAL_IMPORT_CONTACTS, "");
fab.bindFabToContainer(form.getContentPane());
FloatingActionButton with submenu expanded
Figure 114. FloatingActionButton with submenu expanded

Those familiar with this widget know that there are many nuances to this UI that we might implement/expose in the future. At the moment we chose to keep the API simple and minimal for the common use cases and refine it based on feedback.

Using Floating Button as a Badge

Floating buttons can also be used to badge an arbitrary component in the style popularized by iOS/Mac OS. A badge appears at the top right corner and includes special numeric details such as "unread count"..

The code below adds a simple badge to an icon button:

Form hi = new Form("Badge");

Button chat = new Button("");
FontImage.setMaterialIcon(chat, FontImage.MATERIAL_CHAT, 7);

FloatingActionButton badge = FloatingActionButton.createBadge("33");
hi.add(badge.bindFabToContainer(chat, Component.RIGHT, Component.TOP));

TextField changeBadgeValue = new TextField("33");
changeBadgeValue.addDataChangedListener((i, ii) -> {
    badge.setText(changeBadgeValue.getText());
    badge.getParent().revalidate();
});
hi.add(changeBadgeValue);

hi.show();

The code above results in this, notice you can type into the text field to change the badge value:

Badge floating button in action
Figure 115. Badge floating button in action

SplitPane

The split pane component is a bit desktop specific but works reasonably well on devices. To get the image below we changed SalesDemo.java in the kitchen sink by changing this:

private Container encloseInMaximizableGrid(Component cmp1, Component cmp2) {
    GridLayout gl = new GridLayout(2, 1);
    Container grid = new Container(gl);
    gl.setHideZeroSized(true);

    grid.add(encloseInMaximize(grid, cmp1)).
            add(encloseInMaximize(grid, cmp2));
    return grid;
}

To:

private Container encloseInMaximizableGrid(Component cmp1, Component cmp2) {
    return new SplitPane(SplitPane.VERTICAL_SPLIT, cmp1, cmp2, "25%", "50%", "75%");
}
Split Pane in the Kitchen Sink Demo
Figure 116. Split Pane in the Kitchen Sink Demo

This is mostly self explanatory but only "mostly". We have 5 arguments the first 3 make sense:

  • Split orientation

  • Components to split

The last 3 arguments seem weird but they also make sense once you understand them, they are:

  • The minimum position of the split - 1/4 of available space

  • The default position of the split - middle of the screen

  • The maximum position of the split - 3/4 of available space

The units don’t have to be percentages they can be mm (millimeters) or px (pixels).


1. String width is the real expensive part here, the complexity of font kerning and the recursion required to reflow text is a big performance hurdle
2. Image by RegisFrey - Own work, Public Domain, https://commons.wikimedia.org/w/index.php?curid=10298177
3. Fisheye is an effect where the selection stays in place as the list moves around it
5. The text below is from A Wiki of Ice & Fire: http://awoiaf.westeros.org/index.php/A_Game_of_Thrones
Clone this wiki locally