Preface

This developer guide is automatically generated from the wiki pages at https://github.com/codenameone/CodenameOne/wiki.

You can edit any page within the wiki pages using AsciiDoc. Your changes might make it (after review) into the official documentation available on the web here: https://www.codenameone.com/manual/ and available as a PDF file here: https://www.codenameone.com/files/developer-guide.pdf.

Occasionally this book is updated to the print edition available here: https://www.amazon.com/dp/1549910035

We also recommend that you check out the full JavaDoc reference for Codename One which is very complete.

You can download the full Codename One source code from the Codename One git repository where you can also edit the JavaDocs and submit pull requests to include new features into Codename One.

Authors

This document includes content from multiple authors and community wiki edits. If you edit pages within the guide feel free to add your name here alphabetized by surname:

Rights & Licensing

You may copy/redistribute/print this document without prior permission from Codename One. However, you may not charge for the document itself although charging for costs such as printing is permitted.

Notice that while you can print and reproduce sections arbitrarily such changes must explicitly and clearly state that this is a modified copy and link to the original source at https://www.codenameone.com/manual/ in a clear way!

Conventions

This guide uses some notations to provide tips and further guidance.

In case of further information that breaks from the current tutorial flow we use a sidebar as such:

Sidebar

Dig deeper into some details that don’t quite fit into the current flow. We use sidebars for things that are an important detour, you can skip them while reading but you might want to come back and read them later.

Here are common conventions for highlighting notes:

This is an important note
This is a helpful tip
This is a general informational note, something interesting but not crucial
This is a warning something we should pay attention to

This convention is used when we refer to a button or a widget to press in the UI. E.g. press the button labeled Press Here.

Quotes are presented as:

This is a quote

— By Author

Bold is used for light emphasis on a specific word or two within a sentence.

1. Introduction

Codename One is a Write Once Run Anywhere mobile development platform for Java/Kotlin developers. It integrates with IntelliJ/IDEA, Eclipse or NetBeans to provide seamless native mobile development.

Codename One’s mission statement is:

Unify the complex and fragmented task of mobile device programming into a single set of tools, APIs and services. As a result create a more manageable approach to mobile application development without sacrificing the power/control given to developers.

This effectively means bringing that old "Write Once Run Anywhere" (WORA) Java mantra to mobile devices without "dumbing it down" to the lowest common denominator.

The things that make Codename One stand out from other tools in this field are:

  • Write Once Run Anywhere support with no special hardware requirements and 100% code reuse

  • Compiles Java/Kotlin into native code for iOS, UWP (Universal Windows Platform), Android and even JavaScript/PWA

  • Open Source and Free with commercial backing/support

  • Easy to use with 100% portable Drag and Drop GUI builder

  • Full access to underlying native OS capabilities using the native OS programming language (e.g. Objective-C) without compromising portability

  • Provides full control over every pixel on the screen

  • Lets you use native widgets (views) and mix them with Codename One components within the same hierarchy (heavyweight/lightweight mixing)

  • Supports seamless Continuous Integration out of the box

Codename One can trace its roots to the open source LWUIT project started at Sun Microsystem in 2007 by Chen Fishbein (co-founder of Codename One). It’s a huge project that’s been under constant development for over a decade!

1.1. Build Cloud

One of the things that make Codename One stand out is the build cloud approach to mobile development. iOS native development requires a Mac with xcode. Windows native development requires a Windows machine. To make matters worse, Apple, Google and Microsoft make changes to their tools on a regular basis…​

This makes it hard to keep up.

When we develop an app in Codename One we use the builtin simulator when running and debugging. When we want to build a native app we can use the build cloud where Macs create the native iOS apps and Windows machines create the native Windows apps. This works seamlessly and makes Codename One apps native as they are literally compiled by the native platform. E.g. for iOS builds the build cloud uses Macs running xcode (the native Apple tool) to build the app.

Codename One doesn’t send source code to the build cloud, only compiled bytecode!

Notice that Codename One also provides an option to build offline which means corporations that have policies forbidding such cloud architectures can still use Codename One with some additional overhead/complexity of setting up the native build tools. Since Codename One is open source some developers use the source code to compile applications offline but that’s outside the scope of this book.

1.1.1. Why Build Servers?

The build servers allow building native iOS Apps without a Mac and native Windows apps without a Windows machine. They remove the need to install/update complex toolchains and simplify the process of building a native app to a right click.

E.g.: Since building native iOS applications requires a Mac OS X machine with a recent version of xcode Codename One maintains such machines in the cloud. When developers send an iOS build such a Mac will be used to generate C source code using ParparVM and it will then compile the C source code using xcode & sign the resulting binary using xcode. You can install the binary to your device or build a distribution binary for the appstore. Since C code is generated it also means that your app will be "future proof" in a case of changes from Apple. You can also inject Objective-C native code into the app while keeping it 100% portable thanks to the "native interfaces" capability of Codename One.

Subscribers can receive the C source code back using the include sources feature of Codename One and use those sources for benchmarking, debugging on devices etc.

The same is true for most other platforms. For the Android, J2ME & Blackberry the standard Java code is executed as is.

Java 8 syntax is supported thru retrolambda installed on the Codename One servers. This is used to convert bytecode seamlessly down to Java 5 syntax levels. Java 5 syntax is translated to the JDK 1.3 cldc subset on J2ME/Blackberry to provide those language capabilities and API’s across all devices. This is done using a server based bytecode processor based on retroweaver and a great deal of custom code. Notice that this architecture is transparent to developers as the build servers abstract most of the painful differences between devices.

1.1.2. How does Codename One Work?

Codename One uses a SaaS based approach so the information in this appendix might (and probably will) change in the future to accommodate improved architectures. I included this information for reference only, you don’t need to understand this in order to follow the content of the book…​

Since Android is already based on Java, Codename One is already native to Android and “just works” with the Android VM (ART/Dalvik).

On iOS, Codename One built and open sourced ParparVM, which is a very conservative VM. ParparVM features a concurrent (non-blocking) GC and it’s written entirely in Java/C. ParparVM generates C source code matching the given Java bytecode. This effectively means that an xcode project is generated and compiled on the build servers. It’s as if you handcoded a native app and is thus “future proof” for changes that Apple might introduce. E.g. Apple migrated to 64bit and later introduced bitcode support to iOS. ParparVM needed no modifications to comply with those changes.

Codename One translates the bytecode to C which is faster than Swift/Objective-C. The port code that invokes iOS API’s is hand coded in Objective-C

For Windows 10 desktop and Mobile support, Codename One uses iKVM to target UWP (Universal Windows Platform) and has open sourced the changes to the original iKVM code.

JavaScript build targets use TeaVM to do the translation statically. TeaVM provides support for threading using JavaScript by breaking the app down in a rather elaborate way. To support the complex UI Codename One uses the HTML5 Canvas API which allows absolute flexibility for building applications.

For desktop builds Codename One uses javapackager, since both Macs and Windows machines are available in the cloud the platform specific nature of javapackager is not a problem.

Lightweight Architecture

What makes Codename One stand out is the approach it takes to UI: “lightweight architecture”.

Lightweight architecture is the “not so secrete sauce” to Codename One’s portability. Essentially it means all the components/widgets in Codename One are written in Java. Thus their behavior is consistent across all platforms and they are fully customizable from the developer code as they don’t rely on OS internal semantics. This allows developers to preview the application accurately in the simulators and GUI builders.

One of the big accomplishments in Codename One is its unique ability to embed “heavyweight” widgets into place among the “lightweights”. This is crucial for apps such as Uber where the cars and widgets on top are implemented as Codename One components yet below them we have the native map component.

Codename One achieves fast performance by drawing using the native gaming API’s of most platforms e.g. OpenGL ES on iOS. The core technologies behind Codename One are all open source including most of the stuff developed by Codename One itself, e.g. ParparVM but also the full library, platform ports, designer tool, device skins etc.

Lightweight Architecture Origin

Lightweight components date back to Smalltalk frameworks, this notion was popularized in the Java world by Swing. Swing was the main source of inspiration to Codename One’s predecessor LWUIT. Many frameworks took this approach over the years including JavaFX and most recently Ionic in the JavaScript world.

Why ParparVM

On iOS, Codename One uses ParparVM which translates Java bytecode to C code and boasts a non-blocking GC as well as 64 bit/bitcode support. This VM is fully open source in the Codename One git repository. In the past Codename One used XMLVM to generate native code in a very similar way but the XMLVM solution was too generic for the needs of Codename One. ParparVM boasts a unique architecture of translating code to C (similarly to XMLVM), because of that Codename One is the only solution of its kind that can guarantee future iOS compatibility since the officially supported iOS toolchain is always used instead of undocumented behaviors.

XMLVM could guarantee that in theory but it is no longer maintained

The key advantages of ParparVM over other approaches are:

  • Truly Native — since code is translated to C rather than directly to ARM or LLVM code the app is "more native". It uses the official tools and approaches from Apple and can benefit from their advancements e.g. latest bitcode changes or profiling capabilities.

  • Smaller Class Library — ParparVM includes a very small segment of the full JavaAPI’s resulting in final binaries that are smaller than the alternatives by orders of magnitude. This maps directly to performance and memory overhead.

  • Simple and Extensible — to work with ParparVM you need a basic understanding of C. This is crucial for the fast moving world of mobile development, as Apple changes things left and right we need a more agile VM.

Windows Phone/UWP

In the past Codename One had 2 major Windows VM port rewrites and 3 or 4 rendering pipelines within those ports (depends on how you would define a "rewrite").

The old Windows Phone port was deprecated and is no longer supported, the UWP port is the only supported Windows mobile target

Codename One now targets UWP by leveraging a modified version of iKVM to build native Windows Universal Applications.

iKVM uses a bytecode to CLR translation process that effectively converts Java bytecode directly to the .net equivalent. This is paired with a port of the Codename One API’s that was built for the UWP environment. The UWP port generates native Windows 10 applications that can support ARM Windows devices natively as well as desktops etc. These binaries can be uploaded directly to Microsofts online store without special processing.

JavaScript Port

The JavaScript port of Codename One is based on the amazing work of the TeaVM project. The team behind TeaVM effectively built a JVM that translates Java bytecode into JavaScript source code while maintaining threading semantics using a very imaginative approach.

The JavaScript port allows unmodified Codename One applications to run within a desktop or mobile browser. The port itself is based on the HTML5 Canvas API, this provides a pixel perfect implementation of the Codename One API.

The JavaScript port is only available for Enterprise grade subscribers of Codename One
Desktop and Android

The other ports of Codename One use the VM’s available on the host machines/environments to execute the runtime. Retrolambda is used to provide Java 8 language features in a portable way.

The Android port uses the native Android tools including the gradle build environment in the latest versions.

The desktop port creates a standard JavaSE application which is packaged with the JRE and an installer.

The Desktop port is only available to pro grade subscribers of Codename One

1.1.3. Versions In Codename One

One of the confusing things about Codename One is the versions. Since Codename One is a SaaS product versioning isn’t as simple as a 2.x or 3.x moniker. However, to conform to this convention Codename One does make versioned releases which contribute to the general confusion.

When a version of Codename One is released the version number refers to the libraries at the time of the release. These libraries are then frozen and are made available to developers who use the Versioned Builds feature. The plugin, which includes the designer as well as all development that is unrelated to versioned builds continues with its regular updates immediately after release. The same is true for the build servers that move directly to their standard update cycle.

1.2. History

LWUIT App Screenshot
Figure 1. LWUIT App Screenshot circa 2007

Codename One was started by Chen Fishbein and Shai Almog who authored the Open Source LWUIT project at Sun Microsystems (circa 2007). The LWUIT project aimed to solve the fragmentation within J2ME/Blackberry devices by creating a higher standard of user interface than the common baseline at the time. LWUIT received critical acclaim and traction within multiple industries but was limited by the declining feature phone market. It was forked by several companies including Nokia. It was used as the base standard for DTV in Brazil. Another fork has brought a LWUIT into high end cars from Toyota and other companies. This fork later adapted Codename One as well.

In 2012 Shai and Chen formed Codename One as they left Oracle. The project has taken many of the basic concepts developed within the LWUIT project and adapted them to the smartphone world which is still experiencing similar issues to the device fragmentation of the old J2ME phones.

1.3. Core Concepts of Mobile Programming

Before we proceed I’d like to explain some universal core concepts of mobile programming that might not be intuitive. These are universal concepts that apply to mobile programming regardless of the tools you are using.

You can skip this section if you feel you are familiar enough with the core problems/issues in mobile app development.

1.3.1. Density

Density is also known as DPI (Dots Per Inch) or PPI (pixels or points per inch). Density is confusing, unintuitive and might collide with common sense. E.g. an iPhone 7 plus has a resolution of 1080x1920 pixels and a PPI of 401 for a 5 inch screen. On the other hand an iPad 4 has 1536x2048 pixels with a PPI of 264 on a 9.7 inch screen…​ Smaller devices can have higher resolutions!

As the following figure shows, if a Pixel 2 XL had pixels the size of an iPad it would have been twice the size of that iPad. While in reality it’s nearly half the height of the iPad!

Device Density vs Resolution
Figure 2. Device Density vs. Resolution

Differences in density can be extreme. A second generation iPad has 132 PPI, where modern phones have PPI that crosses the 600 mark. Low resolution images on high PPI devices will look either small or pixelated. High resolution images on low PPI devices will look huge, overscaled (artifacts) and will consume too much memory.

How the Same Image Looks in Different Devices
Figure 3. How the Same Image Looks in Different Devices

The exact same image will look different on each device, sometimes to a comical effect. One of the solutions for this problem is multi-images. All OS’s support the ability to define different images for various densities. I will discuss multi-images later in Chapter 2.

This also highlights the need for working with measurements other than pixels. Codename One supports millimeters (or dips) as a unit of measurement. This is highly convenient and is a better representation of size when dealing with mobile devices.

But there is a bigger conceptual issue involved. We need to build a UI that adapts to the wide differences in form factors. We might have fewer pixels on an iPad but because of its physical size we would expect the app to cram more information into that space so the app won’t feel like a blown up phone application. There are multiple strategies to address that but one of the first steps is in the layout managers.

I’ll discuss the layout managers in depth in Chapter 2 but the core concept is that they decide where a UI element is placed based on generic logic. That way the user interface can adapt automatically to the huge variance in display size and density.

1.3.2. Touch Interface

The fact that mobile devices use a touch interface today isn’t news…​ But the implications of that aren’t immediately obvious to some developers.

UI elements need to be finger sized and heavily spaced. Otherwise we risk the “fat finger” effect. That means spacing should be in millimeters and not in pixels due to device density.

Scrolling poses another challenge in touch based interfaces. In desktop applications it’s very common to nest scrollable items. However, in touch interfaces the scrolling gesture doesn’t allow such nuance. Furthermore, scrolling on both the horizontal and vertical axis (side scrolling) can be very inconvenient in touch based interfaces.

1.3.3. Device Fragmentation

Some developers single out this wide range of resolutions and densities as “device fragmentation”. While it does contribute to development complexity for the most part it isn’t a difficult problem to overcome.

Densities aren’t the cause of device fragmentation. Device fragmentation is caused by multiple OS versions with different behaviors. This is very obvious on Android and for the most part relates to the slow rollout of Android vendor versions compared to Googles rollout. E.g. 7 months after the Android 8 (Oreo) release in 2018 it was still available on 1.1% of the devices. The damning statistic is that 12% of the devices in mid 2018 run Android 4.4 Kitkat released in 2013!

This makes QA difficult as the disparity between these versions is pretty big. These numbers will be out of date by the time you read this but the core problem remains. It’s hard to get all device manufacturers on the same page so this problem will probably remain in the foreseeable future despite everything.

1.3.4. Performance

Besides the obvious need for performance and smooth animation within a mobile app there are a couple of performance related issues that might not be intuitive to new developers: size and power.

App Size

Apps are installed and managed via stores. This poses some restrictions about what an app can do. But it also creates a huge opportunity. Stores manage automatic update and to some degree the marketing/monetization of the app.

A good mobile app is updated once a month and sometimes even once a week. Since the app downloads automatically from the store this can be a huge benefit:

  • Existing users are reminded of the app and get new features instantly

  • New users notice the app featured on a “what’s new” list

If an app is big it might not update over a cellular network connection. Google and Apple have restrictions on automatic updates over cellular networks to preserve battery life and data plans. A large app might negatively impact users perception of the app and trigger uninstalls e.g. when a phone is low on available space.

Power Drain

Desktop developers rarely think about power usage within their apps. In mobile development this is a crucial concept. Modern device OS’s have tools that highlight misbehaving applications and this can lead to bad reviews.

Code that loops forever while waiting for input will block the CPU from sleeping and slowly drain the battery.

Worse. Mobile OS’s kill applications that drain the battery. If the app is draining the battery and is minimized (e.g. during an incoming call) the app could be killed. This will impact app performance and usability.

1.3.5. Sandbox and Permissions

Apps installed on the device are “sandboxed” to a specific area so they won’t harm the device or its functionality. The filesystem of mobile applications is restricted so one application can’t access the files of another application. Things that most developers take for granted on the desktop such as a “file picker” or accessing the image folder don’t work on devices!

This means that when your application works on a file it belongs only to your application. In order to share the file with a different application you need to ask the operating system to do that for you.

Furthermore, some features require a “permission” prompt and in some cases require special flags in system files. Apps need to request permission to use sensitive capabilities e.g. Camera, Contacts etc.
Historically Android developers just declared required permissions for an app and the user was prompted with permissions during install. Android 6 adopted the approach used by iOS of prompting the user for permission when accessing a feature.

This means that in runtime a user might revoke a permission. A good example in the case of an Uber app is the location permission. If a user revokes that permission the app might lose its location.

1.4. Installing Codename One

The minimum JDK for Codename One is JDK 8
The following screenshots are from Mac OS but the process should work exactly the same on Windows and Linux

1.5. IntelliJ/IDEA

Codename One recommends IntelliJ/IDEA 2016 or newer.

Codename One doesn’t support Android Studio! You can use IntelliJ/IDEA community edition instead
IntelliJ Installation Instructions
Figure 4. IntelliJ Installation Instructions

1.6. NetBeans

NetBeans install is pretty simple although the default “plugin center” for NetBeans is notoriously unreliable. That’s why we recommend using the Codename One plugin center: https://www.codenameone.com/files/netbeans/updates.xml

NetBeans Installation Instructions
Figure 5. NetBeans Installation Instructions
Make sure you are using a NetBeans version that includes Java support, don’t download a version for Ruby/PHP or J2ME and make sure the IDE runs on top of JDK 8

1.7. Eclipse

Codename One supports Eclipse Neon 2 or newer. There are a few pitfalls that can happen with an Eclipse install specifically when other JVM versions are installed on your machine.

If you are new to Java, Eclipse might be intimidating. It’s a very powerful IDE but its configuration is rough

Make sure your JAVA_HOME environment variable points at JDK 8 and that the path to the JDK 8 bin directory is first in the PATH statement. If all else fails edit the eclipse.ini file to force Eclipse to use your JDK 8 install. See this site for help with editing the eclipse.ini file: https://wiki.eclipse.org/Eclipse.ini

Eclipse Installation Instructions
Figure 6. Eclipse Installation Instructions
In order to run the app in Eclipse make sure to select the .launch file in Eclipse

1.8. Hello World Application

Before we get to the code there are few important things we need to go over with the new project wizard.

We need to create a new project. We need to pick a project name and I’ll leave that up to you although it’s hard to go wrong with HelloWorld. The following four values are important:

  • App Name - This is the name of the app and the main class, it’s important to get this right as it’s hard to change this value later

  • Package Name - It’s crucial you get this value right. Besides the difficulty of changing this after the fact, once an app is submitted to iTunes/Google Play with a specific package name this can’t be changed! See the sidebar "Picking a Package Name".

  • Theme - There are various types of builtin themes in Codename One, for simplicity I pick Native as it’s a clean slate starting point

  • Template - There are several builtin app templates that demonstrate various features, for simplicity I always pick Bare Bones which includes the bare minimum

The New App Wizard
Figure 7. The New App Wizard
Picking a Package Name

Apple, Google and Microsoft identify applications based on their package names. If you use a domain that you don’t own it’s possible that someone else will use that domain and collide with you. In fact some developers left the default com.mycompany domain in place all the way into production in some cases.

This can cause difficulties when submitting to Apple, Google or Microsoft. Submitting to one of them is no guarantee of success when submitting to another.

To come up with the right package name use a reverse domain notation. So if my website is goodstuff.co.uk my package name should start with uk.co.goodstuff. I highly recommend the following guidelines for package names:

  • Lower Case - some OS’s are case sensitive and handling a mistake in case is painful. The Java convention is lower case and I would recommend sticking to that although it isn’t a requirement

  • Avoid Dash and Underscore - You can’t use a dash character (-) for a package name in Java. Underscore (_) doesn’t work for iOS. If you want more than one word just use a deeper package e.g.: com.mydomain.deeper.meaningful.name

  • Obey Java Rules - A package name can’t start with a number so you can’t use com.mydomain.1sler. You should avoid using Java keywords like this, if etc.

  • Avoid Top Level - instead of using uk.co.goodstuff use uk.co.goodstuff.myapp. That would allow you to have more than one app on a domain

1.8.1. Running

We can run the HelloWorld application by pressing the Play or Run button in the IDE for NetBeans or IntelliJ. In Eclipse we first need to select the simulator .launch file and then press run. When we do that the Codename One simulator launches. You can use the menu of the simulator to control and inspect details related to the device. You can rotate it, determine it’s location in the world, monitor networking calls etc.

With the Skins menu you can download device skins to see how your app will look on different devices.

Some skins are bigger than the screen size, uncheck the Scrollable flag in the Simulator menu to handle them more effectively

Debug works just like Run by pressing the IDE’s debug button. It allows us to launch the simulator in debug mode where we can set breakpoints, inspect variables etc.

HelloWorld Running on the Simulator with an iPhone X Skin
Figure 8. HelloWorld Running on the Simulator with an iPhone X Skin
Simulator vs. Emulator

Codename One ships with a simulator similarly to the iOS toolchain which also has a simulator. Android ships with an emulator. Emulators go the extra mile. They create a virtual machine that’s compatible with the device CPU and then boot the full mobile OS within that environment. This provides an accurate runtime environment but is painfully slow.

Simulators rely on the fact that OS’s are similar and so they leave the low level details in place and just map the API behavior. Since Codename One relies on Java it can start simulating on top of the virtual machine on the desktop. That provides several advantages including fast development cycles and full support for all the development tools/debuggers you can use on the desktop.

Emulators make sense for developers who want to build OS level services e.g. screensavers or low level services. Standard applications are better served by simulators.

1.8.2. The Source Code Of The Hello World App

After clicking finish in the new project wizard we have a HelloWorld project with a few default settings. I’ll break the class down to small pieces and explain each piece starting with the enclosing class:

Listing 1. HelloWorld Class
public class HelloWorld { (1)
    private Form current; (2)
    private Resources theme; (3)

    // ... class methods ...
}
1 This is the main class, it’s the entry point to the app, notice it doesn’t have a main method but rather callback which we will discuss soon
2 Forms are the “top level” UI element in Codename One. Only one Form is shown at a time and everything you see on the screen is a child of that Form
3 Every app has a theme, it determines how everything within the application looks e.g. colors, fonts etc.

Next let’s discuss the first lifecycle method init(Object). I discuss the lifecycle in depth in the Application Lifecycle Sidebar.

Listing 2. HelloWorld init(Object)
public void init(Object context) { (1)
    updateNetworkThreadCount(2); (2)
    theme = UIManager.initFirstTheme("/theme"); (3)
    Toolbar.setGlobalToolbar(true); (4)
    Log.bindCrashProtection(true); (5)
    addNetworkErrorListener(err -> { (6)
        err.consume(); (7)
        if(err.getError() != null) { (8)
            Log.e(err.getError());
        }
        Log.sendLogAsync(); (9)
        Dialog.show("Connection Error", (10)
            "There was a networking error in the connection to " +
            err.getConnectionRequest().getUrl(), "OK", null);
    });
}
1 init is the first of the four lifecycle methods. It’s responsible for initialization of variables and values
2 By default Codename One has one thread that performs all the networking, we set the default to two which gives better performance
3 The theme determines the appearance of the application. We’ll discuss this in the next chapter
4 This enables the Toolbar API by default, it allows finer control over the title bar area
5 Crash protection automatically sends device crash logs through the cloud
6 In case of a network error the code in this block would run, you can customize it to handle networking errors effectively
7 consume() swallows the event so it doesn’t trigger other alerts, it generally means “we got this”
8 Not all errors include an exception, if we have an exception we can log it with this code
9 This will email the log from the device to you if you have a pro subscription
10 This shows an error dialog to the user, in production you might want to remove that code

init(Object) works as a constructor to some degree. We recommend avoiding the constructor for the main class and placing logic in the init method instead. This isn’t crucial but we recommend it since the constructor might happen too early in the application lifecycle.

In a cold start init(Object) is invoked followed by the start() method. However, start() can be invoked more than once if an app is minimized and restored, see the sidebar Application Lifecycle:

Listing 3. HelloWorld start()
public void start() {
    if(current != null){ (1)
        current.show(); (2)
        return;
    }
    Form hi = new Form("Hi World", BoxLayout.y()); (3)
    hi.add(new Label("Hi World")); (4)
    hi.show(); (5)
}
1 If the app was minimized we usually don’t want to do much, just show the last Form of the application
2 current is a Form which is the top most visual element. We can only have one Form showing and we enforce that by using the show() method
3 We create a new simple Form instance. It has the title “Hello World” and arranges elements vertically (on the Y axis)
4 We add another Label below the title, see figure [TitleAndLabelImage]. We will discuss component hierarchy later
5 The show() method places the Form on the screen. Only one Form can be shown at a time
Title and Label in the UI
Figure 9. Title and Label in the UI

There are some complex ideas within this short snippet which I’ll address later in this chapter when talking about layout. The gist of it is that we create and show a Form. Form is the top level UI element, it takes over the whole screen. We can add UI elements to that Form object, in this case the Label. We use the BoxLayout to arrange the elements within the Form from top to the bottom vertically.

Application Lifecycle

A few years ago Romain Guy (a senior Google Android engineer) was on stage at the Google IO conference. He asked for a show of hands of people who understand the Activity lifecycle (Activity is similar to a Codename One main class). He then proceeded to jokingly call the audience members who lifted their hands “liars” claiming that after all his years in Google he still doesn’t understand it…​

Lifecycle seems simple on the surface but hides a lot of nuance. Android’s lifecycle is ridiculously complex. Codename One tries to simplify this and also make it portable. Sometimes complexity leaks out and the nuances can be difficult to deal with.

Simply explained an application has three states:

  • Foreground - it’s running and in the foreground which means the user can physically interact with the app

  • Suspended - the app isn’t in the foreground, it’s either paused or has a background process running

  • Not Running - the app was never launched, was killed or crashed

The lifecycle is the process of transitioning between these 3 states and the callbacks invoked when such a transition occurs. The first time we launch the app we start from a “Cold Start” (Not Running State) but on subsequent launches the app is usually started from the "Warm Start" (Suspended State).

Codename One Application Lifecycle
Figure 10. Codename One Application Lifecycle

Codename One has four standard callback methods in the lifecycle API:

  • init(Object) - is invoked when the app is first launched from a Not Running state.

  • start() - is invoked for two separate cases. After start() is finished the app transitions to the Foreground state.

    • Following init(Object) in case of a cold start. Cold start refers to starting the app from a Not Running state.

    • When the app is restored from Suspended state. In this case init(Object) isn’t invoked

  • stop() - is invoked when the app is minimized e.g. when switching to a different app. After stop() is finished the app transitions to the Suspended state.

  • destroy() - is invoked when the app is destroyed e.g. killed by a user in the task manager. After destroy() is finished the app is no longer running hence it’s in the Not Running state.

destroy() is optional there is no guarantee that it will be invoked. It should be used only as a last resort

Now that we have a general sense of the lifecycle lets look at the last two lifecycle methods:

Listing 4. HelloWorld stop() and destroy()
public void stop() { (1)
    current = getCurrentForm(); (2)
    if(current instanceof Dialog) { (3)
        ((Dialog)current).dispose();
        current = getCurrentForm();
    }
}

public void destroy() { (4)
}
1 stop() is invoked when the app is minimized or a different app is opened
2 As the app is stopped we save the current Form so we can restore it back in start() if the app is restored
3 Dialog is a bit of a special case restoring a Dialog might block the proper flow of application execution so we dispose them and then get the parent Form
4 destroy() is a very special case. Under normal circumstances you shouldn’t write code in destroy(). stop() should work for most cases

That’s it. Hopefully you have a general sense of the code. It’s time to run on the device.

1.8.3. Building and Deploying On Devices

Codename One Settings
Figure 11. Codename One Settings

You can use the project settings to configure almost anything. Specifically, the application title, application version, application icon etc. are all found in the right click menu of your IDE.

Just right click your Codename One project icon in any IDE, select Codename OneCodename One Settings.

There are many options within this UI that control almost every aspect of the application from signing to basic settings.

Signing/Certificates

All of the modern mobile platforms require signed applications but they all take radically different approaches when implementing it.

Signing is a process that marks your final application for the device with a special value. This value (signature) is a value that only you can generate based on the content of the application and your certificate. Effectively it guarantees the app came from you. This blocks a 3rd party from signing their apps and posing as you to the appstore or to the user. It’s a crucial security layer.

A certificate is the tool we use for signing. Think of it as a mathematical rubber stamp that generates a different value each time. Unlike a rubber stamp a signature can’t be forged!

Signing on Android
Backup your Android certificate and save its password!
If you lose your Android certificate you will not be able to update your app

Android uses a self signed certificate approach. You can just generate a certificate by describing who you are and picking a password!

Anyone can do that. However, once a certificate is published it can’t be replaced…​

If this wasn’t the case someone else could potentially push an “upgrade” to your app. Once an app is submitted with a certificate to Google Play this app can’t be updated with any other certificate.

With that in mind generating an Android certificate is trivial.

The following chart illustrates a process that’s identical on all IDE’s
generate certificate android
Figure 12. Process of Certificate Generation for Android
Your certificate will generate into the file Keychain.ks in your home directory
Make sure to back that up and the password as losing these can have dire consequences
Should I Use a Different Certificate for Each App?

In theory yes. In practice it’s a pain…​ Keeping multiple certificates and managing them is a pain so we often just use one.

The drawback of this approach occurs when you are building an app for someone else or want to sell the app. Giving away your certificate is akin to giving away your house keys. So it makes sense to have separate certificates for each app.

Signing and Provisioning iOS

Code signing for iOS relies on Apple as the certificate authority. This is something that doesn’t exist on Android. iOS also requires provisioning as part of the certificate process and completely separates the process for development/release.

But first let’s start with the good news:

  • Losing an iOS certificate is no big deal - in fact we revoke them often with no impact on shipping apps

  • Codename One has a wizard that hides most of the pain related to iOS signing

In iOS Apple issues the certificates for your applications. That way the certificate is trusted by Apple and is assigned to your Apple iOS developer account. There is one important caveat: You need an iOS Developer Account and Apple charges a 99USD Annual fee for that.

The 99USD price and requirement have been around since the introduction of the iOS developer program for roughly 10 years at the time of this writing. It might change at some point though

Apple also requires a “provisioning profile” which is a special file bound to your certificate and app. This file describes some details about the app to the iOS installation process. One of the details it includes during development is the list of permitted devices.

The 4 files Required for iOS Signing and Provisioning
Figure 13. The Four Files Required for iOS Signing and Provisioning

We need 4 files for signing. Two certificates and two provisioning profiles:

  1. Production — The production certificate/provisioning pair is used for builds that are uploaded to iTunes

  2. Development — The development certificate/provisioning is used to install on your development devices

The certificate wizard automatically creates these 4 files and configures them for you.

Using the iOS Certificate Wizard Steps 1 and 2
Figure 14. Using the iOS Certificate Wizard Steps 1 and 2
Using the iOS Certificate Wizard Steps 3 and 4
Figure 15. Using the iOS Certificate Wizard Steps 3 and 4
Using the iOS Certificate Wizard Steps 5 and 6
Figure 16. Using the iOS Certificate Wizard Steps 5 and 6

If you have more than one project you should use the same iOS P12 certificate files in all the projects and just regenerate the provisioning. In this situation the certificate wizard asks you if you want to revoke the existing certificate which you shouldn’t revoke in such a case. You can update the provisioning profile in Apple’s iOS developer website.

One important aspect of provisioning on iOS is the device list in the provisioning step. Apple only allows you to install the app on 100 devices during development. This blocks developers from skipping the appstore altogether. It’s important you list the correct UDID for the device in the list otherwise install will fail.

There are several apps and tools that offer the UDID of the device, they aren’t necessarily reliable and might give a fake number!
Get the UDID of a Device
Figure 17. Get the UDID of a Device
You can right click the UDID and select copy to copy it

The simplest and most reliable process for getting a UDID is via iTunes. I’ve used other approaches in the past that worked but this approach is guaranteed.

Ad hoc provisioning allows 1000 beta testers for your application but it’s a more complex process that we won’t discuss here although it’s supported by Codename One
Build and Install

Before we continue with the build we should sign up at https://www.codenameone.com/build-server.html where you can soon follow the progress of your builds. You need a Codename One account in order to build for the device.

Now that we have certificates the process of device builds is literally a right click away for both OS’s. We can right click the project and select Codename OneSend iOS Debug Build or Codename OneSend Android Build.

Right click menu options for sending device builds
Figure 18. Right click menu options for sending device builds
The first time you send a build you will be prompted for the email and password you provided when signing up for Codename One

Once you send a build you should see the results in the build server page:

Build Results
Figure 19. Build Results
On iOS make sure you use Safari when installing, as 3rd party browsers might have issues

Once you go through those steps you should have the HelloWorld app running on your device. This process is non-trivial when starting so if you run into difficulties don’t despair and seek help at the discussion forum (https://www.codenameone.com/discussion-forum.html) or stack overflow (https://stackoverflow/tags/codenameone/). Once you go through signing and installation, it becomes easier.

You can also install the application either by emailing the install link to your account (using the e-mail Link button)

You can also download the binaries in order to upload them to the appstores.

1.9. Kotlin

Codename One started before Kotlin became public. Kotlin has since shown itself as an interesting option for developers especially within the Android community. With that in mind we decided to integrate support for Kotlin into Codename One.

To use Kotlin with Codename One you will need the following:

  • You need to use IntelliJ/IDEA - Currently only the IDEA version of the Codename One plugin has Kotlin support

  • You need to install the Kotlin support libraries from the extension manager tool in Codename One Preferences

  • Don’t use the project conversion tools or accept the warning that the project isn’t a Kotlin project. We do our own build process

  • Warnings and errors aren’t listed correctly and a build that claimed to have 2 errors actually passed…​

  • This will increase your jar size by roughly 730kb which might make it harder for free tier users

You can install the Kotlin libraries which are required for compilation by using the extension manager.

In the right click menu select Codename OneCodename One Preferences.

Select Extensions type in kotlin and install. Then right click the project select Codename OneRefresh Libs.

1.9.1. Hello Kotlin

Due to the way Kotlin works you can just create a regular Java project and convert sources to Kotlin. You can mix Java and Kotlin code without a problem and Codename One would "just work".

The hello world Java source file looks like this (removed some comments and whitespace):

public class MyApplication {
    private Form current;
    private Resources theme;

    public void init(Object context) {
        theme = UIManager.initFirstTheme("/theme");
        Toolbar.setGlobalToolbar(true);
        Log.bindCrashProtection(true);
    }

    public void start() {
        if(current != null){
            current.show();
            return;
        }
        Form hi = new Form("Hi World", BoxLayout.y());
        hi.add(new Label("Hi World"));
        hi.show();
    }

    public void stop() {
        current = getCurrentForm();
        if(current instanceof Dialog) {
            ((Dialog)current).dispose();
            current = getCurrentForm();
        }
    }

    public void destroy() {
    }
}

When you select that file and select the menu option CodeConvert Java file to Kotlin File you should get this:

class MyApplication {
    private var current: Form? = null
    private var theme: Resources? = null

    fun init(context: Any) {
        theme = UIManager.initFirstTheme("/theme")
        Toolbar.setGlobalToolbar(true)
        Log.bindCrashProtection(true)
    }

    fun start() {
        if (current != null) {
            current!!.show()
            return
        }
        val hi = Form("Hi World", BoxLayout.y())
        hi.add(Label("Hi World"))
        hi.show()
    }

    fun stop() {
        current = getCurrentForm()
        if (current is Dialog) {
            (current as Dialog).dispose()
            current = getCurrentForm()
        }
    }

    fun destroy() {
    }
}

That’s pretty familiar. The problem is that there are two bugs in the automatic conversion…​ That is the code for Kotlin behaves differently from standard Java.

The first problem is that Kotlin classes are final unless declared otherwise so we need to add the open keyword before the class declaration as such:

open class MyApplication

This is essential as the build server will fail with weird errors related to instanceof.

This only applies to the main class of the project, other classes in Codename One can remain final

The second problem is that arguments are non-null by default. The init method might have a null argument. So this fails with an exception. The solution is to add a question mark to the end of the call: fun init(context: Any?).

So the full working sample is:

open class MyApplication {
    private var current: Form? = null
    private var theme: Resources? = null
    fun init(context: Any?) {
        theme = UIManager.initFirstTheme("/theme")
        Toolbar.setGlobalToolbar(true)
        Log.bindCrashProtection(true)
    }

    fun start() {
        if (current != null) {
            current!!.show()
            return
        }
        val hi = Form("Hi World", BoxLayout.y())
        hi.add(Label("Hi World"))
        hi.show()
    }

    fun stop() {
        current = getCurrentForm()
        if (current is Dialog) {
            (current as Dialog).dispose()
            current = getCurrentForm()
        }
    }

    fun destroy() {
    }
}

Once all of that is in place Kotlin should just work. This should be possible for additional JVM languages in the future.

2. Basics: Themes, Styles, Components and Layouts

Let’s start with a brief overview of the ideas within Codename One. We’ll dig deeper into these ideas as we move forward.

2.1. Components

Every button, label or element you see on the screen in a Codename One application is a Component. This is a highly simplified version of this class hierarchy:

The Core Component Class Hierarchy
Figure 20. The Core Component Class Hierarchy

The Form is a special case Component. It’s the root component that you can show to the user. Container is a Component type that can hold other components within it. This lets us create elaborate hierarchies by nesting Container instances.

Component UML
Figure 21. Component UML

A Codename One application is effectively a series of forms, only one Form can be shown at a time. The Form includes everything we see on the screen. Under the hood the Form is comprised of a few separate pieces:

Structure of a Form
Figure 22. Structure of a Form
  • Content Pane - this is literally the body of the Form. When we add a Component into the Form it goes into the content pane. Notice that Content Pane is scrollable by default on the Y axis!

  • Title Area - we can’t add directly into this area. The title area is managed by the Toolbar class. Toolbar is a special component that resides in the top portion of the form and abstracts the title design. The title area is broken down into two parts:

    • Title of the Form and its commands (the buttons on the right/left of the title)

    • Status Bar - on iOS the area on the top includes a special space so the notch, battery, clock etc. can fit. Without this the battery indicator/clock or notch would be on top of the title

Now that we understand this let’s look at the new project we created and open the Java file TodoApp.java. In it we should see the lines that setup the UI in the start() method:

2.1.1. Layout Managers

A layout manager is an algorithm that decides the size and location of the components within a Container. Every Container has a layout manager associated with it. The default layout manager is FlowLayout.

To understand layouts we need to understand a basic concept about Component. Each component has a “preferred size”. This is the size in which a component “wants” to appear. E.g. for a Label the preferred size will be the exact size that fits the label text, icon and padding of the component.

Understanding Preferred Size

The Component class contains many useful methods. One of the most important ones is calcPreferredSize() which is invoked to recalculate the size a component “wants” when something changes

By default Codename One invokes the getPreferredSize() method and not calcPreferredSize() directly.
getPreferredSize() invokes calcPreferredSize() and caches the value

The preferred size is decided by the component based on internal constraints such as the font size, border sizes, padding etc.

When a layout manager positions and sizes the component, it MIGHT take the preferred size into account. Notice that it MIGHT ignore it entirely!

E.g. FlowLayout always gives components their exact preferred size, yet BorderLayout resizes the center component by default (and the other components are resized on one of their axis).

You can define a group of components to have the same preferred width or height by using the setSameWidth and setSameHeight methods e.g.:

Listing 5. setSameWidth/Height
Component.setSameWidth(cmp1, cmp2, cmp3, cmp4);
Component.setSameHeight(cmp5, cmp6, cmp7);

Codename One has a setPreferredSize method that allows developers to explicitly request the size of the component. However, this caused quite a lot of problems. E.g. the preferred size should change with device orientation or similar operations. The API also triggered frequent inadvertent hardcoding of UI values such as forcing pixel sizes for components. As a result the method was deprecated.

We recommend developers use setSameWidth, setSameHeight when sizing alignment is needed. setHidden when hiding is needed. As a last resort we recommend overriding calcPreferredSize.

A layout manager places a component based on its own logic and the preferred size (sometimes referred to as “natural size”). A FlowLayout will just traverse the components based on the order they were added and size/place them one after the other. When it reaches the end of the row it will go to the new row.

Use FlowLayout Only for Simple Things
FlowLayout is great for simple things but has issues when components change their sizes dynamically (like a text field). In those cases it can make bad decisions about line breaks and take up too much space
Layout Manager Primer Part I
Figure 23. Layout Manager Primer Part I
Layout Manager Primer Part II
Figure 24. Layout Manager Primer Part II

Scrolling doesn’t work well for all types of layouts as the positioning algorithm within the layout might break. Scrolling on the Y axis works great for BoxLayout Y which is why I picked it for the TodoForm:

Table 1. Scrolling in Layout Managers
Layout Scrollable

Flow Layout

Possible on Y axis only

Border Layout

Scrolling is blocked

Box Layout Y

Scrollable only on the Y axis

Box Layout X

Scrollable only on the X axis

Grid Layout

Scrollable

LayeredLayout

Not scrollable (usually)

Nesting Scrollable Containers

Only one element can be scrollable within the hierarchy, otherwise if you drag your finger over the Form Codename One won’t know which element you are trying to scroll. By default form’s content pane is scrollable on the Y axis unless you explicitly disable it (setting the layout to BorderLayout implicitly disables scrolling).

It’s important to notice that it’s OK to have non-scrollable layouts, e.g. BorderLayout, as items within a scrollable container type. E.g. in the TodoApp we added TodoItem which uses BorderLayout into a scrollable BoxLayout Form.

Layouts can be divided into two distinct groups:

  • Constraint Based - BorderLayout (and a few others such as GridBagLayout, MigLayout and TableLayout)

  • Regular - All of the other layout managers

When we add a Component to a Container with a regular layout we do so with a simple add method:

Listing 6. Adding to a Regular Container
Container cnt = new Container(BoxLayout.y());
cnt.add(new Label("Just Added"));

This works great for regular layouts but might not for constraint based layouts. A constraint based layout accepts another argument. E.g. BorderLayout needs a location for the Component:

Listing 7. Adding to a Regular Container
cnt.add(NORTH, new Label("Just Added"));

This line assumes you have an import static com.codename1.ui.CN.*; in the top of the file. In BorderLayout (which is a constraint based layout) placing an item in the NORTH places it in the top of the Container.

The CN class is a class that contains multiple static helper methods and functions. It’s specifically designed for static import in this way to help keep our code terse
Static Global Context

The CN class is a thin wrapper around features in Display, NetworkManager, Storage, FileSystemStorage etc. It also adds common methods and constants from several other classes so Codename One code feels more terse e.g. we can do:

import static com.codename1.ui.CN.*;
That’s optional, if you don’t like static imports you can just write CN. for every element

From that point on you can write code that looks like this:

callSerially(() -> runThisOnTheEDT());

Instead of:

Display.getInstance().callSerially(() -> runThisOnTheEDT());

The same applies for most network manager calls e.g.:

addToQueue(myConnectionRequest);

Instead of:

NetworkManager.getInstance().addToQueue(myConnectionRequest);

Some things were changed so we won’t have too many conflicts e.g. Log.p or Log.e would have been problematic so we now have:

log("my log message");
log(myException);

Instead of Display.getInstance().getCurrent() we now have getCurrentForm() since getCurrent() is too generic. For most methods you should just be able to remove the NetworkManager or Display access and it should "just work".

The motivation for this is three fold:

  • Terse code

  • Small performance gain

  • Cleaner API without some of the baggage in Display or NetworkManager

Some of our samples in this guide might rely on that static import being in place. This helps us keep the code terse and readable in the code listings.

Terse Syntax

Almost every layout allows us to add a component using several variants of the add method:

Listing 8. Versions of add
Container cnt = new Container(BoxLayout.y());
cnt.add(new Label("Just Added")); (1)
cnt.addAll(new Label("Adding Multiple"), (2)
    new Label("Second One"));

cnt.add(new Label("Chaining")). (3)
    add(new Label("Value"));
1 Regular add
2 addAll accepts several components and adds them in a batch
3 add returns the parent Container instance so we can chain calls like that

In the race to make code “tighter” we can make this even shorter. Almost all layout managers have their own custom terse syntax style e.g.:

Listing 9. Terse Syntax
Container boxY = BoxLayout.encloseY(cmp1, cmp2); (1)
Container boxX = BoxLayout.encloseX(cmp3, cmp4);
Container flowCenter = FlowLayout. (2)
    encloseCenter(cmp5, cmp6);
1 Most layouts have a version of enclose to encapsulate components within
2 FlowLayout has variants that support aligning the components on various axis

To sum this up, we can use layout managers and nesting to create elaborate UI’s that implicitly adapt to different screen sizes and device orientation.

Flow Layout
Flow Layout
Figure 25. Flow Layout

Flow layout lets the components “flow” horizontally and break a line when reaching the edge of the container. It’s the default layout manager for containers. Because it’s so flexible it’s also problematic as it can result in incorrect preferred size values for the parent Container. This can create a reflow issue, as a result we recommend using flow layout only for trivial cases. Avoid it for things such as text input etc. As the size of the text input can vary in runtime.

Form hi = new Form("Flow Layout", new FlowLayout());
hi.add(new Label("First")).
    add(new Label("Second")).
    add(new Label("Third")).
    add(new Label("Fourth")).
    add(new Label("Fifth"));
hi.show();

Flow layout also supports terse syntax shorthand such as:

Container flowLayout = FlowLayout.encloseIn(
        new Label("First"),
        new Label("Second"),
        new Label("Third"),
        new Label("Fourth"),
        new Label("Fifth")));

Flow layout can be aligned to the left (the default), to the center, or to the right. It can also be vertically aligned to the top (the default), middle (center), or bottom.

Flow layout aligned to the center
Figure 26. Flow layout aligned to the center
Flow layout aligned to the right
Figure 27. Flow layout aligned to the right
Flow layout aligned to the center horizontally & the middle vertically
Figure 28. Flow layout aligned to the center horizontally & the middle vertically

Components within the flow layout get their natural preferred size by default and are not stretched in any axis.

The natural sizing behavior is often used to prevent other layout managers from stretching components. E.g. if we have a border layout element in the south and we want it to keep its natural size instead of adding the element to the south directly we can wrap it using parent.add(BorderLayout.SOUTH, FlowLayout.encloseCenter(dontGrowThisComponent)).
Box Layout

BoxLayout places elements in a row (X_AXIS) or column (Y_AXIS) according to box orientation. Box is a very simple and predictable layout that serves as the "workhorse" of component lists in Codename One.

You can create a box layout Y using something like this:

Form hi = new Form("Box Y Layout", new BoxLayout(BoxLayout.Y_AXIS));
hi.add(new Label("First")).
    add(new Label("Second")).
    add(new Label("Third")).
    add(new Label("Fourth")).
    add(new Label("Fifth"));

Which results in this

BoxLayout Y
Figure 29. BoxLayout Y

Box layout also supports a shorter terse notation which we use here to demonstrate the X axis box.

Container box = BoxLayout.encloseX(new Label("First"),
        new Label("Second"),
        new Label("Third"),
        new Label("Fourth"),
        new Label("Fifth")));
BoxLayout X
Figure 30. BoxLayout X

The box layout keeps the preferred size of its destination orientation and scales elements on the other axis. Specifically X_AXIS will keep the preferred width of the component while growing all the components vertically to match in size. Its Y_AXIS counterpart keeps the preferred height while growing the components horizontally.

This behavior is very useful since it allows elements to align as they would all have the same size.

In some cases the growing behavior in the X axis is undesired, for these cases we can use the X_AXIS_NO_GROW variant.

BoxLayout X_AXIS_NO_GROW
Figure 31. BoxLayout X_AXIS_NO_GROW
FlowLayout vs. BoxLayout.X_AXIS
When applicable we recommend BoxLayout over FlowLayout as it acts more consistently in all situations. Another advantage of BoxLayout is the fact that it grows and thus aligns nicely
Border Layout
Border Layout
Figure 32. Border Layout

Border layout is quite unique. BorderLayout is a constraint-based layout that can place up to five components in one of the five positions: NORTH, SOUTH, EAST, WEST or CENTER.

Form hi = new Form("Border Layout", new BorderLayout());
hi.add(BorderLayout.CENTER, new Label("Center")).
    add(BorderLayout.SOUTH, new Label("South")).
    add(BorderLayout.NORTH, new Label("North")).
    add(BorderLayout.EAST, new Label("East")).
    add(BorderLayout.WEST, new Label("West"));
hi.show();
The Constraints are Included in the CN class
You can use the static import of the CN class and then the syntax can be add(SOUTH, new Label("South"))

The layout always stretches the NORTH/SOUTH components on the X-axis to completely fill the container and the EAST/WEST components on the Y-axis. The center component is stretched to fill the remaining area by default. However, the setCenterBehavior allows us to manipulate the behavior of the center component so it is placed in the center without stretching.

E.g.:

Form hi = new Form("Border Layout", new BorderLayout());
((BorderLayout)hi.getLayout()).setCenterBehavior(BorderLayout.CENTER_BEHAVIOR_CENTER);
hi.add(BorderLayout.CENTER, new Label("Center")).
    add(BorderLayout.SOUTH, new Label("South")).
    add(BorderLayout.NORTH, new Label("North")).
    add(BorderLayout.EAST, new Label("East")).
    add(BorderLayout.WEST, new Label("West"));
hi.show();

Results in:

Border Layout with CENTER_BEHAVIOR_CENTER
Figure 33. Border Layout with CENTER_BEHAVIOR_CENTER
Scrolling is Disabled in Border Layout
Because of its scaling behavior scrolling a border layout makes no sense. Container implicitly blocks scrolling on a border layout, but it can scroll its parents/children

In the case of RTL the EAST and WEST values are implicitly reversed as shown in this image:

Border Layout in RTL mode
Figure 34. Border Layout in RTL mode
RTL and Bidi

RTL (Right To Left) or Bidi (bi-directional) are common terms used for languages such as Hebrew, Arabic etc. These languages are written from the right to left direction hence all the UI needs to be “reversed”. Bidi denotes the fact that while the language is written from right to left, the numbers are still written in the other direction hence two directions…​

Preferred Size Still Matters
The preferred size of the center component doesn’t matter in border layout but the preferred size of the sides is. E.g. If you place an very large component in the SOUTH it will take up the entire screen and won’t leave room for anything
Grid Layout

GridLayout accepts a predefined grid (rows/columns) and grants all components within it equal size based on the dimensions of the largest components.

The main use case for this layout is a grid of icons e.g. like one would see in the iPhone home screen

If the number of rows * columns is smaller than the number of components added a new row is implicitly added to the grid. However, if the number of components is smaller than available cells (won’t fill the last row) blank spaces will be left in place.

In this example we can see that a 2x2 grid is used to add 5 elements, this results in an additional row that’s implicitly added turning the grid to a 3x2 grid implicitly and leaving one blank cell.

Form hi = new Form("Grid Layout 2x2", new GridLayout(2, 2));
hi.add(new Label("First")).
    add(new Label("Second")).
    add(new Label("Third")).
    add(new Label("Fourth")).
    add(new Label("Fifth"));
Grid Layout 2x2
Figure 35. Grid Layout 2x2

When we use a 2x4 size ratio we would see elements getting cropped as we do here. The grid layout uses the grid size first and doesn’t pay too much attention to the preferred size of the components it holds.

Grid Layout 2x4
Figure 36. Grid Layout 2x4

Grid also has an autoFit attribute that can be used to automatically calculate the column count based on available space and preferred width. This is really useful for working with UI’s where the device orientation might change.

There is also a terse syntax for working with a grid that has two versions, one that uses the "auto fit" option and another that accepts the number of columns. Here’s a sample of the terse syntax coupled with auto fit followed by screenshots of the same code in two orientations:

GridLayout.encloseIn(new Label("First"),
    new Label("Second"),
    new Label("Third"),
    new Label("Fourth"),
    new Label("Fifth")));
Grid Layout autofit portrait
Figure 37. Grid Layout autofit portrait
Grid Layout autofit landscape
Figure 38. Grid Layout autofit landscape

2.1.2. Table Layout

The TableLayout is a very elaborate constraint based layout manager that can arrange elements in rows/columns while defining constraints to control complex behavior such as spanning, alignment/weight etc.

Note the Different Package for TableLayout
The TableLayout is in the com.codename1.ui.table package and not in the layouts package.
This is due to the fact that TableLayout was originally designed for the Table class.

Despite being constraint based the TableLayout isn’t strict about constraints and will implicitly add a constraint when one is missing. This is unlike the BorderLayout which will throw an exception in this case.

Unlike GridLayout TableLayout won’t implicitly add a row if the row/column count is incorrect
Form hi = new Form("Table Layout 2x2", new TableLayout(2, 2));
hi.add(new Label("First")).
    add(new Label("Second")).
    add(new Label("Third")).
    add(new Label("Fourth")).
    add(new Label("Fifth"));
hi.show();
2x2 TableLayout with 5 elements
Figure 39. 2x2 TableLayout with 5 elements, notice that the last element is missing

TableLayout supports the ability to grow the last column which can be enabled using the setGrowHorizontally method. You can also use a shortened terse syntax to construct a TableLayout however since the TableLayout is a constraint based layout you won’t be able to utilize its full power with this syntax.

The default usage of the encloseIn method below uses the setGrowHorizontally flag.

Container tl = TableLayout.encloseIn(2, new Label("First"),
                new Label("Second"),
                new Label("Third"),
                new Label("Fourth"),
                new Label("Fifth")));
TableLayout.encloseIn() with default behavior of growing the last column
Figure 40. TableLayout.encloseIn() with default behavior of growing the last column
The Full Potential

TableLayout is a beast, to truly appreciate it we need to use the constraint syntax which allows us to span, align and set width/height for the rows and columns.

TableLayout works with a Constraint instance that can communicate our intentions into the layout manager. Such constraints can include more than one attribute e.g. span and height.

TableLayout constraints can’t be reused for more than one component

The constraint class supports the following attributes

Table 2. Constraint Properties

column

The column for the table cell. This defaults to -1 which will just place the component in the next available cell

row

Similar to column, defaults to -1 as well

width

The column width in percentages, -1 will use the preferred size. -2 for width will take up the rest of the available space

height

Similar to width but doesn’t support the -2 value

spanHorizontal

The cells that should be occupied horizontally defaults to 1 and can’t exceed the column count - current offset.

spanVertical

Similar to spanHorizontal with the same limitations

horizontalAlign

The horizontal alignment of the content within the cell, defaults to the special case -1 value to take up all the cell space can be either -1, Component.LEFT, Component.RIGHT or Component.CENTER

verticalAlign

Similar to horizontalAlign can be one of -1, Component.TOP, Component.BOTTOM or Component.CENTER

You only need to set width/height to one cell in a column/row

The table layout constraint sample tries to demonstrate some of the unique things you can do with constraints.

TableLayout tl = new TableLayout(2, 3); (1)
Form hi = new Form("Table Layout Cons", tl);
hi.setScrollable(false); (2)
hi.add(tl.createConstraint(). (3)
            widthPercentage(20),
                new Label("AAA")).

        add(tl.createConstraint(). (4)
            horizontalSpan(2).
            heightPercentage(80).
            verticalAlign(Component.CENTER).
            horizontalAlign(Component.CENTER),
                new Label("Span H")).

        add(new Label("BBB")).

        add(tl.createConstraint().
            widthPercentage(60).
            heightPercentage(20),
                new Label("CCC")).

        add(tl.createConstraint().
            widthPercentage(20),
                new Label("DDD"));
1 We need the TableLayout instance to create constraints. A constraint must be created for every component and must be used with the same layout as the parent container
2 To get the look in the screenshot we need to turn scrolling off so the height constraint doesn’t take up available height. Otherwise it will miscalculate available height due to scrolling. You can scroll a TableLayout but sizing will be different
3 We create the constraint and instantly apply width to it. This is a shorthand syntax for the code block below
4 We can chain constraint creation using a call like this so multiple constraints apply to a single cell. Notice that we don’t span and set width on the same axis (horizontal span + width), doing something like that would create confusing behavior

Here is the full code mentioned in item 3:

TableLayout.Constraint cn = tl.createConstraint();
cn.setWidthPercentage(20);
hi.add(cn, new Label("AAA")).
TableLayout constraints can be used to create very elaborate UI’s
Figure 41. TableLayout constraints can be used to create very elaborate UI’s
TextMode Layout

TextModeLayout is a unique layout manager. It acts like TableLayout on Android and like BoxLayout.Y_AXIS in other platforms. Internally it delegates to one of these two layout managers so in a sense it doesn’t have as much functionality of its own.

E.g. this is a sample usage of TextModeLayout:

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();
TextModeLayout on iOS
Figure 42. TextModeLayout on iOS
TextModeLayout on Android with the same code
Figure 43. TextModeLayout on Android with the same code

As you can see from the code and samples above there is a lot going on under the hood. On Android we want a layout that’s similar to TableLayout so we can “pack” the entries. On iOS we want a box layout Y type of layout but we also want the labels/text to align properly…​

The TextModeLayout isn’t really a layout as much as it is a delegate. When running in the Android mode (which we refer to as the “on top” mode) the layout is almost an exact synonym of TableLayout and in fact delegates to an underlying TableLayout. In fact there is a public final table instance within the layout that you can refer to directly…​

There is one small difference between the TextModeLayout and the underlying TableLayout and that’s our choice to default to align entries to TOP with this mode.

Aligning to TOP is important for error handling for TextComponent in Android otherwise the entries “jump”

When working in the non-android environment we use a BoxLayout on the Y axis as the delegate. There’s one thing we do here that’s different from a default box layout: grouping. Grouping allows the labels to align by setting them to the same width, internally it invokes Component.setSameWidth(). Since text components hide the labels there is a special group method there that can be used. However, this is implicit with the TextModeLayout which is pretty cool.

TextModeLayout was created specifically for the TextComponent and InputComponent so check out the section about them in the components chapter.

2.1.3. Layered Layout

When used without constraints, the LayeredLayout places the components in order one on top of the other and sizes them all to the size of the largest component. This is useful when trying to create an overlay on top of an existing component. E.g. an “x” button to allow removing the component.

The X on this button was placed there using the layered layout code below
Figure 44. The X on this button was placed there using the layered layout code below

The code to generate this UI is slightly complex and contains very few relevant pieces. The only truly relevant piece is this block:

hi.add(LayeredLayout.encloseIn(settingsLabel,
        FlowLayout.encloseRight(close)));

We are doing three distinct things here:

  1. We are adding a layered layout to the form

  2. We are creating a layered layout and placing two components within. This would be the equivalent of just creating a LayeredLayout Container and invoking add twice

  3. We use FlowLayout to position the X close button in the right position

When used without constraints, the layered layout sizes all components to the exact same size one on top of the other. It usually requires that we use another container within; in order to position the components correctly

This is the full source of the example for completeness:

Form hi = new Form("Layered Layout");
int w = Math.min(Display.getInstance().getDisplayWidth(), Display.getInstance().getDisplayHeight());
Button settingsLabel = new Button("");
Style settingsStyle = settingsLabel.getAllStyles();
settingsStyle.setFgColor(0xff);
settingsStyle.setBorder(null);
settingsStyle.setBgColor(0xff00);
settingsStyle.setBgTransparency(255);
settingsStyle.setFont(settingsLabel.getUnselectedStyle().getFont().derive(w / 3, Font.STYLE_PLAIN));
FontImage.setMaterialIcon(settingsLabel, FontImage.MATERIAL_SETTINGS);
Button close = new Button("");
close.setUIID("Container");
close.getAllStyles().setFgColor(0xff0000);
FontImage.setMaterialIcon(close, FontImage.MATERIAL_CLOSE);
hi.add(LayeredLayout.encloseIn(settingsLabel,
        FlowLayout.encloseRight(close)));

Forms have a built in layered layout that you can access via getLayeredPane(), this allows you to overlay elements on top of the content pane.

The layered pane is used internally by components such as InteractionDialog, AutoComplete etc.

Codename One also includes a GlassPane that resides on top of the layered pane. Its useful if you just want to "draw" on top of elements but is harder to use than layered pane
Insets and Reference Components

As of Codename One 3.7, LayeredLayout supports insets for its children. This effectively allows you to position child components precisely where you want them, relative to their container or siblings. This functionality forms the under-pinnings of the GUI Builder’s Auto-Layout mode.

As an example, suppose you wanted to position a button in the lower right corner of its container. This can be achieved with LayeredLayout as follows:

Container cnt = new Container(new LayeredLayout());
Button btn = new Button("Submit");
LayeredLayout ll = (LayeredLayout)cnt.getLayout();
cnt.add(btn);
ll.setInsets(btn, "auto 0 0 auto");

The result is:

Button positioned in bottom right using insets

The only thing new here is this line:

ll.setInsets(btn, "auto 0 0 auto");

This is called after btn has already been added to the container. It says that we want its insets to be "auto" on the top and left, and 0 on the right and bottom. This insets string follows the CSS notation of top right bottom left (i.e. start on top and go clockwise), and the values of each inset may be provided in pixels (px), millimetres (mm), percent (%), or the special "auto" value. Like CSS, you can also specify the insets using a 1, 2, or 3 values. E.g.

  1. "1mm" - Sets 1mm insets on all sides.

  2. "1mm 2mm" - Sets 1mm insets on top and bottom; 2mm on left and right.

  3. "1mm 10% 2mm" - Sets 1mm on top, 10% on left and right, and 2mm on bottom.

  4. "1mm 2mm 1px 50%" - Sets 1mm on top, 2mm on right, 1px on bottom, and 50% on left.

auto Insets

The special "auto" inset indicates that it is a flexible inset. If all insets are set to "auto", then the component will be centered both horizontally and vertically inside its "bounding box".

The "inset bounding box" is the containing box from which a component’s insets are measured. If the component’s insets are not linked to any other components, then its inset bounding box will be the inner bounds (i.e. taking padding into account) of the component’s parent container.

If one inset is fixed (i.e. defined in px, mm, or %), and the opposite inset is "auto", then the "auto" inset will simply allow the component to be its preferred size. So if you want to position a component to be centered vertically, and 5mm from the left edge, you could do:

ll.setInsets(btn, "auto auto auto 5mm");

Resulting in:

Button vertically centered 5mm from left edge
Figure 45. Button vertically centered 5mm from left edge

Move it to the right edge with:

ll.setInsets(btn, "auto 5mm auto auto");
% Insets

Percent (%) insets are calculated with respect to the inset bounding box. A 50% inset is measured as 50% of the length of the bounding box on the inset’s axis. E.g. A 50% inset on top would be 50% of the height of the inset bounding box. A 50% inset on the right would be 50% of the width of the inset bounding box.

Insets, Margin, and Padding

A component’s position in a layered layout is determined as follows: (Assume that cmp is the component that we are positioning, and cnt is the container (In pseudo-code):

x = cnt.paddingLeft + cmp.calculatedInsetLeft + cmp.marginLeft
y = cnt.paddingTop + cmp.calculatedInsetTop + cmp.marginTop
w = cnt.width - cnt.verticalScroll.width - cnt.paddingRight - cmp.calculatedInsetRight - cmp.marginRight - x
h = cnt.height - cnt.horizontalScroll.height - cnt.paddingBottom - cmp.calculatedInsetBottom - cmp.marginBottom - y
The calculatedInsetXXX values here will be the same as the corresponding provided inset if the inset has no reference component. If it does have a reference component, then the calculated inset will depend on the position of the reference component.

If no inset is specified, then it is assumed to be 0. This ensures compatibility with designs that were created before layered layout supported insets.

Component References: Linking Components together

If all you need to do is position a component relative to its parent container’s bounds, then mere insets provide you with sufficient vocabulary to achieve this. But most UIs are more complex than this and require another concept: reference components. In many cases you will want to position a component relative to another child of the same container. This is also supported.

For example, suppose I want to place a text field in the center of the form (both horizontally and vertically), and have a button placed beside it to the right. Positioning the text field is trivial (setInset(textField, "auto")), but there is no inset that we can provide that would position the button to the right of the text field. To accomplish our goal, we need to set the text field as a reference component of the button’s left inset - so that the button’s left inset is "linked" to the text field. Here is the syntax:

Container cnt = new Container(new LayeredLayout());
LayeredLayout ll = (LayeredLayout)cnt.getLayout();
Button btn = new Button("Submit");
TextField tf = new TextField();
cnt.add(tf).add(btn);
ll.setInsets(tf, "auto")
  .setInsets(btn, "auto auto auto 0")
  .setReferenceComponentLeft(btn, tf, 1f);

This would result in:

Button’s left inset linked to text field
Figure 46. Button’s left inset linked to text field

The two active lines here are the last two:

  .setInsets(btn, "auto auto auto 0") (1)
  .setReferenceComponentLeft(btn, tf, 1f); (2)
1 Sets the left inset on btn to 0.
2 Links btn’s left inset to `tf so that it is measured from the text field. The third parameter (1.0) is the reference position. This will generally either be 0 (meaning the reference point is the left edge of the text field), or 1 (meaning the reference point is the right edge of the text field). In this case we set a reference position of 1.0 because we want the button to be aligned to the text field’s right edge.
The reference position is defined as the distance, expressed as a fraction of the reference component’s length on the inset’s axis, between the reference component’s leading (outer) edge and the point from which the inset is measured. A reference position of 0 means that the inset is measured from the leading edge of the reference component. A value of 1.0 means that the inset is measured from the trailing edge of the reference component. A value of 0.5 means that the inset is measured from the center of the reference component. Etc…​ Any floating point value can be used. The designer currently only makes use of 0 and 1.

The definition above may make reference components and reference position seem more complex than it is. Some examples:

  1. For a top inset:

    1. referencePosition == 0 ⇒ the inset is measured from the top edge of the reference component.

    2. referencePosition == 1 ⇒ the inset is measured from the bottom edge of the reference component.

  2. For a bottom inset:

    1. referencePosition == 0 ⇒ the inset is measured from the bottom edge of the reference component.

    2. referencePosition == 1 ⇒ the inset is measured from the top edge of the reference component.

  3. For a left inset:

    1. referencePosition == 0 ⇒ the inset is measured from the left edge of the reference component.

    2. referencePosition == 1 ⇒ the inset is measured from the right edge of the reference component.

  4. For a right inset:

    1. referencePosition == 0 ⇒ the inset is measured from the right edge of the reference component.

    2. referencePosition == 1 ⇒ the inset is measured from the left edge of the reference component.

Layers In Codename One

Codename One allows placing components one on top of the other and we commonly use layered layout to do that. The form class has a builtin Container that resides in a layer on top of the content pane of the form.

When you add an element to a form it implicitly goes into the content pane. However, you can use getLayeredPane() and add any Component there. Such a Component will appear above the content pane. Notice that this layer resides below the title area (on the Y axis) and won’t draw on top of that.

When Codename One introduced the layered pane it was instantly useful. However, its popularity caused conflicts. Two separate pieces of code using the layered pane could easily collide with one another. Codename One solved it with getLayeredPane(Class c, boolean top). This method allocates a layer for a specific class within the layered pane. This way if two different classes use this method instead of the getLayeredPane() method they won’t collide. Each will get its own container in a layered layout within the layered pane seamlessly. The top flag indicates whether we want the layer to be the top most or bottom most layer within the layered pane (assuming it wasn’t created already). This allows you to place a layer that can appear above or below the already installed layers.

We only make use of the layered pane in this book but there are two additional layers on top of it. The form layered pane is identical to the layered pane but spans the entire height of the Form (including the title area). As a result the form layered pane is slower as it needs to handle some special cases to support this functionality.

The glass pane is the top most layer, unlike the layered pane it’s purely a graphical layer. You can only draw on the glass pane with a Painter instance and a Graphics object. You can’t add components into that layer.

The Layered Pane
Figure 47. The Layered Pane

2.1.4. GridBag Layout

GridBagLayout was introduced to simplify the process of porting existing Swing/AWT code with a more familiar API. The API for this layout is problematic as it was designed for AWT/Swing where styles were unavailable. As a result it has its own insets API instead of using elements such as padding/margin.

Our recommendation is to use Table which is just as powerful but has better Codename One integration.

To demonstrate GridBagLayout we ported the sample from the Java tutorial to Codename One.

Button button;
hi.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
//natural height, maximum width
c.fill = GridBagConstraints.HORIZONTAL;
button = new Button("Button 1");
c.weightx = 0.5;
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
hi.addComponent(c, button);

button = new Button("Button 2");
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 0.5;
c.gridx = 1;
c.gridy = 0;
hi.addComponent(c, button);

button = new Button("Button 3");
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 0.5;
c.gridx = 2;
c.gridy = 0;
hi.addComponent(c, button);

button = new Button("Long-Named Button 4");
c.fill = GridBagConstraints.HORIZONTAL;
c.ipady = 40;      //make this component tall
c.weightx = 0.0;
c.gridwidth = 3;
c.gridx = 0;
c.gridy = 1;
hi.addComponent(c, button);

button = new Button("5");
c.fill = GridBagConstraints.HORIZONTAL;
c.ipady = 0;       //reset to default
c.weighty = 1.0;   //request any extra vertical space
c.anchor = GridBagConstraints.PAGE_END; //bottom of space
c.insets = new Insets(10,0,0,0);  //top padding
c.gridx = 1;       //aligned with button 2
c.gridwidth = 2;   //2 columns wide
c.gridy = 2;       //third row
hi.addComponent(c, button);

Notice that because of the way gridbag works we didn’t provide any terse syntax API for it although it should be possible.

GridbagLayout sample from the Java tutorial running on Codename One
Figure 48. GridbagLayout sample from the Java tutorial running on Codename One

2.1.5. Group Layout

GroupLayout is a layout that would be familiar to the users of the NetBeans GUI builder (Matisse). Its a layout manager that’s really hard to use for manual coding but is powerful for some elaborate use cases. Although MiGLayout and LayeredLayout might be superior options.

It was originally added during the LWUIT days as part of an internal attempt to port Matisse to LWUIT. It’s still useful to this day as developers copy and paste Matisse code into Codename One and produce very elaborate layouts with drag and drop.

Since the layout is based on an older version of GroupLayout some things need to be adapted in the code or you should use the special "compatibility" library for Matisse to get better interaction. We also recommend tweaking Matisse to use import statements instead of full package names, that way if you use Label just changing the awt import to a Codename One import will make it use work for Codenmae One’s Label.

Unlike any other layout manager GroupLayout adds the components into the container instead of the standard API. This works nicely for GUI builder code but as you can see from this sample it doesn’t make the code very readable:

Form hi = new Form("GroupLayout");

Label label1 = new Label();
Label label2 = new Label();
Label label3 = new Label();
Label label4 = new Label();
Label label5 = new Label();
Label label6 = new Label();
Label label7 = new Label();

label1.setText("label1");

label2.setText("label2");

label3.setText("label3");

label4.setText("label4");

label5.setText("label5");

label6.setText("label6");

label7.setText("label7");

GroupLayout layout = new GroupLayout(hi.getContentPane());
hi.setLayout(layout);
layout.setHorizontalGroup(
    layout.createParallelGroup(GroupLayout.LEADING)
    .add(layout.createSequentialGroup()
        .addContainerGap()
        .add(layout.createParallelGroup(GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .add(label1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(LayoutStyle.RELATED)
                .add(layout.createParallelGroup(GroupLayout.LEADING)
                    .add(label4, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                    .add(label3, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                    .add(label2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)))
            .add(label5, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
            .add(layout.createSequentialGroup()
                .add(label6, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(LayoutStyle.RELATED)
                .add(label7, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)))
        .addContainerGap(296, Short.MAX_VALUE))
);
layout.setVerticalGroup(
    layout.createParallelGroup(GroupLayout.LEADING)
    .add(layout.createSequentialGroup()
        .addContainerGap()
        .add(layout.createParallelGroup(GroupLayout.TRAILING)
            .add(label2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
            .add(label1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(LayoutStyle.RELATED)
        .add(label3, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(LayoutStyle.RELATED)
        .add(label4, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(LayoutStyle.RELATED)
        .add(label5, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(LayoutStyle.RELATED)
        .add(layout.createParallelGroup(GroupLayout.LEADING)
            .add(label6, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
            .add(label7, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
        .addContainerGap(150, Short.MAX_VALUE))
);
GroupLayout Matisse generated UI running in Codename One
Figure 49. GroupLayout Matisse generated UI running in Codename One

If you are porting newer Matisse code there are simple changes you can do:

  • Change addComponent to add

  • Change addGroup to add

  • Remove references to ComponentPlacement and reference LayoutStyle directly

2.1.6. Mig Layout

MigLayout is a popular cross platform layout manager that was ported to Codename One from Swing.

MiG is still considered experimental so proceed with caution!
The API was deprecated to serve as a warning of its experimental status.

The best reference for MiG would probably be its quick start guide (PDF link). As a reference we ported one of the samples from that PDF to Codename One:

Form hi = new Form("MigLayout", new MigLayout("fillx,insets 0"));

hi.add(new Label("First")).
    add("span 2 2", new Label("Second")).  // The component will span 2x2 cells.
    add("wrap", new Label("Third")).      // Wrap to next row
    add(new Label("Forth")).
    add("wrap", new Label("Fifth")).    // Note that it "jumps over" the occupied cells.
    add(new Label("Sixth")).
    add(new Label("Seventh"));
hi.show();
MiG layout sample ported to Codename One
Figure 50. MiG layout sample ported to Codename One

It should be reasonably easy to port MiG code but you should notice the following:

  • MiG handles a lot of the spacing/padding/margin issues that are missing in Swing/AWT. With Codename One styles we have the padding and margin which are probably a better way to do a lot of the things that MiG does

  • The add method in Codename One can be changed as shown in the sample above.

  • The constraint argument for Coedname One add calls appears before the Component instance.

2.2. Themes and Styles

Next we need to introduce you to 3 important terms in Codename One: Theme, Style and UIID.

Themes are very similar conceptually to CSS, in fact they can be created with CSS syntax as we’ll discuss soon. The various Codename One ports ship with a native theme representing the appearance of the native OS UI elements. Every Codename One application has its own theme that derives the native theme and overrides behavior within it.

If the native theme has a button defined, we can override properties of that button in our theme. This allows us to customize the look while retaining some native appearances. This works by merging the themes to one big theme where our application theme overrides the definitions of the native theme. This is pretty similar to the cascading aspect of CSS if you are familiar with that.

Themes consist of a set of UIID definitions. Every component in Codename One has a UIID associated with it. UIID stands for User Interface Identifier. This UIID connects the theme to a specific component. A UIID maps to CSS classes if you are familiar with that concept. However, Codename One doesn’t support the complex CSS selector syntax options as those can impact runtime performance.

E.g. see this code where:

Listing 10. setUIID on TextField
nameText.setUIID("Label");

This is a text field component (user input field) but it will look like a Label.

Effectively we told the text field that it should use the UIID of Label when it’s drawing itself. It’s very common to do tricks like that in Codename One. E.g. button.setUIID("Label") would make a button appear like a label and allow us to track clicks on a “Label”.

The UIID’s translate the theme elements into a set of Style objects. These Style objects get their initial values from the theme but can be further manipulated after the fact. So if I want to make the text field’s foreground color red I could use this code:

Listing 11. setUIID on TextField
nameText.getAllStyles().setFgColor(0xff0000);

The color is in hexadecimal RRGGBB format so 0xff00 would be green and 0xff0000 would be red.

getAllStyles() returns a Style object but why do we need “all” styles?

Each component can have one of 4 states and each state has a Style object. This means we can have 4 style objects per Component:

  • Unselected — used when a component isn’t touched and doesn’t have focus. You can get that object with getUnselectedStyle().

  • Selected — used when a component is touched or if focus is drawn for non-touch devices. You can get that object with getSelectedStyle().

  • Pressed — used when a component is pressed. Notice it’s only applicable to buttons and button subclasses usually. You can get that object with getPressedStyle().

  • Disabled — used when a component is disabled. You can get that object with getDisabledStyle().

The getAllStyles() method returns a special case Style object that lets you set the values of all 4 styles from one class so the code before would be equivalent to invoking all 4 setFgColor methods. However, getAllStyles() only works for setting properties not for getting them!

Don’t use getStyle() for manipulation
getStyle() returns the current Style object which means it will behave inconsistently. The paint method uses getStyle() as it draws the current state of the Component but other code should avoid that method. Use the specific methods instead: getUnselectedStyle(), getSelectedStyle(), getPressedStyle(), getDisabledStyle() and getAllStyles()

As you can see, it’s a bit of a hassle to change styles from code which is why the theme is so appealing.

2.2.1. Theme

A theme allows the designer to define the styles externally via a set of UIID’s (User Interface ID’s), the themes are created via the Codename One Designer tool and allow developers to separate the look of the component from the application logic.

You can customize the theme using CSS which we’ll discuss a bit later

The theme is stored in the theme.res file in the src root of the project. We load the theme file using this line of code in the init(Object) method in the main class of the application:

Listing 12. Theme Loading Code
theme = UIManager.initFirstTheme("/theme");

This code is shorthand for resource file loading and for the installation of theme. You could technically have more than one theme in a resource file at which point you could use initNamedTheme() instead. The resource file is a special file format that includes inside it several features:

  • Themes

  • Images

  • Localization Bundles

  • Data files

It also includes some legacy features such as the old GUI builder.

We’re mentioning the legacy GUI builder for reference only we won’t discuss the old GUI builders in this guide

We can open the designer tool by double clicking the res file. The UI can be a bit overwhelming at first so I’ll try to walk slowly through the steps.

Codename One Designer Feature Map
Figure 51. Codename One Designer Feature Map
If you end up using CSS this section isn’t crucial but it’s worth browsing through as using the designer tool helps in tracking CSS issues

There are two crucial features in this tool: theming and images.

Add a New Image
Figure 52. Add a New Image

You will notice there is more than one type of image. We’ll discuss multi-images later.

Now we can go back to the theme view in the designer tool and press the Add button in the Unselected tab. Notice we have tabs for every state of Style as well as a special tabl for theme constants that we will discuss later.

The Add Button
Figure 53. The Add Button

After pressing that button we should see something that looks like this:

Add the Theme Entry for the Toolbar
Figure 54. Add the Theme Entry for the Toolbar
Don’t forget to press the save button in the designer after making changes

There are several other options in the add theme entry dialog. Lets go over them and review what we should do for each tab in this UI:

The Rest of the Add Theme Entry Dialog
Figure 55. The Rest of the Add Theme Entry Dialog - Part I
The Rest of the Add Theme Entry Dialog
Figure 56. The Rest of the Add Theme Entry Dialog - Part II

We’ll cover these options in-depth in the theming chapters.

Native Theme

By default Codename One applications are created with a theme that derives from the builtin OS native theme. You can add additional themes into the resource file by pressing the Add A New Theme button. You can also create multiple themes and ship them with your app or download them dynamically.

You can create a theme from scratch or customize one of the Codename one themes to any look you desire.

To preview the look of your theme in the various platforms use the Native Theme menu option in the designer
The native theme menu option
Figure 57. The native theme menu option

You can easily create deep customizations that span across all themes by adding a UIID or changing an existing UIID. E.g. looking at the getting started application that ships with the plugin you will notice a green button. This button is defined using the "GetStarted" UIID which is defined within the designer as:

  • A green background

  • White foreground

  • A thin medium sized font

  • Center aligned text

  • A small amount of spacing between the text and the edges

To achieve the colors we define them in the color tab.

We define the transparency to 255 which means the background will be a solid green color. This is important since the native OS’s might vary with the default value for background transparency so this should be defined explicitly
The Color tab for the get started button theme settings
Figure 58. The Color tab for the get started button theme settings

The alignment of the text is pretty simple, notice that the alignment style attribute applies to text and doesn’t apply to other elements. To align other elements we use layout manager logic.

The alignment tab for the get started button theme settings
Figure 59. The alignment tab for the get started button theme settings

Padding can be expressed in pixels, millimeters (approximate) or percentages of the screen size.

We recommend using millimeters for all measurements to keep the code portable for various device DPI’s.
The padding tab for the get started button theme settings
Figure 60. The padding tab for the get started button theme settings

The font uses native OS light font but has a fallback for older OS’s that don’t support truetype fonts. The "True Type" font will be applicable for most modern OS’s. In the case of the "native:" fonts Android devices will use Roboto whereas iOS devices will use Helvetica Neue. You can supply your own TTF and work with that.

Since Codename One cannot legally ship Helvetica Neue fonts the simulator will fallback to Roboto on PC’s.
At the time of this writing the desktop/simulator version of the Roboto font doesn’t support many common character sets (languages). This will have no effect on an Android device where thenative font works properly.
The font tab for the get started button theme settings
Figure 61. The font tab for the get started button theme settings

2.3. GUI Builder

The GUI builder allows us to arrange components visually within a UI using drag and drop, property sheets etc. With the GUI builder we can create elaborate, rich UI’s without writing the layout code.

Why two GUI Builders?

The original old GUI builder has its roots in our work at Sun Microsystems, it was developed directly into the designer tool and stores it’s data as part of the resource file. When creating an application for the old GUI builder you must define it as a "visual application" which will make it use the old GUI builder.

The roots of this GUI builder are pretty old. When we initially built it we still had to support feature phones with 2mb of RAM and the iPad wasn’t announced yet. Due to that we picked an architecture that made sense for those phones with a greater focus on navigation and resource usage. Newer mobile applications are rivaling desktop applications in complexity and in those situations the old GUI builder doesn’t make as much sense

The old GUI builder is in the designer tool, it’s a Swing application that includes the theme design etc.
It generates a Statemachine class that contains all the main user GUI interaction code.

The new GUI builder is a standalone application that you launch from the right click menu by selecting a form as explained below. Here are screenshots of both to help you differentiate:

The old GUI builder
Figure 62. The old GUI builder
The new GUI builder
Figure 63. The same UI in the new GUI builder

As of version 3.7, the new GUI Builder also supports an auto layout mode which allows you to freely position and resize components on a canvas. This mode is now the default for new GUI forms, and it always uses LayeredLayout as the root layout manager.

The new GUI builder auto layout mode
Figure 64. The new GUI builder in auto-layout mode

2.3.1. Hello World

Creating a hello world app in the new GUI builder is actually pretty trivial, you need to start with a regular handcoded application. Not a GUI builder application as it refers to the old GUI builder!

This is the exact same hello world application we created before…​

Following are the instructions for creating a form and launching the GUI builder. While they are similar there are minor IDE differences. Usage of the GUI builder is identical in all IDE’s as the GUI builder is a separate application.

NetBeans

In NetBeans you need to follow these 4 steps:

Right click the package select New → Other
Figure 65. Right click the package select NewOther
In the Codename One section select the GUI builder form
Figure 66. In the Codename One section select the GUI builder form
Type in the name of the form and click finish
Figure 67. Type in the name of the form and click finish, you can change the type to be a Container or Dialog
Launch the GUI builder thru the right click menu on the newly created file
Figure 68. Launch the GUI builder thru the right click menu on the newly created file
IntelliJ/IDEA

In IntelliJ you need to follow these 3 steps:

Right click the package select New → Codename One Form (or Dialog/Container)
Figure 69. Right click the package select NewCodename One AutoLayout Form (or Dialog/Container)
Type in a name for the new form
Figure 70. Type in a name for the new form
Launch the GUI builder thru the right click menu on the newly created file
Figure 71. Launch the GUI builder thru the right click menu on the newly created file
Eclipse

In Eclipse you need to follow these 4 steps:

Right click the package select New → Other
Figure 72. Right click the package select NewOther
In the Codename One section select the GUI builder option
Figure 73. In the Codename One section select the GUI builder option
Type in the name of the form and click finish
Figure 74. Type in the name of the form and click finish, you can change the type to be a Container or Dialog
Launch the GUI builder thru the right click menu on the newly created file
Figure 75. Launch the GUI builder thru the right click menu on the newly created file
Basic Usage

Notice that the UI of the new GUIBuilder might change in various ways but the basic concepts should remain the same.

The GUI builder is controlled via it’s main toolbar, notice that your changes will only be applied when you click the Save button on the right:

The features of the left toolbar
Figure 76. The features of the left toolbar
The features of the right toolbar
Figure 77. The features of the right toolbar

The main UI includes four important parts:

  • Main Form — This is where we place the components of the UI we are building

  • Component Tree — This is a logical representation of the component hierarchy within the Main Form. It’s often easier to pick a component from the tree rather than the form itself

  • Property Inspector — When we select an element in the tree or form we can see its details here. We can then edit the various details of the component in this area

  • Palette — Components can be dragged from the palette to the Main Form and placed in the UI

The four parts of the GUI builder
Figure 78. The four parts of the GUI builder

We’ll start by selecting the Component Palette and dragging a button into the UI:

You can drag any component you want from the palette to the main UI
Figure 79. You can drag any component you want from the palette to the main UI

By default the auto-layout mode of the GUI builder uses layered layout to position components. Sides can be bound to a component or to the Form. We then use distance units to determine the binding behavior. The GUI builder tries to be "smart" and guesses your intention as you drag the components along.

When you select the component you placed you can edit the properties of that component:

Properties allow you to customize everything about a component
Figure 80. Properties allow you to customize everything about a component

There are five property sheets per component:

  • Basic Settings — These include the basic configuration for a component e.g. name, icon, text etc.

  • Advanced Settings — These include features that aren’t as common such as icon gap, mask etc.

  • Events — By clicking a button in this tab a method will be added to the source file with a callback matching your component name. This will let you bind an event to a button, text field etc.

  • Layout — You can determine the layout of the parent Container here. For auto-layout this should stay as layered layout, however you can nest other layout types in here

  • Style Customization — This isn’t a theme, if you want to customize the style of a specific component you can do that through this UI. The theme works on a more global/reusable level and this is designed for a specific component only

For things like setting the text on the component we can use a convenient "long click" on the component to edit the text in place as such:

Use the long click to edit the text "in place"
Figure 81. Use the long click to edit the text "in place"
Events

When a component supports broadcasting events you can bind such events by selecting it, then selecting the events tab and clicking the button matching the event type

The events tab is listed below supported event types can be bound above
Figure 82. The events tab is listed below supported event types can be bound above

Once an event is bound the IDE will open to the event code e.g.:

public void onButton_1ActionEvent(com.codename1.ui.events.ActionEvent ev) {
}
Some IDE’s only generate the project source code after you explicitly build the project so if your code needs to access variables etc. try building first

Within the code you can access all the GUI components you defined with the gui_ prefix e.g. Button_1 from the UI is represented as:

private com.codename1.ui.Button gui_Button_1 = new com.codename1.ui.Button();
Underlying XML

Saving the project generates an XML file representing the UI into the res directory in the project, the GUI file is created in a matching hierarchy in the project under the res/guibuilder directory:

The java and GUI files in the hierarchy
Figure 83. The java and GUI files in the hierarchy
If you refactor (rename or move) the java file it’s connection with the GUI file will break. You need to move/rename both

You can edit the GUI file directly but changes won’t map into the GUI builder unless you reopen it. These files should be under version control as they are the main files that change. The GUI builder file for the button and label code looks like this:

<?xml version="1.0" encoding="UTF-8"?>

<component type="Form" layout="FlowLayout" flowLayoutFillRows="false" flowLayoutAlign="1"
flowLayoutValign="0"  title="My new title" name="MyForm">
  <component type="Button" text="Button" name="Button_1" actionEvent="true">
  </component>
  <component type="Label" text="Hi World" name="Label_1">
  </component>
</component>

This format is relatively simple and is roughly the same format used by the old GUI builder which makes the migration to the new GUI builder possible. This file triggers the following Java source file:

package com.mycompany.myapp;

/**
 * GUI builder created Form
 *
 * @author shai
 */
public class MyForm extends com.codename1.ui.Form {

    public MyForm() {
        this(com.codename1.ui.util.Resources.getGlobalResources());
    }

    public MyForm(com.codename1.ui.util.Resources resourceObjectInstance) {
        initGuiBuilderComponents(resourceObjectInstance);
    }

//-- DON'T EDIT BELOW THIS LINE!!!
    private com.codename1.ui.Label gui_Label_1 = new com.codename1.ui.Label();
    private com.codename1.ui.Button gui_Button_1 = new com.codename1.ui.Button();


// <editor-fold defaultstate="collapsed" desc="Generated Code">
    private void guiBuilderBindComponentListeners() {
        EventCallbackClass callback = new EventCallbackClass();
        gui_Button_1.addActionListener(callback);
    }

    class EventCallbackClass implements com.codename1.ui.events.ActionListener, com.codename1.ui.events.DataChangedListener {
        private com.codename1.ui.Component cmp;
        public EventCallbackClass(com.codename1.ui.Component cmp) {
            this.cmp = cmp;
        }

        public EventCallbackClass() {
        }

        public void actionPerformed(com.codename1.ui.events.ActionEvent ev) {
            com.codename1.ui.Component sourceComponent = ev.getComponent();
            if(sourceComponent.getParent().getLeadParent() != null) {
                sourceComponent = sourceComponent.getParent().getLeadParent();
            }

            if(sourceComponent == gui_Button_1) {
                onButton_1ActionEvent(ev);
            }
        }

        public void dataChanged(int type, int index) {
        }
    }
    private void initGuiBuilderComponents(com.codename1.ui.util.Resources resourceObjectInstance) {
        guiBuilderBindComponentListeners();
        setLayout(new com.codename1.ui.layouts.FlowLayout());
        setTitle("My new title");
        setName("MyForm");
        addComponent(gui_Label_1);
        addComponent(gui_Button_1);
        gui_Label_1.setText("Hi World");
        gui_Label_1.setName("Label_1");
        gui_Button_1.setText("Click Me");
        gui_Button_1.setName("Button_1");
    }// </editor-fold>

//-- DON'T EDIT ABOVE THIS LINE!!!
    public void onButton_1ActionEvent(com.codename1.ui.events.ActionEvent ev) {
    }

}
Don’t touch the code within the DON’T EDIT comments…​

The GUI builder uses the "magic comments" approach where code is generated into those areas to match the XML defined in the GUI builder. Various IDE’s generate that code at different times. Some will generate it when you run the app while others will generate it as you save the GUI in the builder.

You can write code freely within the class both by using the event mechanism, by writing code in the constructors or thru overriding functionality in the base class.

2.3.2. Auto-Layout Mode

As of version 3.7, new forms created with the GUI Builder will use auto-layout mode. In this mode you can move and resize your components exactly as you see fit. You aren’t constrained to the positions dictated by the form’s layout manager.

All forms designed in auto-layout mode use LayeredLayout
Auto-Layout Mode is built upon the inset support in LayeredLayout. Component positioning uses insets and reference components, not absolute positioning

As an example, let’s drag a button onto a blank form and see what happens. The button will be "selected" initially after adding, it so you’ll see its outline, and resize handles for adjusting its size and position. You’ll also see four floating labels (above, below, to the left, and to the right) that show the corresponding side’s inset values and allow you to adjust them.

Selected component in designer allows you to freely drag it to a new position
Figure 84. A button selected on the canvas in auto-layout mode. You can drag it to reposition it, or resize it using the resize handles.

Press the mouse inside the bounds of the button and drag it around to reposition it. You will notice that the inset labels change to reflect the new inset values. If you drag the button close to the edge of the form, the corresponding inset value will change to millimetres. If you move farther away from the edge, it will change to percentage values.

The Inset Control

Let’s take a closer look at the inset control (the inset controls are the black buttons that appear to the top, bottom, left, and right of the selected component).

Inset control
Figure 85. The inset control allows you to change the inset size and units, toggle it between fixed and flexible, and link it to another component.

Each control has three sections:

  1. The inset value drop-down menu. This shows the current value of the inset (e.g. 0mm, 25%, auto, etc…​). If you click on this, it will open a menu that will allow you to change the units. If the inset is currently in millimetres, it will have options for pixels, and percent. If the inset is in percent, it will have options for pixels and millimetres. Etc.. It also includes a text field to enter a an inset value explicitly.

    Inset drop-down menu
  2. The "Link" Button Link button - If the inset is linked to a reference component, then this button will be highlighted "blue", and hovering over it will highlight the reference component in the UI so that you can clearly see which component it is linked to. Clicking on this button will open a dialog that will allow you to "break" this link. You can drag this button over any component in the form to "link".

  3. The "Lock" Button" Inset fixed button - This button allows you to toggle the inset between "flexible" (i.e. auto) and "fixed" (i.e. millimetres or percent).

Auto Snap

Notice the "auto-snap" checkbox that appears in the top-right corner of the designer.

Auto-snap checkbox
Figure 86. Auto-snap checkbox

Auto-snap does exactly what it sounds like: It automatically snaps two components together when you drag them near each other. This is handy for linking components together without having to explicitly link them (using the "link" button). This feature is turned on by default. If auto-snap is turned off, you can still initiate a "snap" by holding down the ALT/Option key on your keyboard during the drag.

Smart Insets

Beside the "auto-snap" checkbox is another checkbox named "Smart Insets".

Smart insets checkbox
Figure 87. Smart insets checkbox

Smart Inset uses some heuristics during a drag to try to determine how the insets should be linked. Currently the heuristics are quite basic (it tries to link to the nearest neighbor component in most cases), but we will be working on improving this for future releases. This feature is turned off by default while it is still being refined. The goal is to improve this to the point where it always makes the correct link choices - at which time you will be able to use the designer without having any knowledge of insets or reference components.

The Widget Control Pad
Widget control pad
Figure 88. Widget control pad

When a component is selected, you should see a black floating panel appear in the lower right of the screen.

This is the widget control pad, and it provides an alternative view of the component’s links. It also provides a useful list of incoming links (i.e. components that "depend on" this component’s positioning). In some cases, you may want to disconnect incoming links so that you can drag the component without affecting the position of dependent components.

This control pad also includes game-pad-like controls (up, down, left, right), that allow you to "tab" the component to the next guide in that direction. Tab positions exist at component edges in the form. This is useful for aligning components with each other.

Keyboard Short-Cuts
  1. Arrow Keys - Use the up/down/left/right arrow keys to nudge the currently selected component a little bit at a time. This is a convenient way to move the component to a position that is more precise than can easily be achieved with a mouse drag.

  2. Arrow Keys + SHIFT - Hold down the SHIFT key while pressing an arrow key and it will "tab" the component to the next tab marker. The form has implicit tab markers at the edge of each component on the form.

  3. ALT/Option Key + Click or Drag - Holding down the option/alt key while clicking or dragging a component will resulting in "snapping" behaviour even if auto-snap is turned off.

Sub-Containers

In some cases, you may need to add sub-containers to your form to aid in grouping your components together. You can drag a container onto your form using the "Container" palette item (under "Core Components"). The default layout the subcontainer will be LayeredLayout so that you are able to position components within the sub-container with precision, just like on the root container.

You can also change the layout of subcontainers to another classical layout manager (e.g. grid layout, box layout, etc..) and drag components directly into it just as you did with the old designer. This is very useful if parts of your form lend themselves. As an example, let’s drag a container onto the canvas that uses BoxLayout Y. (You can find this under the "Containers" section of the component palette).

Drag the button (that was previously on the form) over that container, and you should see a drop-zone become highlighted.

Dropping container on child container with box layout y
Figure 89. Dropping container on child container with box layout y

You can drop the button directly there. You can As you drag more components into the sub-container, you’ll see them automatically laid out vertically.

Box Layout Y dropping 2nd child
Figure 90. Box Layout Y dropping 2nd child
The Canvas Resize Tool

When designing a UI with the new designer it is very important that you periodically test the form’s "resizing" behavior so that you know how it will behave on different devices. Components may appear to be positioned correctly when the canvas is one size, but become out of whack when the container is resized. After nearly every manipulation you perform, it is good practice to drag the canvas resize tool (the button in the lower right corner of the designer) smaller and bigger so you can see how the positions are changed. If things grow out of whack, you may need to toggle an inset between fixed and auto, or add a link between some of the components so that the resizing behavior matches your expectations.

3. Theme Basics

This chapter covers the creation of a simple hello world style theme and its visual customization. It uses the Codename One Designer tool to demonstrate basic concepts in theme creation such as 9-piece borders, selectors and style types. We would recommend reviewing this even if you end up using CSS.

3.1. Understanding Codename One Themes

Codename One themes are pluggable CSS like elements that allow developers to determine/switch the look of the application in runtime. A theme can be installed via the UIManager class and themes can be layered one on top of the other (like CSS). A theme can be generated from CSS as well, we’ll cover this (and the supported CSS syntax) soon.

By default, Codename One themes derive the native operating system themes, although this behavior is entirely optional.

A theme initializes the Style objects, which are then used by the components to render themselves or by the LookAndFeel and DefaultLookAndFeel classes to create the appearance of the application.

Codename One themes have some built-in defaults. E.g. borders for buttons and padding/margin/opacity for various components. These are a set of “common sense” defaults that can be overridden within the theme.

Codename One themes are effectively a set of UIID’s mapped to a Style object. Codename One applications always have a theme, you can modify it to suit your needs and you can add multiple themes within the main resource file.

You can also add multiple resource files to a project and work with them. In code a theme is initialized using this code in your main class:

private Resources theme;

public void init(Object context) {
    theme = UIManager.initFirstTheme("/theme");
}

The initFirstTheme method is a helper method that hides some try/catch logic as well as some verbosity. This could be expressed as:

try {
    theme = Resources.openLayered("/theme");
    UIManager.getInstance().setThemeProps(theme.getTheme(theme.getThemeResourceNames()[0]));
} catch(IOException e){
    e.printStackTrace();
}

3.2. Customizing Your Theme

We can launch the designer tool by double clicking on the theme.res file found in typical Codename One applications. In the left side you can see the section picker and within it the Theme section.

The theme area in the designer
Figure 91. The theme area in the designer

When you select the theme you will see the theme default view.

Theme default view
Figure 92. Theme default view

There are several interesting things to notice here the preview section allows us to instantly see the changes we make to the theme data.

Theme preview section
Figure 93. Theme preview section

The theme state tabs and constant tabs allow us to pass between the various editing modes for the theme and also add theme constants.

We discussed styles before, you can pick the right style mode through the tabs.

You can use these tabs to add the various types of styles and theme constants
Figure 94. You can use these tabs to add the various types of styles and theme constants

The most important section is the style section. It allows us to add/edit/remove style UIID’s.

Notice the Default Style section, it allows us to customize global defaults for the styles. Use it with caution as changes here can have wide implications.

The theme selection area allows us to add
Figure 95. The theme selection area allows us to add, edit and delete entries. Notice the default style entry which is a unique special case

When we add an entry to the style we can just type the desired UIID into the box at the top of the dialog. We can also pick a UIID from the combo box but that might not include all potential options.

You can use the Component Inspector tool in the simulator to locate a component and its UIID in a specific Form
When pressing the Add/Edit entry we can edit a specific style entry UIID
Figure 96. When pressing the Add/Edit entry we can edit a specific style entry UIID

When we add/edit an entry an important piece of the puzzle is the Derive check box that appears next to all of the UIID entries. All styles derive from the base style and usually from the native theme defaults, so when this flag is checked the defaults will be used.

When you uncheck that checkbox the fields below it become editable and you can override the default behavior. To restore the default just recheck that flag.

A common oddity for developers is that when they press Add and don’t derive any entry nothing is actually added. The entries in the theme are essentially key/value pairs so when you don’t add anything there are no keys so the entry doesn’t show up

3.3. Customizing The Title

The title is a great target for customization since it includes a few interesting "complexities".

The Title is surrounded by a TitleArea container that encloses it, above the title you will also see the StatusBar UIID that prevents the status details from drawing on top of the title text.

Title Area UIID’s
Figure 97. Title Area UIID’s
The StatusBar UIID is a special case that is only there on iOS. In iOS the application needs to render the section under the status bar (which isn’t the case for other OS’s) and the StatusBar UIID was added so developers can ignore that behavior.

3.3.1. Background Priorities and Types

A slightly confusing aspects of styles in Codename One is the priorities of backgrounds. When you define a specific type of background it will override prior definitions, this even applies to inheritance.

E.g. if the theme defined a border for the Button UIID (a very common case) if you will try to define the background image or the background color of Button those will be ignored!

The solution is to derive the border and select the Empty border type

The order for UIID settings for background is as follows:

  1. Border - if the component has a border it can override everything. Image borders always override all background settings you might have.

  2. Image or gradient - an image or gradient background overrides background color/transparency settings.

  3. Background Color/Transparency - If transparency is larger than 0 then this takes effect.

3.3.2. The Background Behavior and Image

Lets start in the first page of the style entry, we’ll customize the background behavior for the Title UIID and demonstrate/explain some of the behaviors.

The pictures below demonstrate the different types of background image behaviors.

IMAGE_SCALED scales the image without preserving aspect ratio to fit the exact size of the component
Figure 98. IMAGE_SCALED scales the image without preserving aspect ratio to fit the exact size of the component
IMAGE_SCALED_FILL scales the image while preserving aspect ratio so it fills the entire space of the component
Figure 99. IMAGE_SCALED_FILL scales the image while preserving aspect ratio so it fills the entire space of the component
Aspect ratio is the ratio between the width and the height of the image. E.g. if the image is 100x50 pixels and we want the width to be 200 pixels preserving the aspect ratio will require the height to also double to 200x100.
We highly recommend preserving the aspect ratio to keep images more "natural".
IMAGE_SCALED_FIT scales the image while preserving aspect ratio so it fits within the component
Figure 100. IMAGE_SCALED_FIT scales the image while preserving aspect ratio so it fits within the component
IMAGE_TILE_BOTH tiles the image on both axis of the component
Figure 101. IMAGE_TILE_BOTH tiles the image on both axis of the component
IMAGE_TILE_VERTICAL_ALIGN_LEFT tiles the image on the left side of the component
Figure 102. IMAGE_TILE_VERTICAL_ALIGN_LEFT tiles the image on the left side of the component
IMAGE_TILE_VERTICAL_ALIGN_CENTER tiles the image in the middle of the component
Figure 103. IMAGE_TILE_VERTICAL_ALIGN_CENTER tiles the image in the middle of the component
IMAGE_TILE_VERTICAL_ALIGN_RIGHT tiles the image on the right side of the component
Figure 104. IMAGE_TILE_VERTICAL_ALIGN_RIGHT tiles the image on the right side of the component
IMAGE_TILE_HORIZONTAL_ALIGN_TOP tiles the image on the top of the component
Figure 105. IMAGE_TILE_HORIZONTAL_ALIGN_TOP tiles the image on the top of the component
IMAGE_TILE_HORIZONTAL_ALIGN_CENTER tiles the image in the middle of the component
Figure 106. IMAGE_TILE_HORIZONTAL_ALIGN_CENTER tiles the image in the middle of the component
IMAGE_TILE_HORIZONTAL_ALIGN_BOTTOM tiles the image to the bottom of the component
Figure 107. IMAGE_TILE_HORIZONTAL_ALIGN_BOTTOM tiles the image to the bottom of the component
IMAGE_ALIGNED_TOP places the image centered at the top part of the component
Figure 108. IMAGE_ALIGNED_TOP places the image centered at the top part of the component
IMAGE_ALIGNED_BOTTOM places the image centered at the bottom part of the component
Figure 109. IMAGE_ALIGNED_BOTTOM places the image centered at the bottom part of the component
IMAGE_ALIGNED_LEFT places the image centered at the left part of the component
Figure 110. IMAGE_ALIGNED_LEFT places the image centered at the left part of the component
IMAGE_ALIGNED_RIGHT places the image centered at the right part of the component
Figure 111. IMAGE_ALIGNED_RIGHT places the image centered at the right part of the component
IMAGE_ALIGNED_TOP_LEFT places the image at the top left corner
Figure 112. IMAGE_ALIGNED_TOP_LEFT places the image at the top left corner
IMAGE_ALIGNED_TOP_RIGHT places the image at the top right corner
Figure 113. IMAGE_ALIGNED_TOP_RIGHT places the image at the top right corner
IMAGE_ALIGNED_BOTTOM_LEFT places the image at the bottom left corner
Figure 114. IMAGE_ALIGNED_BOTTOM_LEFT places the image at the bottom left corner
IMAGE_ALIGNED_BOTTOM_RIGHT places the image at the bottom right corner
Figure 115. IMAGE_ALIGNED_BOTTOM_RIGHT places the image at the bottom right corner
IMAGE_ALIGNED_CENTER places the image in the middle of the component
Figure 116. IMAGE_ALIGNED_CENTER places the image in the middle of the component

3.3.3. The Color Settings

The color settings are much simpler than the background behavior. As explained above the priority for color is at the bottom so if you have a border, image or gradient defined the background color settings will be ignored.

Add theme entry color settings
Figure 117. Add theme entry color settings

There are three color settings:

  • Foreground color is the RRGGBB color that sets the style foreground color normally used to draw the text of the component. You can use the color picker button on the side to pick a color

  • Background same as foreground only determines the background color of the component

  • Transparency represents the opacity of the component background as a value between 0 (transparent) and 255 (opaque)

Setting the background will have no effect unless transparency is higher than 0. If you don’t explicitly define this it might have a different value based on the native theme

3.3.4. Alignment

Not all component types support alignment and even when they do they don’t support it for all elements. E.g. a Label and its subclasses support alignment but will only apply it to the text and not the icon.

Notice that Container doesn’t support alignment. You should use the layout manager to tune component positioning.

Alignment of the text within some component types
Figure 118. Alignment of the text within some component types
Aligning text components to anything other than the default alignment might be a problem if they are editable. The native editing capabilities might collide with the alignment behavior.
Bidi/RtL layout reverses the alignment value so left becomes right and visa versa

3.3.5. Padding and Margin

Padding and margin are concepts derived from the CSS box model. They are slightly different in Codename One, where the border spacing is part of the padding, but other than that they are pretty similar:

Padding and Margin
Figure 119. Padding and Margin/Box Model

In the diagram, we can see the component represented in yellow occupying its preferred size. The padding portion in gray effectively increases the components size. The margin is the space between components, it allows us to keep whitespace between multiple components. Margin is represented in red in the diagram.

The theme allows us to customize the padding/margin, and specify them for all 4 sides of a component. They can be specified in pixels, millimeters/dips, or screen percentage:

Padding tab
Figure 120. Padding tab
We recommend using millimeters for all spacing to make it look good for all device densities. Percentages make sense only in very extreme cases

3.3.6. Borders

Borders are a big subject in their own right, the UI for their creation is also a bit confusing:

Border entry in the theme
Figure 121. Border entry in the theme

3.3.7. 9-Piece Image Border

A common border type is the 9-piece image border, to facilitate that border type we have a special Image Border Wizard.

A 9 piece image border is a common convention in UI theming that divides a border into 9 pieces 4 representing corners, 4 representing the sides and one representing the middle.

Android uses a common variation on the 9-piece border: 9-patch. The main difference between the 9-piece border and 9-patch is that 9-piece borders tile the sides/center whereas 9-patch scales them

9-piece image borders work better than background images for many use cases where the background needs to "grow/shrink" extensively and might need to change aspect ratio.

They don’t work well in cases where the image is asymmetric on both axis. E.g. a radial gradient image. 9-piece images in general don’t work very well with complex gradients.

The image border wizard simplifies the process of generating a 9-piece image border using a 3 stage process.

Stage 1: create or pick an image from an existing PNG file that we will convert to a 9-piece image
Figure 122. Stage 1: create or pick an image from an existing PNG file that we will convert to a 9-piece image
Use an image that’s designed for a high DPI device

For your convenience you can create a rudimentary image with the create image stage but for a professional looking application you would usually want to use a design by a professional designer.

Stage 2: Cutting the image and adapting it to the DPI’s
Figure 123. Stage 2: Cutting the image and adapting it to the DPI’s

The second stage is probably the hardest and most important one in this wizard!

You can change the values of the top/bottom/left/right spinners to move the position of the guide lines that indicate the various 9 pieces. The image shows the correct cut for this image type with special attention to the following:

  • The left/right position is high enough to fit in the rounded corners in their entirety. Notice that we didn’t just leave 1 pixel as that performs badly, we want to leave as much space as possible!

  • The top and bottom lines have exactly one pixel between them. This is to avoid breaking the gradient. E.g. if we set the lines further apart we will end up with this:

This is why it’s important to keep the lines close when a gradient is involved
Figure 124. This is why it’s important to keep the lines close when a gradient is involved, notice the tiling effect…​
When the lines are close together the gradient effect grows more effectively
Figure 125. When the lines are close together the gradient effect grows more effectively
  • The elements on the right hand side include the Generate Multi Image options. Here you can indicate the density of the source image you are using (e.g. if its for iPhone 5 class device pick Very High). You can then select in the checkboxes below the densities that should be generated automatically for you. This allows fine detail on the border to be maintained in the various high/low resolution devices.

We go into a lot of details about multi images in the advanced theming section.
Stage 3: Styles to which the border is applied
Figure 126. Stage 3: Styles to which the border is applied

The last page indicates the styles to which the wizard will apply the border. Under normal usage you don’t really need to touch this as its properly filled out.

You can define the same border for multiple UIIDs from here though.

Border Minimum Size

A common oddity when using the image borders is the fact that even when padding is removed the component might take a larger size than the height of the text within it.

The reason for that is the border. Because of the way borders are implemented they can’t be drawn to be smaller than the sum of their corners. E.g. the minimum height of a border would be the height of the bottom corner + the height of the top corner. The minimum width would be the width of the left + right corners.

This is coded into the common preferred size methods in Codename One and components generally don’t shrink below the size of the image border even if padding is 0.

Customizing The 9-Piece Border

Normally we can just use the 9-piece border wizard but we can also customize the border by pressing the "…​" button on the border section in the theme.

Press this to customize borders
Figure 127. Press this to customize borders

The UI for the 9-piece border we created above looks like this.

9-piece border in edit mode
Figure 128. 9-piece border in edit mode

You can pick the image represented by every section in the border from the combo boxes. They are organized in the same way the border is with the 9-pieces placed in the same position they would occupy when the border is rendered.

Notice that the other elements in the UI are disabled when the image border type is selected.
3 Image Mode

The 9-piece border has a (rarely used) special case: 3 image mode. In this mode a developer can specify the top left corner, the top image and the center image to produce a 9 piece border. The corner and top piece are then rotated dynamically to produce a standard 9-piece border on the device.

This is useful for reducing application code size but isn’t used often as it requires a more symetric UI.

Don’t confuse the 3-image mode for the 9-piece border with the horizontal/vertical image border below

3.3.8. Horizontal/Vertical Image Border

The 9-piece border is the workhorse of borders in Codename One, however there are some edge cases of UI elements that should grow on one axis and not on another. A perfect example of this is the iOS 6 style back button. If we tried to cut it into a 9-piece border the arrow effect would be broken.

Horizontal image border is commonly used for UI’s that can’t grow vertically e.g. the iOS style back button
Figure 129. Horizontal image border is commonly used for UI’s that can’t grow vertically e.g. the iOS 6 style back button

The horizontal and vertical image borders accept 3 images of their respective AXIS and build the border by placing one image on each side and tiling the center image between them. E.g. A horizontal border will never grow vertically.

In RTL/Bidi [1] modes the borders flip automatically to show the reverse direction. An iOS style back button will point to the right in such languages.

3.3.9. Empty Border

Empty borders enforce the removal of a border. This is important if you would like to block a base style from having a border.

E.g. Buttons have borders by default. If you would like to create a Button that is strictly of solid color you could just define the border to be empty and then use the solid color as you see fit.

There is a null border which is often confused with an empty border. You should use empty border and not null border

3.3.10. Round Border

Circles and completely round border sides are problematic for multi-resolutions. You need to draw them dynamically and can’t use image borders which can’t be tiled/cut to fit round designs (due to physical constraints of the round shape).

We designed the RoundBorder to enable two distinct types of borders:

  • Circular borders - e.g. Android floating action

  • Rectangles with round (not rounded) sides

Round Border is a bit confusing since we already support a rounded border type. The rounded border type is a rectangle with rounded corners whereas the round border has completely round sides or appears as a circle.

To make matters worse the round border has a ridiculous number of features/configurations that would have made the already cluttered UI darn near impossible to navigate. To simplify this we split the UI into 3 tabs for standard borders, image borders and round border.

Round Border
Figure 130. Round Border

3.3.11. Rounded Rectangle Border

The RoundRectBorder was developed based on the RoundBorder and has similar features. It produces a rounded rectangle UI.

Don’t confuse the Rounded Rectangle border with the deprecated Rounded border…​

It’s a pretty simple border type akin to the RoundBorder.

Rounded Rectangle Border
Figure 131. Rounded Rectangle Border

3.3.12. Bevel/Etched Borders

We generally recommend avoiding bevel/etched border types as they aren’t as efficient and look a bit dated in todays applications. We cover them here mostly for completeness.

Bevel border
Figure 132. Bevel border
Etched border
Figure 133. Etched border

3.3.13. Derive

Derive allows us to inherit the behavior of a UIID and extend it with some customization.

E.g. Lets say we created a component that’s supposed to look like a title, we could do something like:

cmp.setUIID("Title");

But title might sometimes be aligned to the left (based on theme) and we always want our component to be center aligned. However, we don’t want that to affect the actual titles in the app…​

To solve this we can define a MyTitle UIID and derive the Title UIID. Then just customize that one attribute.

Derive title
Figure 134. Derive title
Issues With Derive

Style inheritance is a problematic topic in every tool that supports such behavior. Codename One styles start from a global default then have a system default applied and on top of that have the native OS default applied to them.

At that point a developer can define the style after all of the user settings are in place. Normally this works reasonably well, but there are some edge cases where inheriting a style can fail.

When you override an existing style such as Button and choose to derive from Button in a different selection mode or even a different component altogether such as Label you might trigger a recursion effect where a theme setting in the base theme depends on something in a base triggering an infinite loop.

To avoid this always inherit only from UIID’s you defined e.g. MyButton.

3.3.14. Fonts

Codename One currently supports 3 font types:

  • System fonts — these are very simplistic builtin fonts. They work on all platforms and come in one of 3 sizes. However, they are ubiquitous and work in every platform in all languages.

  • TTF files — you can just place a TTF file in the src directory of the project and it will appear in the True Type combo box.

  • Native fonts — these aren’t supported on all platforms but generally they allow you to use a set of platform native good looking fonts. E.g. on Android the devices Roboto font will be used and on iOS San Francisco or Helvetica Neue will be used. This is the recommended font type we suggest for most use cases!

If you use a TTF file MAKE SURE not to delete the file when there MIGHT be a reference to it. This can cause hard to track down issues!
Notice that a TTF file must have the ".ttf" extension, otherwise the build server won’t be able to recognize the file as a font and set it up accordingly (devices need fonts to be defined in very specific ways). Once you do that, you can use the font from code or from the theme
Font Theme Entry
Figure 135. Font Theme Entry
System fonts are always defined even if you use a TTF or native font. If the native font/TTF is unavailable in a specific platform the system font will be used instead.

You can size native/TTF fonts either via pixels, millimeters or based on the size of the equivalent system fonts:

  1. System font size - the truetype font will have the same size as a small, medium or large system font. This allows the developer to size the font based on the device DPI

  2. Millimeter size - allows sizing the font in a more DPI aware size

  3. Pixels - useful for some unique cases, but highly problematic in multi-DPI scenarios

You should notice that font sizing is very inconsistent between platforms we recommend using millimeters for sizing

You can load a truetype font from code using:

if(Font.isTrueTypeFileSupported()) {
    Font myFont = Font.createTrueTypeFont(fontName, fontFileName);
    myFont = myFont.derive(sizeInPixels, Font.STYLE_PLAIN);
    // do something with the font
}

Notice that, in code, only pixel sizes are supported, so it’s up to you to decide how to convert that. We recommend using millimeters with the convertToPixels method. You also need to derive the font with the proper size, unless you want a 0 sized font which isn’t very useful.

The font name is the difficult bit, iOS requires the name of the font in order to load the font. This font name doesn’t always correlate to the file name making this task rather "tricky". The actual font name is sometimes viewable within a font viewer. It isn’t always intuitive, so be sure to test that on the device to make sure you got it right.

due to copyright restrictions we cannot distribute Helvetica and thus can’t simulate it. In the simulator you will see Roboto and not the device font unless you are running on a Mac

The code below demonstrates all the major fonts available in Codename One with the handlee ttf file posing as a standin for arbitrary TTF:

private Label createForFont(Font fnt, String s) {
  Label l = new Label(s);
  l.getUnselectedStyle().setFont(fnt);
  return l;
}

public void showForm() {
  GridLayout gr = new GridLayout(5);
  gr.setAutoFit(true);
  Form hi = new Form("Fonts", gr);

  int fontSize = Display.getInstance().convertToPixels(3);

  // requires Handlee-Regular.ttf in the src folder root!
  Font ttfFont = Font.createTrueTypeFont("Handlee", "Handlee-Regular.ttf").
                      derive(fontSize, Font.STYLE_PLAIN);

  Font smallPlainSystemFont = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
  Font mediumPlainSystemFont = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
  Font largePlainSystemFont = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE);
  Font smallBoldSystemFont = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL);
  Font mediumBoldSystemFont = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM);
  Font largeBoldSystemFont = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_LARGE);
  Font smallItalicSystemFont = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_ITALIC, Font.SIZE_SMALL);
  Font mediumItalicSystemFont = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_ITALIC, Font.SIZE_MEDIUM);
  Font largeItalicSystemFont = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_ITALIC, Font.SIZE_LARGE);

  Font smallPlainMonospaceFont = Font.createSystemFont(Font.FACE_MONOSPACE, Font.STYLE_PLAIN, Font.SIZE_SMALL);
  Font mediumPlainMonospaceFont = Font.createSystemFont(Font.FACE_MONOSPACE, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
  Font largePlainMonospaceFont = Font.createSystemFont(Font.FACE_MONOSPACE, Font.STYLE_PLAIN, Font.SIZE_LARGE);
  Font smallBoldMonospaceFont = Font.createSystemFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_SMALL);
  Font mediumBoldMonospaceFont = Font.createSystemFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_MEDIUM);
  Font largeBoldMonospaceFont = Font.createSystemFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_LARGE);
  Font smallItalicMonospaceFont = Font.createSystemFont(Font.FACE_MONOSPACE, Font.STYLE_ITALIC, Font.SIZE_SMALL);
  Font mediumItalicMonospaceFont = Font.createSystemFont(Font.FACE_MONOSPACE, Font.STYLE_ITALIC, Font.SIZE_MEDIUM);
  Font largeItalicMonospaceFont = Font.createSystemFont(Font.FACE_MONOSPACE, Font.STYLE_ITALIC, Font.SIZE_LARGE);

  Font smallPlainProportionalFont = Font.createSystemFont(Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN, Font.SIZE_SMALL);
  Font mediumPlainProportionalFont = Font.createSystemFont(Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
  Font largePlainProportionalFont = Font.createSystemFont(Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN, Font.SIZE_LARGE);
  Font smallBoldProportionalFont = Font.createSystemFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_SMALL);
  Font mediumBoldProportionalFont = Font.createSystemFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_MEDIUM);
  Font largeBoldProportionalFont = Font.createSystemFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_LARGE);
  Font smallItalicProportionalFont = Font.createSystemFont(Font.FACE_PROPORTIONAL, Font.STYLE_ITALIC, Font.SIZE_SMALL);
  Font mediumItalicProportionalFont = Font.createSystemFont(Font.FACE_PROPORTIONAL, Font.STYLE_ITALIC, Font.SIZE_MEDIUM);
  Font largeItalicProportionalFont = Font.createSystemFont(Font.FACE_PROPORTIONAL, Font.STYLE_ITALIC, Font.SIZE_LARGE);

  String[] nativeFontTypes = {
      "native:MainThin", "native:MainLight",
      "native:MainRegular", "native:MainBold",
      "native:MainBlack", "native:ItalicThin",
      "native:ItalicLight", "native:ItalicRegular",
      "native:ItalicBold", "native:ItalicBlack"};

  for(String s : nativeFontTypes) {
      Font tt  = Font.createTrueTypeFont(s, s).derive(fontSize, Font.STYLE_PLAIN);
      hi.add(createForFont(tt, s));
  }

  hi.add(createForFont(ttfFont, "Handlee TTF Font")).
          add(createForFont(smallPlainSystemFont, "smallPlainSystemFont")).
          add(createForFont(mediumPlainSystemFont, "mediumPlainSystemFont")).
          add(createForFont(largePlainSystemFont, "largePlainSystemFont")).
          add(createForFont(smallBoldSystemFont, "smallBoldSystemFont")).
          add(createForFont(mediumBoldSystemFont, "mediumBoldSystemFont")).
          add(createForFont(largeBoldSystemFont, "largeBoldSystemFont")).
          add(createForFont(smallPlainSystemFont, "smallItalicSystemFont")).
          add(createForFont(mediumItalicSystemFont, "mediumItalicSystemFont")).
          add(createForFont(largeItalicSystemFont, "largeItalicSystemFont")).

          add(createForFont(smallPlainMonospaceFont, "smallPlainMonospaceFont")).
          add(createForFont(mediumPlainMonospaceFont, "mediumPlainMonospaceFont")).
          add(createForFont(largePlainMonospaceFont, "largePlainMonospaceFont")).
          add(createForFont(smallBoldMonospaceFont, "smallBoldMonospaceFont")).
          add(createForFont(mediumBoldMonospaceFont, "mediumBoldMonospaceFont")).
          add(createForFont(largeBoldMonospaceFont, "largeBoldMonospaceFont")).
          add(createForFont(smallItalicMonospaceFont, "smallItalicMonospaceFont")).
          add(createForFont(mediumItalicMonospaceFont, "mediumItalicMonospaceFont")).
          add(createForFont(largeItalicMonospaceFont, "largeItalicMonospaceFont")).

          add(createForFont(smallPlainProportionalFont, "smallPlainProportionalFont")).
          add(createForFont(mediumPlainProportionalFont, "mediumPlainProportionalFont")).
          add(createForFont(largePlainProportionalFont, "largePlainProportionalFont")).
          add(createForFont(smallBoldProportionalFont, "smallBoldProportionalFont")).
          add(createForFont(mediumBoldProportionalFont, "mediumBoldProportionalFont")).
          add(createForFont(largeBoldProportionalFont, "largeBoldProportionalFont")).
          add(createForFont(smallItalicProportionalFont, "smallItalicProportionalFont")).
          add(createForFont(mediumItalicProportionalFont, "mediumItalicProportionalFont")).
          add(createForFont(largeItalicProportionalFont, "largeItalicProportionalFont"));

  hi.show();
}
The fonts running on the ipad simulator on a Mac
Figure 136. The fonts running on the ipad simulator on a Mac, notice that this will look different on a PC
The same demo running on a OnePlus One device with Android 5.1
Figure 137. The same demo running on a OnePlus One device with Android 5.1
Font Effects

You can define an effect to be applied to a specific font, specifically:

  • Underline

  • Strike thru

  • 3d text raised/lowered

  • 3d shadow north

The "3d" effects effectively just draw the text twice, with a sligh offest and two different colors to create a "3d" feel.

All of the effects are relatively simple and performant.

4. Advanced Theming

Before we go into CSS there are a few advanced theme concepts. Notice this still applies to CSS as features such as theme constants are used there as well…​

4.1. Working With UIID’s

UIID’s (User Interface IDentifier) are unique qualifiers given to UI components that associate a set of theme definitions with a specific set of components. E.g. we can associate the Button UIID with a component and then define the look for the Button in the theme.

One of the biggest advantages with UIID’s is the ability to change the UIID of a component. E.g. to create a multiline label, one can use something like:

TextArea t = …;
t.setUIID("Label");
t.setEditable(false);
This is pretty much how components such as SpanLabel are implemented internally

UIID’s can be customized via the GUI builder and allow for powerful customization of individual components.

The class name of the component is commonly the same as the UIID, but they are in essence separate entities

4.2. Theme Layering

There are two use cases in which you would want to use layering:

  • You want a slightly different theme in one platform

  • You want the ability to customize your theme for a specific use case, e.g. let a user select larger fonts

This is actually pretty easy to do and doesn’t require re-doing the entire theme. You can do something very similar to the cascading effect of CSS where a theme is applied "on top" of another theme. To do that just add a new theme using the Add Theme button.

Make sure to remove the includeNativeBool constant in the new theme!

In the new theme define the changes e.g. if you just want a larger default font define only that property for all the relevant UIID’s and ignore all other properties!

For a non-gui builder app the theme loading looks like this by default:

theme = UIManager.initFirstTheme("/theme");

You should fix it to look like this:

theme = UIManager.initNamedTheme("/theme", "Theme");
This assumes the name of your main theme is "Theme" (not the layer theme you just added).

The original code relies on the theme being in the 0 position in the theme name array which might not be the case!

When you want to add the theme layer use:

UIManager.getInstance().addThemeProps(theme.getTheme("NameOfLayerTheme"));

The addThemeProps call will layer the secondary theme on top of the primary "Theme" and keep the original UIID’s defined in the "Theme" intact.

If you apply theme changes to a running application you can use Form’s `refreshTheme() to update the UI instantly and provide visual feedback for the theme changes.

4.3. Override Resources In Platform

When we want to adapt the look of an application to different OS conventions one of the common requirements is to use different icons. Sometimes we want to change behavior based on device type e.g. have a different UI structure for a Tablet.

Codename One allows you to override a resource for a specific platform when doing this you can redefine a resource differently for that specific platform and also add platform specific resources.

Override resources for specific platform
Figure 138. Override resources for specific platform

Overriden resources take precedence over embedded resources thus allowing us to change the look or even behavior (when overriding a GUI builder element) for a specific platform/OS.

Overriding the theme is dangerous as a theme has external dependencies (e.g. image borders). The solution is to use theme layering and override the layer!

To override select the platform where overriding is applicable

Override for platform
Figure 139. Override for platform, allows us to override the checked resources and replace them with another resource

You can then click the green checkbox to define that this resource is specific to this platform. All resources added when the platform is selected will only apply to the selected platform. If you change your mind and are no longer interested in a particular override just delete it in the override mode and it will no longer be overridden.

4.4. Theme Constants

The Codename One Designer has a tab for creating constants which can be used to add global values of various types and behavior hints to Codename One and its components. Constants are always strings. There are some conventions that allow the UI to adapt to input types e.g. if a constant ends with the word Bool it is treated as a boolean (true/false) value. Such a value will display as a checkbox. Similarly an Int suffix will display a numeric picker and an Image suffix will show a combo box to pick an image.

The combo box in the designer for adding a theme constant is editable, you can just type in any value you want!

To use a constant one can use the UIManager's methods to get the appropriate constant type specifically:

  • getThemeConstant

  • isThemeConstant

  • getThemeImageConstant

Internally, Codename One has several built in constants and the list is constantly growing. As we add features to Codename One, we try to keep this list up to date but the very nature of theme constants is "adhoc" and some might not make it here.

Table 3. Theme Constants
Constant Description/Argument

alwaysTensileBool

Enables tensile drag even when there is no scrolling in the container (only for scrollable containers)

backGestureThresholdInt

The threshold for the back gesture in the SwipeBackSupport class, defaults to 5

backUsesTitleBool

Indicates to the GUI builder that the back command should use the title of the previous form and not just the word "Back"

buttonRippleBool

true to activate the material design ripple effect on the buttons. This effect draws a growing circle from the point of touch onwards. This is false by default except for Android where it defaults to true. This is equivalent to setButtonRippleEffectDefault(bool) in the Button class

capsButtonTextBool

true to activate the caps text mode in the buttons. When activated setText on Button and all the constructors will invoke upcase() on all the strings effectively making the application buttons use uppercase exclusively. This is false by default except for Android where it defaults to true. It’s equivalent to Button.setCapsTextDefault(boolean) and can be tuned to an individual Component via Component.setRippleEffect(boolean)

capsButtonUiids

A list of the UIID’s that should be capitalized by default (in supported platforms) other than the Button and RaisedButton which are already capitalized. This list can be separated by spaces or commas e.g. capsButtonUiids=UpcaseButton,OtherCustomButton

defaultCommandImage

Image to give a command with no icon

dialogButtonCommandsBool

Place commands in the dialogs as buttons

dialogBlurRadiusInt

Sets the default Gaussian blur radius for the background of the dialogs. The default value is -1 indicating no blur

dialogPosition

Place the dialog in an arbitrary border layout position (e.g. North, South, Center, etc.)

centeredPopupBool

Popup of the combo box will appear in the center of the screen

changeTabOnFocusBool

Useful for feature phones, allows changing the tab when the focus changes immediately, without pressing a key

checkBoxCheckDisImage

CheckBox image to use instead of Codename One drawing it on its own

checkBoxCheckedImage

CheckBox image to use instead of Codename One drawing it on its own

checkBoxOppositeSideBool

Indicates the check box should be drawn on the opposite side to the text and not next to the text

checkBoxUncheckDisImage

CheckBox image to use instead of Codename One drawing it on its own

checkBoxUncheckedImage

CheckBox image to use instead of Codename One drawing it on its own

comboImage

Combo image to use instead of Codename One drawing it on its own

commandBehavior

Deprecated: Don’t use this constant as it conflicts with the Toolbar. Indicates how commands should act, as a touch menu, native menu etc. Possible values: SoftKey, Touch, Bar, Title, Right, Native

ComponentGroupBool

Enables component group, which allows components to be logically grouped together, so the UIID’s of components would be modified based on their group placement. This allows for some unique styling effects where the first/last elements have different styles from the rest of the elements. It’s disabled by default, thus leaving its usage up to the designer

dialogTransitionIn

Default transition for dialog

dialogTransitionInImage

Default transition Image for dialog, causes a Timeline transition effect

dialogTransitionOut

Default transition for dialog

defaultCommandImage

An image to place on a command if none is defined, only applies to touch commands

defaultEmblemImage

The emblem painted on the side of the multibutton, by default this is an arrow on some platforms

dialogTransitionOutImage

Default transition Image for dialog, causes a Timeline transition effect

disabledColor

Color to use when disabling entries by default

dlgButtonCommandUIID

The UIID used for dialog button commands

dlgCommandButtonSizeInt

Minimum size to give to command buttons in the dialog

dlgCommandGridBool

Places the dialog commands in a grid for uniform sizes

dlgInvisibleButtons

Includes an RRGGBB color for the line separating dialog buttons, as is the case with Android 4 and iOS 7 buttons in dialogs

dlgSlideDirection

Slide hints

dlgSlideInDirBool

Slide hints

dlgSlideOutDirBool

Slide hints

drawMapPointerBool

Indicates whether a pointer should appear in the center of the map component

fadeScrollBarBool

Boolean indicating if the scrollbar should fade when there is inactivity

fadeScrollEdgeBool

Places a fade effect at the edges of the screen to indicate that it’s possible to scroll until we reach the edge (common on Android)

fadeScrollEdgeInt

Amount of pixels to fade out at the edge

firstCharRTLBool

Indicates to the GenericListCellRenderer that it should determine RTL status based on the first character in the sentence

noTextModeBool

Indicates that the on/off switch in iOS shouldn’t draw text on top of the switch, which is the case for iOS 7+ but not for prior versions

fixedSelectionInt

Number corresponding to the fixed selection constants in List

formTransitionIn

Default transition for form

formTransitionInImage

Default transition Image for form, causes a Timeline transition effect

formTransitionOut

Default transition for form

formTransitionOutImage

Default transition Image for form, causes a Timeline transition effect

globalToobarBool

Indicates that the Toolbar API should be on/off by default for all forms

hasRaisedButtonBool

Is true in platforms where the theme has the RaisedButton UIID defined. This is currently only true in the native Android theme to allow some material design guidelines

hideBackCommandBool

Hides the back command from the side menu when possible

hideEmptyTitleBool

Indicates that a title with no content should be hidden even if the border for the title occupies space

hideLeftSideMenuBool

Hides the side menu icon that appears on the left side of the UI

ignorListFocusBool

Hide the focus component of the list when the list doesn’t have focus

infiniteImage

The image used by the infinite progress component, the component will rotate it as needed

includeNativeBool

True to derive from the platform native theme, false to create a blank theme that only uses the basic defaults

labelGap

Positive floating point value representing the default gap value between the label text and the icon in millimeters

listItemGapInt

Built-in item gap in the list, this defaults to 2, which predated padding/margin in Codename One

listLongPressBool

Indicates whether a list should handle long press events, defaults to true

mapTileLoadingImage

An image to preview while loading the MapComponent tile

mapTileLoadingText

The text of the tiles in the MapComponent during loading, defaults to "Loading…​"

mapZoomButtonsBool

Indicates whether buttons should be drawn on the map component

mediaBackImage

Media icon used by the media player class

mediaFwdImage

Media icon used by the media player class

mediaPauseImage

Media icon used by the media player class

mediaPlayImage

Media icon used by the media player class

menuButtonBottomBool

When set to true this flag aligns the menu button to the bottom portion of the title. Defaults to false

menuButtonTopBool

When set to true this flag aligns the menu button to the top portion of the title. Defaults to false

menuHeightPercent

Allows positioning and sizing the menu

menuImage

The three dot menu image used in Android and the Toolbar to show additional command entries

menuImageSize

The size in millimeters (floating point value) of the generated side menu image, this is only used if you don’t supply a custom image. The default value is 4.5.

menuPrefSizeBool

Allows positioning and sizing the menu

menuSlideDirection

Defines menu entrance effect

menuSlideInDirBool

Defines menu entrance effect

menuSlideOutDirBool

Defines menu entrance effect

menuTransitionIn

Defines menu entrance effect

menuTransitionInImage

Defines menu entrance effect

menuTransitionOut

Defines menu exit effect

menuTransitionOutImage

Defines menu entrance effect

menuWidthPercent

Allows positioning and sizing the menu

minimizeOnBackBool

Indicates whether the form should minimize the entire application when the physical back button is pressed (if available) and no command is defined as the back command. Defaults to true

onOffIOSModeBool

Indicates whether the on/off switch should use the iOS or Android mode

otherPopupRendererBool

Indicates that a separate renderer UIID/instance should be used to the list within the combo box popup

PackTouchMenuBool

Enables preferred sized packing of the touch menu (true by default), when set to false this allows manually determining the touch menu size using percentages

paintsTitleBarBool

Indicates that the StatusBar UIID should be added to the top of the form to space down the title area, as is the case on iOS 7+ where the status bar is painted on top of the UI

popupCancelBodyBool

Indicates that a cancel button should appear within the combo box popup

PopupDialogArrowBool

Indicates whether the popup dialog has an arrow, notice that this constant will change if you change UIID of the popup dialog

PopupDialogArrowBottomImage

Image of the popup dialog arrow, notice that this constant will change if you change UIID of the popup dialog

PopupDialogArrowTopImage

Image of the popup dialog arrow, notice that this constant will change if you change UIID of the popup dialog

PopupDialogArrowLeftImage

Image of the popup dialog arrow, notice that this constant will change if you change UIID of the popup dialog

PopupDialogArrowRightImage

Image of the popup dialog arrow, notice that this constant will change if you change UIID of the popup dialog

popupNoTitleAddPaddingInt

Adds padding to a popup when no title is present

popupTitleBool

Indicates that a title should appear within the combo box popup

pullToRefreshImage

The arrow image used to draw the pullToRefresh animation

pureTouchBool

Indicates the pure touch mode

radioOppositeSideBool

Indicates the radio button should be drawn on the opposite side to the text and not next to the text

radioSelectedDisImage

Radio button image

radioSelectedImage

Radio button image

radioUnselectedDisImage

Radio button image

radioUnselectedImage

Radio button image

radioSelectedDisFocusImage

Radio button image

radioSelectedFocusImage

Radio button image

radioUnselectedDisFocusImage

Radio button image

radioUnselectedFocusImage

Radio button image

releaseRadiusInt

Indicates the distance from the button with dragging, in which the button should be released, defaults to 0

rendererShowsNumbersBool

Indicates whether renderers should render the entry number

reverseSoftButtonsBool

Swaps the softbutton positions

rightSideMenuImage

Same as sideMenuImage only for the right side, optional and defaults to sideMenuImage

rightSideMenuPressImage

Same as sideMenuPressImage only for the right side, optional and defaults to sideMenuPressImage

scrollVisibleBool

true/false default is platform dependent. Toggles whether the scroll bar is visible

showBackCommandOnTitleBool

Used by the Toolbar API to indicate whether the back button should appear on the title

shrinkPopupTitleBool

Indicates the title of the popup should be set to 0 if it’s missing

sideMenuAnimSpeedInt

The speed at which a sidemenu moves defaults to 300 milliseconds

sideMenuFoldedSwipeBool

Indicates the side menu could be opened via swiping

sideMenuImage

The image representing the side menu, three lines (Hamburger menu)

sideMenuPressImage

Optional pressed version of the sideMenuImage

sideMenuScrollVisibleBool

Indicates whether the scroll bar on the side menu should be visible or not, defaults to hidden

sideMenuShadowBool

Indicates whether the shadow for the side menu should be drawn

sideMenuShadowImage

The image used when drawing the shadow (a default is used if this isn’t supplied)

sideMenuSizeTabPortraitInt

The size of the side menu when expanded in a tablet in portrait mode

sideMenuSizePortraitInt

The size of the side menu when expanded in a phone in portrait mode

sideMenuSizeTabLandscapeInt

The size of the side menu when expanded in a tablet in landscape mode

sideMenuSizeLandscapeInt

The size of the side menu when expanded in a phone in landscape mode

sideMenuTensileDragBool

Enables/disables the tensile drag behavior within the opened side menu

sideSwipeActivationInt

Indicates the threshold in the side menu bar at which a swipe should trigger activation, defaults to 15 (percent)

sideSwipeSensitiveInt

Indicates the region of the screen that is sensitive to side swipe in the side menu bar, defaults to 10 (percent)

slideDirection

Default slide transition settings

slideInDirBool

Default slide transition settings

slideOutDirBool

Default slide transition settings

sliderThumbImage

The thumb image that can appear on the sliders

snapGridBool

Snap to grid toggle

statusBarScrollsUpBool

Indicates that a tap on the status bar should scroll up the UI, only relevant in OS’s where paintsTitleBarBool is true

switchButtonPadInt

Indicates the padding in the on/off switch, defaults to 16

switchMaskImage

Indicates the mask image used in iOS mode to draw on top of the switch

switchOnImage

Indicates the on image used in iOS mode to draw the on/off switch

switchOffImage

Indicates the off image used in iOS mode to draw the on/off switch

TabEnableAutoImageBool

Indicates images should be filled by default for tabs

TabSelectedImage

Default selected image for tabs (if TabEnableAutoImageBool=true)

TabUnselectedImage

Default unselected image for tabs (if TabEnableAutoImageBool=true)

tabPlacementInt

The placement of the tabs in the Tabs component: TOP = 0, LEFT = 1, BOTTOM = 2, RIGHT = 3

tabsSlideSpeedInt

The time of the animation that occurs (in milliseconds) between between releasing a swiped tab and reaching the next tab. Currently defaults to 200

tabsFillRowsBool

Indicates if the tabs should fill the row using flow layout

tabsGridBool

Indicates whether tabs should use a grid layout thus forcing all tabs to have identical sizes

tabsOnTopBool

Indicates the tabs should be drawn on top of their content in a layered UI, this allows a tab to intrude into the content of the tabs

textCmpVAlignInt

The vertical alignment of the text component: TOP = 0, CENTER = 4, BOTTOM = 2

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

textFieldCursorColorInt

The color of the cursor as an integer (not hex)

tickerSpeedInt

The speed of label/button etc. (in milliseconds)

tintColor

The aarrggbb hex color to tint the screen when a dialog is shown

topMenuSizeTabPortraitInt

The size of the side menu when expanded and attached to the top in a tablet in portrait mode

topMenuSizePortraitInt

The size of the side menu when expanded and attached to the top in a phone in portrait mode

topMenuSizeTabLandscapeInt

The size of the side menu when expanded and attached to the top in a tablet in landscape mode

topMenuSizeLandscapeInt

The size of the side menu when expanded and attached to the top in a phone in landscape mode

touchCommandFillBool

Indicates how the touch menu should layout the commands within

touchCommandFlowBool

Indicates how the touch menu should layout the commands within

transitionSpeedInt

Indicates the default speed for transitions

treeFolderImage

Picture of a folder for the Tree class

treeFolderOpenImage

Picture of a folder expanded for the Tree class

treeNodeImage

Picture of a file node for the Tree class

tensileDragBool

Indicates that tensile drag should be enabled/disabled. This is usually set by platform themes

Dynamic Theme Swapping & Theme Constants

Once a theme constant is set by a theme, it isn’t removed on a refresh when replacing the theme.

E.g. if one would set the comboImage constant to a specific value in theme A and then switch to theme B, that doesn’t define the comboImage, the original theme A comboImage might remain!

The reason for this is simple: when extracting the constant values, components keep the values in cache locally and just don’t track the change in value. Furthermore, since the components allow manually setting values, it’s impractical for them to track whether a value was set by a constant or explicitly by the user.

The solution for this is to either manually reset undesired values before replacing a theme (e.g. for the case, above by calling the default look and feel method for setting the combo image with a null value), or defining a constant value to replace the existing value.

4.5. Native Theming

Codename One uses a theme constant called includeNativeBool, when that constant is set to true Codename One starts by loading the native theme first and then applying all the theme settings. This effectively means your theme "derives" the style of the native theme first, similar to the cascading effect of CSS. Internally this is exactly what the theme layering section covered.

By avoiding this flag you can create themes that look EXACTLY the same on all platforms.

If you avoid the native theming you might be on your own. A few small device oddities such as the iOS status bar are abstracted by native theming. Without it you will need to do everything from scratch

You can simulate different OS platforms by using the native theme menu option

The native theme menu option
Figure 140. The native theme menu option

Developers can pick the platform of their liking and see how the theme will appear in that particular platform by selecting it and having the preview update on the fly.

4.6. Under the Hood of the Theme Engine

To truly understand a theme we need to understand what it is. Internally a theme is just a Hashtable key/value pair between UIID based keys and their respective values. E.g. the key:

Button.fgColor=ffffff

Will set the foreground color of the Button UIID to white.

When a Codename One Component is instantiated it requests a Style object from the UIManager class. The Style object is based on the settings within the theme and can be modified thru code or by using the theme.

We can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the refreshTheme() method.

It’s a common mistake to invoke refreshTheme() without actually changing the theme. We see developers doing it when all they need is a repaint() or revalidate(). Since refreshTheme() is very expensive we recommend that you don’t use it unless you really need to…​

A theme Hashtable key is comprised of:

[UIID.][type#]attribute

The UIID, corresponds to the component’s UIID e.g. Button, CheckBox etc. It is optional and may be omitted to address the global default style.

The type is omitted for the default unselected type, and may be one of sel (selected type), dis (disabled type) or press (pressed type). The attribute should be one of:

  • derive - the value for this attribute should be a string representing the base component.

  • bgColor - represents the background color for the component, if applicable, in a web hex string format RRGGBB e.g. ff0000 for red.

  • fgColor - represents the foreground color, if applicable.

  • border - an instance of the border class, used to display the border for the component.

  • bgImage - an Image object used in the background of a component.

  • transparency - a String containing a number between 0-255 representing the alpha value for the background. This only applies to the bgColor.

  • margin - the margin of the component as a String containing 4 comma separated numbers for top,bottom,left,right.

  • padding - the padding of the component, it has an identical format to the margin attribute.

  • font - A Font object instance.

  • alignment - an Integer object containing the LEFT/RIGHT/CENTER constant values defined in Component.

  • textDecoration - an Integer value containing one of the TEXT_DECORATION_* constant values defined in Style.

  • backgroundType - a Byte object containing one of the constants for the background type defined in Style under BACKGROUND_*.

  • backgroundGradient - contains an Object array containing 2 integers for the colors of the gradient. If the gradient is radial it contains 3 floating points defining the x, y & size of the gradient.

So to set the foreground color of a selected button to red, a theme will define a property like:

Button.sel#fgColor=ff0000

This information is mostly useful for understanding how things work within Codename One, but it can also be useful in runtime.

E.g. to increase the size of all fonts in the application, we can do something like:

Hashtable h = new Hashtable();
h.put("font", largeFont);
UIManager.getInstance().addThemeProps(h);
Display.getInstance().getCurrent().refreshTheme();

4.7. Understanding Images and Multi-Images

This section provides a very high level overview of images. We dive deeper into the various types of images in the graphics section.

When working with a theme, we often use images for borders or backgrounds. We also use images within the GUI for various purposes and most such images will be extracted from the resource file.

Adding a standard JPEG/PNG image to the resource file is straight forward, and the resulting image can be viewed within the images section. However, due to the wide difference between device types, an image that would be appropriate in size for an iPhone 3gs would not be appropriate in size for a Nexus device or an iPhone 4 (but perhaps, surprisingly, it will be just right for iPad 1 and iPad 2).

The density of the devices varies significantly and Codename One tries to simplify the process by unifying everything into one set of values to indicate density. For simplicity’s sake, density is sometimes expressed in terms of pixels, however it is mapped internally to actual screen measurements where possible.

A multi-image is an image that has multiple varieties for different densities, and thus looks sharp in all the densities. Since scaling on the device can’t interpolate the data (due to performance considerations), significant scaling on the device becomes impractical. However, a multi-image will just provide the “right” resolution image for the given device type.

From the programming perspective this is mostly seamless, a developer just accesses one image and has no ability to access the images in the different resolutions. Within the designer, however, we can explicitly define images for multiple resolutions and perform high quality scaling so the “right” image is available.

We can use two basic methods to add a multi-image: quick add and standard add.

Both methods rely on understanding the source resolution of the image, e.g. if you have an icon that you expect to be 128x128 pixels on iPhone 4, 102x102 on nexus one and 64x64 on iPhone 3gs. You can provide the source image as the 128 pixel image and just perform a quick add option while picking the Very High density option.

This will indicate to the algorithm that your source image is designed for the "very high" density and it will scale for the rest of the densities accordingly.

This relies on the common use case of asking your designer to design for one high end device (e.g. iPhone X) then you can take the resources and add them as "HD" resources. They will automatically adapt to the lower resolutions

Alternatively, you can use the standard add multi-image dialog and set it like this:

Multi-image resolution dialog

Notice that we selected the square image option, essentially eliminating the height option. Setting values to 0 prevents the system from generating a multi-image entry for that resolution, which will mean a device in that category will fall on the closest alternative.

The percentage value will change the entire column, and it means the percentage of the screen. E.g. We know the icon is 128 for the very high resolution, we can just move the percentage until we reach something close to 128 in the “Very High” row and the other rows will represent a size that should be pretty close in terms of physical size to the 128 figure.

At runtime, you can always find the host device’s approximate pixel density using the Display.getDeviceDensity() method. This will return one of:

Table 4. Densities

Constant

Density

Example Device

Display.DENSITY_VERY_LOW

~ 88 ppi

Display.DENSITY_LOW

~ 120 ppi

Android ldpi devices

Display.DENSITY_MEDIUM

~ 160 ppi

iPhone 3GS, iPad, Android mdpi devices

Display.DENSITY_HIGH

~ 240 ppi

Android hdpi devices

Display.DENSITY_VERY_HIGH

~ 320 ppi

iPhone 4, iPad Air 2, Android xhdpi devices

Display.DENSITY_HD

~ 540 ppi

iPhone 6+, Android xxhdpi devices

Display.DENSITY_560

~ 750 ppi

Android xxxhdpi devices

Density.DENSITY_2HD

~ 1000 ppi

Density.DENSITY_4K

~ 1250ppi

4.8. Use Millimeters for Padding/Margin and Font Sizes

When configuring your styles, you should almost never use "Pixels" as the unit for padding, margins, font size, and border thickness because the results will be inconsistent on different densities. Instead, you should use millimeters for all non-zero units of measurement.

As we now understand the complexities of DPI it should be clear why this is important.

4.8.1. Fractions of Millimeters

Sometimes millimeters don’t give you enough precision for what you want to do. Currently the designer only allows you to specify integer values for most units. However, you can achieve more precise results when working directly in Java. The Display.convertToPixels() method will allow you to convert millimeters (or DIPS) to pixels. It also only takes an integer input, but you can use it to obtain a multiplier that you can then use to convert any millimeter value you want into pixels.

E.g.

double pixelsPerMM = ((double)Display.getInstance().convertToPixels(10, true)) / 10.0;

And now you can set the padding on an element to 1.5mm. E.g.

myButton.getAllStyles().setPaddingUnit(Style.UNIT_TYPE_PIXELS);
int pixels = (int)(1.5 * pixelsPerMM);
myButton.getAllStyles().setPadding(pixels, pixels, pixels, pixels);

4.9. Creating a Great Looking Side Menu

Side Menu final result
Figure 141. Side Menu final result

A side menu is a crucial piece of an elegant application. We’ll explain how one creates a simple side menu that’s elegant, portable and easy to build. This is a good "starting point" side menu from which you can build more elaborate designs.

To get this result we will start from a native theme and a bare bones application to keep things simple.

The code for the side menu is this:

Form hi = new Form("Hi World");

Toolbar tb = hi.getToolbar();
Image icon = theme.getImage("icon.png"); (1)
Container topBar = BorderLayout.east(new Label(icon));
topBar.add(BorderLayout.SOUTH, new Label("Cool App Tagline...", "SidemenuTagline")); (2)
topBar.setUIID("SideCommand");
tb.addComponentToSideMenu(topBar);

tb.addMaterialCommandToSideMenu("Home", FontImage.MATERIAL_HOME, e -> {}); (3)
tb.addMaterialCommandToSideMenu("Website", FontImage.MATERIAL_WEB, e -> {});
tb.addMaterialCommandToSideMenu("Settings", FontImage.MATERIAL_SETTINGS, e -> {});
tb.addMaterialCommandToSideMenu("About", FontImage.MATERIAL_INFO, e -> {});

hi.addComponent(new Label("Hi World"));
hi.show();
1 This is the icon which was used in lieu of a logo it appears in the top right of the side menu
2 This is the top bar containing the tagline and the icon it’s styled as if it’s a command but you can put anything here e.g. an image etc.
3 The commands are added as usual to the side menu with no styling or functionality, the entire look is determined by the theme

Next we’ll open the designer tool to style the UI

Open the side menu so we will get the right values in the combo box on add
Figure 142. Open the side menu so we will get the right values in the combo box on add

Now when we press Add the side menu entries will appear in the combo box (you can type them but this is more convenient). We’ll start with the SideNavigationPanel style:

The SideNavigationPanel has an opaque white background
Figure 143. The SideNavigationPanel has an opaque white background

The SideCommand style is a bit more elaborate, we start with a white foreground and an opaque bluish/purple color:

The SideCommand has a white foreground and opaque bluish background
Figure 144. The SideCommand has a white foreground and opaque bluish background

We’ll set padding to 3 millimeters which gives everything a good feel and spacing. This is important for finger touch sensitivity.

Padding is 3mm so it will feel spacious and touch friendly
Figure 145. Padding is 3mm so it will feel spacious and touch friendly

We’ll set margin to 0 except for the bottom one pixel which will leave a nice white line by showing off the background. This means the commands will have a space between them and the white style we gave to the SideNavigationPanel will appear thru that space.

Margin is 0 except for a thin line below each command
Figure 146. Margin is 0 except for a thin line below each command

Setting the border to empty is crucial!

The iOS version of the side command inherits a border style so we must "remove" it by defining a different border in this case an empty border. Since borders take precedence over color this would have prevented the color changes we made from appearing.

Border must be defined as Empty
Figure 147. Border must be defined as Empty

Next we need to pick a good looking font and make sure it’s large enough. We use millimeters size it correctly for all OS’s and override the derived text decoration which has a value in the iOS native theme so it can impact the final look.

Pick a good looking font for the side command
Figure 148. Pick a good looking font for the side command

Next we need to move to the selected tab and add a new side command entry that derives from the unselected version. We’ll pick a new color that’s slightly deeper and will make the selected style appear selected. We’ll also copy and paste this selected style to the pressed style.

Selected & Pressed SideCommand
Figure 149. Selected & Pressed SideCommand
Color for the Selected/Pressed SideCommand
Figure 150. Color for the Selected/Pressed SideCommand

The SidemenuTagline is just a SideCommand style that was slightly adapted. We’ll remove the padding and margin because the whole section is wrapped in a side command and we don’t want double padding. We’ll leave 1mm padding at the top for a bit of spacing from the logo.

Padding of the SidemenuTagline
Figure 151. Padding of the SidemenuTagline

We’ll also update the font to a smaller size and italic styling so it will feel like a tagline.

Font for the SideMenuTagline is slightly smaller and italic
Figure 152. Font for the SideMenuTagline is slightly smaller and italic

The last change for the theme is for the StatusBarSideMenu UIID which is a spacing on the top of the sidemenu. This spacing is there for iOS devices which render the clock/battery/reception symbols on top of the app. We’ll set the padding to 0.

StatusBarSideMenu padding for the top of the side menu
Figure 153. StatusBarSideMenu padding for the top of the side menu

Finally, we’ll add the icon image (or a logo if you have it) into the theme as a multi image so we can use it within the side menu as a good looking logo. A relatively large icon image works as a 2HD multi-image but you can use many strategies to get a fitting image for this spot.

Rounded images work well here, you can round images dynamically using masking

These steps produce the UI above as a side menu, they might seem like a long set of steps but each step is pretty simple as you walk thru each one. This does show off the versatility and power of Codename One as a change to one step can create a radically different UI design.

4.10. Converting a PSD To A Theme

Codename One provides extensive support for designing beautiful user interfaces, but it isn’t necessarily obvious to new developers how to achieve their desired results. A common workflow for app design includes a PSD file with mock-ups of the UI, created by a professional designer.

PSD is the Adobe Photoshop file format, it’s the most common format for UI designs in the industry

For this tutorial we adapt a very slick looking sign-up form found online and convert it to a Codename One component that can be used inside an application.

The process we followed was:

  1. Find the PSD design we want to use: this PSD file created by Adrian Chiran (we mirrored it here in case it goes offline):

    Sign Up form Design
    Figure 154. Sign Up form Design
  2. Re-create the general structure and layout of the design in a Codename One Form using nested components and layout managers. Here is a break-down of how we structured the component hierarchy in the Form:

    Component hierarchy and layouts
    Figure 155. Component hierarchy and layouts
  3. Extract the images we needed using Photoshop - this process is often referred to as "cutting"

  4. Extract the fonts, colors, and styles we needed to reproduce the design in Codename One

  5. Import images into the Codename one project, and define theme styles so that our components match the look of the original design

Here is a screenshot of the resulting component running inside the Codename One simulator:

Resulting app in the Codename One simulator
Figure 156. Resulting app in the Codename One simulator

4.10.1. Breaking Down the PSD

Open the PSD you are interested in using Photoshop.

You might be missing fonts in your system so you can either install them or ignore that. Keep in mind that some fonts might not be redistributable with your application

In this PSD we want only one of the screen designs so initially we want to remove everything that isn’t related so we can get our bearings more effectively:

  • Select the drag tool (the top left tool)

  • In the toolbar for the tool (top bar area) check the Auto Select mode

  • Select the Layer Mode for auto selection (in some cases group would actually be better so feel free to experiment)

  • Click on the portion in the PSD that you are interested in

You should end up with something like this where a layer is selected in the layers window:

Selecting a layer from the region you are interested in
Figure 157. Selecting a layer from the region you are interested in

Scroll up the hierarchy a bit and uncheck/recheck the eye icon on the left until you locate the right element layer.

Find the right element layer you are interested in
Figure 158. Selecting a layer from the region you are interested in

Right click the layer and select Convert To Smart Object.

The right click menu will present different options when you click different areas of the layer, clicking on the left area of the layer works
In the right click menu option select "Convert To Smart Object"
Figure 159. In the right click menu option select "Convert To Smart Object"

Once the layer hierarchy is a smart object you can just double click it which will open the sub hierarchy in a new tab and you now only have the pieces of the image you care about.

Double clicking the smart object allows us to edit only the form we need
Figure 160. Double clicking the smart object allows us to edit only the form we need
Removing the Noise

The first thing we need to do is remove from the image all of the things that we don’t really need. The status bar area on the top is redundant as if is a part of the phones UI. We can select it using the select tool and click the eye icon next to the layer to hide it.

Normally we’d want to have the back arrow but thanks to the material design icons that are a part of Codename One we don’t need that icon so we can hide that too.

We don’t need the "Sign Up" or "Done" strings in the title either but before removing them we’d like to know the font that is used.

To discover that I can click them to select the layer then switch to the text tool:

The text tool allows us to inspect the font used
Figure 161. The text tool allows us to inspect the font used

Then I can double click the text area layer to find out the font in the top of the UI like this:

The Done toolbar entry uses SourceSansPro Regular
Figure 162. The Done toolbar entry uses SourceSansPro Regular
Notice that I don’t actually need to have the font installed in this case I don’t (hence the square brackets)

Also notice that the color of the font is accessible in that toolbar, by clicking the color element we get this dialog which shows the color value to be f73267, this is something we will use later

The color dialog lists the hex color at the bottom
Figure 163. The color dialog lists the hex color at the bottom, we can paste that directly to the designer tool

We can now hide both text layers so they won’t pose a problem later.

The Camera Button

The camera button includes an icon and the button background itself. You can just use that as a single image and be done with it, but for the purpose of this tutorial I will take the harder route of separating this into a button background and a foreground image.

When you click on the camera icon you will notice that the camera icon is comprised of two separate layers: the camera and the "x" symbol above it. We can select both layers using ctrl-click (command click on the Mac) and convert both to a smart object together using the same method as before:

The camera smart object
Figure 164. The camera smart object

Since the image is used as an icon we want it to be completely square which isn’t the situation here!
This is important as a non-square image can trigger misalignment when dealing with icons and the background. So we need to use the ImageCanvas Size menu and set the values to be the same (the higher value of the two).

The canvas size dialog for the camera.png file
Figure 165. The canvas size dialog for the camera.png file

We can now use FileExport and save the first image resource we will need into a temporary directory. Make sure to save a PNG file to preserve quality and transparency!

Use FileExport and never use FileSave As. The latter can produce a huge size difference as it retains image meta-data

For convenience we’ll refer to the file as camera.png when we need it later.

The camera icon image
Figure 166. The camera icon image

We can follow the exact same procedure with the parent button layer (the white portion) which we can convert to a smart object and export camera-button.png.

The camera button image set to a gray background so it will be visible
Figure 167. The camera button image set to a gray background so it will be visible

Now we can hide both of these elements and proceed to get the background image for the title.

Here the "smart object trick" won’t work…​ There is an effects layer in place and the smart object will provide us with the real underlying image instead of the look we actually want. However, solving this is trivial now that we hid all of the elements on top of the image!

We need to switch to the rectangular select tool:

The select tool and the clean image we want to select
Figure 168. The select tool and the clean image we want to select

Now drag the select tool to select the image don’t cross into the white pixels below the image. You can use the zoom value and set it to a very high value to get the selection right.

When the selection is right click EditCopy Merged. Normally Copy would only copy a specific layer but in this case we want to copy what we see on the screen!

Now click FileNew it should have the Presets set to Clipboard which means the newly created image is based on what we just copied (that is seriously great UX). Just accept that dialog and paste (Ctrl-V or Command-V).

You can now save the image, since it’s just a background using JPEG is totally acceptable in this case. We named it background.jpg.

The background image
Figure 169. The background image

The last thing we need is the colors used in the UI. We can use the "eye drop" tool in a high zoom level to discover the colors of various elements e.g. the text color is 4d606f and the separator color is f5f5f5:

The eye drop tool can be pointed at an area of the image to get the color in that region
Figure 170. The eye drop tool can be pointed at an area of the image to get the color in that region

4.10.2. The Code

While that was verbose it was relatively simple. We’ll create a simple barebones manual application with the native theme.

The reason for this is to avoid "noise", if we use a more elaborate theme it would have some existing settings. This can make the tutorial harder to follow
Simple bare bones app settings
Figure 171. Simple bare bones app settings

Once the project is created double click the theme.res file and within the designer select ImagesQuick Add Multi Images. Select the 3 images we created above: background.jpg, camera.png & camera-button.png. Leave the default setting on Very High and press OK.

Then save the resource file so we can use these images from code.

Here is the source code we used to work with the UI above there are comments within the code explaining some of the logic:

private Label createSeparator() {
    Label sep = new Label();
    sep.setUIID("Separator");
    // the separator line  is implemented in the theme using padding and background color, by default labels
    // are hidden when they have no content, this method disables that behavior
    sep.setShowEvenIfBlank(true);
    return sep;
}

public void start() {
    if(current != null){
        current.show();
        return;
    }
    // The toolbar uses the layered mode so it resides on top of the background image, the theme makes
    // it transparent so we will see the image below it, we use border layout to place the background image on
    // top and the "Get started" button in the south
    Form psdTutorial = new Form("Signup", new BorderLayout());
    Toolbar tb = new Toolbar(true);
    psdTutorial.setToolbar(tb);

    // we create 4mm material arrow images for the back button and the Get started button
    Style iconStyle = psdTutorial.getUIManager().getComponentStyle("Title");
    FontImage leftArrow = FontImage.createMaterial(FontImage.MATERIAL_ARROW_BACK, iconStyle, 4);
    FontImage rightArrow = FontImage.createMaterial(FontImage.MATERIAL_ARROW_FORWARD, iconStyle, 4);

    // we place the back and done commands in the toolbar, we need to change UIID of the "Done" command
    // so we can color it in Red
    tb.addCommandToLeftBar("", leftArrow, (e) -> Log.p("Back pressed"));
    Command doneCommand = tb.addCommandToRightBar("Done", null, (e) -> Log.p("Done pressed"));
    tb.findCommandComponent(doneCommand).setUIID("RedCommand");

    // The camera button is comprised of 3 pieces. A label containing the image and the transparent button
    // with the camera icon on top. This is all wrapped in the title container where the title background image
    // is placed using the theme. We chose to use a Label rather than a background using the cameraLayer so
    // the label will preserve the original size of the image without scaling it and take up the space it needs
    Button cameraButton = new Button(theme.getImage("camera.png"));
    Container cameraLayer = LayeredLayout.encloseIn(
            new Label(theme.getImage("camera-button.png")),
            cameraButton);
    cameraButton.setUIID("CameraButton");
    Container titleContainer = Container.encloseIn(
            new BorderLayout(BorderLayout.CENTER_BEHAVIOR_CENTER),
            cameraLayer, BorderLayout.CENTER);
    titleContainer.setUIID("TitleContainer");

    TextField firstName = new TextField("", "First Name");
    TextField lastName = new TextField("", "Last Name");
    TextField email = new TextField("", "Email Address", 20, TextField.EMAILADDR);
    TextField password = new TextField("", "Choose a Password", 20, TextField.PASSWORD);
    TextField phone = new TextField("", "Phone Number", 20, TextField.PHONENUMBER);
    Label phonePrefix = new Label("+1");
    phonePrefix.setUIID("TextField");

    // The phone and full name have vertical separators, we use two table layouts to arrange them correctly
    // so the vertical separator will be in the right place
    TableLayout fullNameLayout = new TableLayout(1, 3);
    Container fullName = new Container(fullNameLayout);
    fullName.add(fullNameLayout.createConstraint().widthPercentage(49), firstName).
        add(fullNameLayout.createConstraint().widthPercentage(1), createSeparator()).
        add(fullNameLayout.createConstraint().widthPercentage(50), lastName);
    Container fullPhone = TableLayout.encloseIn(3, phonePrefix, createSeparator(), phone);

    // The button in the south portion needs the arrow icon to be on the right side so we place the text on the left
    Button southButton = new Button("Get started", rightArrow);
    southButton.setTextPosition(Component.LEFT);
    southButton.setUIID("SouthButton");

    // we add the components and the separators the center portion contains all of the elements in a box
    // Y container which we allow to scroll. BorderLayout Containers implicitly disable scrolling
    Container by = BoxLayout.encloseY(
                    fullName,
                    createSeparator(),
                    email,
                    createSeparator(),
                    password,
                    createSeparator(),
                    fullPhone,
                    createSeparator()
            );
    by.setScrollableY(true);
    psdTutorial.add(BorderLayout.NORTH, titleContainer).
            add(BorderLayout.SOUTH, southButton).
            add(BorderLayout.CENTER, by);


    psdTutorial.show();
}

4.10.3. Styling The UI

So the code above is most of the work but we still need to put everything together using the theme. This is what we have so far:

Before applying the changes to the theme this is what we have
Figure 172. Before applying the changes to the theme this is what we have
This is what we are aiming at with no additional code changes
Figure 173. This is what we are aiming at with no additional code changes

This looks like a major set of changes but it requires exactly 10 UIID definitions to get to this look!

Open the designer and select the theme. Press the Add button and type in TitleContainer. Uncheck derive for the background and select IMAGE_SCALED_FILL for the Type and the background.jpg image.

Define the padding as:

  • Left - 3 millimeter

  • Right - 3 millimeter

  • Top - 8 millimeter

  • Bottom - 2 millimeter

This will allow enough space for the title. Define margin as 0 on all sides. Then press OK.

Add the "Title" UIID. In the Color tab define the foreground as ffffff define transparency as 0 (fully transparent so we will see the TitleContainer). Define padding as 1 millimeter on all sides and margin as 0 on all sides.

In the Border tab press the …​ button and select [Empty].

In the Font tab select the True Type as native:MainThin. Select the True Type Size as millimeters and set the value to 3.5.

Press OK to save the changes.

Copy the Title UIID and paste it, change the name to "TitleCommand" and press OK to save the changes.

Copy the Title UIID again and paste it, change the name to "RedCommand". In the Color tab set the foreground color to f73267. In the Font tab set the True Type to native:MainLight and set the size to 3. Press OK to save the changes.

Add the "TitleArea" UIID. In the Color tab define transparency as 0 (fully transparent so we will see the TitleContainer). Define padding and margin as 0 on all sides.
In the Border tab press the …​ button and select [Empty]. Press OK to save the changes.

Add the "TextField" UIID. In the Color tab define transparency as 255 (fully opaque) and the background as ffffff (white). Define padding as 2 millimeter on all sides and margin as 0 on all sides.
In the Border tab press the …​ button and select [Empty]. In the Font tab set the True Type to native:MainLight and set the size to 2. Press OK to save the changes.

Copy the TextField UIID again and paste it, change the name to "TextHint". In the Color tab set the foreground color to 4d606f. Press OK to save the changes.

Add the "SouthButton" UIID. In the Color tab define transparency as 255 (fully opaque) and the background as f73267 (red) and the foreground as ffffff (white). Define Alignment as Center.

Define padding as:

  • Left - 1 millimeter

  • right - 1 millimeter

  • top - 2 millimeters

  • bottom - 2 millimeters

Define margin as 0 on all sides. In the Font tab set the True Type to native:MainThin and set the size to 3. Press OK to save the changes.

Add the "CameraButton" UIID. In the Color tab define transparency as 0 (fully transparent). Define Alignment as Center.
Define padding as:

  • Left - 1 millimeter

  • right - 1 millimeter

  • top - 3 millimeters

  • bottom - 1 millimeter

This helps spacing away from the title

Define margin as 1 millimeter on all sides. Press OK to save the changes.

You can now save the theme and the app should look like the final result!

Not Quite There Yet

There is one last piece that you would notice if you actually try to run this code. When pressing the buttons/text fields you would see their look change completely due to the different styles for focus/press behavior.

You can derive the regular styles from the selected/pressed styles but one of the simplest ways is to just copy & paste the styles to the pressed/selected tabs. We can copy CameraButton, RedCommand, SouthButton & TextField to the selected state. Then copy CameraButton, RedCommand & SouthButton to the pressed state to get the complete app running!

5. CSS

In this chapter we’ll discuss theming with CSS in Codename One.

CSS Changes Don’t Require a Recompile
You can change the CSS values while the simulator is running and the changes will reflect in the simulator within a few seconds

5.1. Activating CSS

Codename One applications always use the resource file. The CSS support compiles a file in CSS syntax to a Codename One resource file and adds it to the application. In runtime the CSS no longer exists and the file acts like a regular theme file.

To enable CSS support in Codename One you need to flip a switch in Codename One Settings.

The CSS Option in Codename One Settings
Figure 174. The CSS Option in Codename One Settings Part I
The CSS Option in Codename One Settings
Figure 175. The CSS Option in Codename One Settings Part II

Once enabled your theme.res file will regenerate from a CSS file that resides under the css directory. Changes you make to the CSS file will instantly update the simulator as you save. However, there are some limits to this live update so in some cases a simulator restart would be necessary.

5.2. Supported CSS Selectors

Since Codename One stylesheets are meant to be used with Codename One component hierarchies instead of XML/HTML documents, selectors work a little differently.

  1. All selectors (with some specific exceptions discussed below) are interpreted as UIIDs.

  2. Only 4 predefined CSS classes are supported:

    • .pressed — Targets the component when in "Pressed" state.

    • .selected — Targets the component when in "Selected" state.

    • .unselected — Targets the component when in "Unselected" state.

    • .disabled — Targets the component when in "Disabled" state.

      If no class is specified, then the selector targets "all" states of the given component.

The following are a few possible selectors you can include in your stylesheet.

  1. Button — Defines styles for the "Button" UIID.

  2. Button.pressed — Defines styles for the "Button" UIID’s "pressed" state.

  3. Button, TextField, Form — Defines styles for the "Button", "TextField", and "Form" UIIDs.

The following example creates a simple button with a border, and text aligned center. By default the button will have a transparent background, but when it is pressed, it will have a gray background:

Button {
    text-align: center;
    border: 1pt solid gray;
    background-color: transparent;
}

Button.pressed {
    background-color: gray;
}

5.2.1. Inheriting properties using cn1-derive

The following example defines a custom Button style named "MyButton" that inherits all of the styles of Button but changes the background color to blue.

MyButton {
   cn1-derive: Button;
   background-color: blue;
}

5.3. Special Selectors

5.3.1. #Device

The #Device selector allows you to define which device resolutions this CSS file should target. Mutli-images generated from this style-sheet will only be include variants for device resolutions in the range (min-resolution, max-resolution) as defined in this section. By default all resolutions are generated.

#Device {
    min-resolution: 120dpi;
    max-resolution: 480dpi;
    resolution: 480dpi;
}

5.3.2. #Constants

The #Constants selector allows you to specify theme constants.

e.g.

#Constants {
    PopupDialogArrowBool: false;
    calTitleDayStyleBool: true;
    calTransitionVertBool: false;
    calendarLeftImage: "calendar-arrow-left.png";
    calendarRightImage: "calendar-arrow-right.png";
    centeredPopupBool: false;
    checkBoxCheckDisFocusImage: "Check-Box_Normal.png";
    checkBoxCheckedFocusImage: "Check-Box_Press.png";
    checkBoxCheckedImage: "Check-Box_Press.png";
    checkBoxOppositeSideBool: true;
    checkBoxUncheckedFocusImage: "Check-Box_Normal.png";
    checkBoxUncheckedImage: "Check-Box_Normal.png";
    comboImage: "combo.png";
    commandBehavior: "Side";
    dialogTransitionIn: "fade";
    dialogTransitionOut: "fade";
    dlgButtonCommandUIID: "DialogButton";
    dlgCommandGridBool: true;
    dlgInvisibleButtons: #1a1a1a;
    formTransitionIn: "empty";
    formTransitionOut: "slide";
    includeNativeBool: true;
    menuImage: "of_menu.png";
    noTextModeBool: true;
    onOffIOSModeBool: true;
    otherPopupRendererBool: false;
    pureTouchBool: true;
    radioSelectedFocusImage: "Radio_btn_Press.png";
    radioSelectedImage: "Radio_btn_Press.png";
    radioUnselectedFocusImage: "Radio_btn_Normal.png";
    radioUnselectedImage: "Radio_btn_Normal.png";
    sideMenuImage: "menu.png";
    switchMaskImage: "switch-mask-3.png";
    switchOffImage: "switch-off-3.png";
    switchOnImage: "switch-on-3.png";
    tabPlacementInt: 0;
    backIconImage: "Back-icon.png";
    articleSourceIconImage: "Source-icon.png";
    articleDateIconImage: "Date-icon.png";
    articleArrowRightImage: "Arrow-right.png";
    articleShareIconImage: "Share-icon.png";
    articleBookmarkIconImage: "Bookmark-icon.png";
    articleTextIconImage: "Text-icon.png";
    articleCommentsIconImage: "Comments-icon.png";
    newsIconImage: "News-icon.png";
    channelsIconImage: "Channels-icon.png";
    bookmarksIconImage: "Bookmarks-icon.png";
    overviewIconImage: "Overview-icon.png";
    calendarIconImage: "Calendar-icon.png";
    timelineIconImage: "Timeline-icon.png";
    profileIconImage: "Profile-icon.png";
    widgetsIconImage: "Widgets-icon.png";
    settingsIconImage: "Settings-icon.png";
    SubmitIconImage: "Submit-icon.png";
    SubmitIconDarkImage: "SubmitButtonLight-icon.png";
    defaultFontSizeInt: 18;
    defaultDesktopFontSizeInt: 14;
    defaultSourceDPIInt: "0";

}

In the above example, the constants referring to an image name as a string requires that the image exists in one of the following locations:

  • res/<cssfilename>/<imageName>

  • ../res/<cssfilename>/<imageName>

  • ../../res/<cssfilename>/<imageName>

or that it has been defined as a background image in some selector in this CSS file.

5.3.3. Default

The Default selector is special in that it will set properties on the theme’s "default" element. The default element is a special UIID in Codename One from which all other UIIDs in the same theme are derived. This is a good place to set things like default fonts or background-colors.

5.4. Standard CSS Properties

  • padding (and variants)

  • margin (and variants)

  • border (and variants)

  • border-radius

  • background (Usage below)

  • background-color

  • background-repeat

  • background-image

  • border-image

  • border-image-slice

  • font (Usage is covered in the following font section)

  • font-family (Usage is covered in the following font section)

  • font-style (Usage is covered in the following font section)

  • font-size (Usage is covered in the following font section)

  • @font-face (Usage is covered in the following font section)

  • color

  • text-align

  • text-decoration(Usage below)

  • opacity

  • box-shadow

  • width (only used for generating background-images and borders)

  • height (only used for generating background-images and borders)

5.5. Custom Properties

cn1-source-dpi

Used to specify source DPI for multi-image generation of background images. Accepted values: 0 (Not multi-image), 120 (Low res), 160 (Medium Res), 320 (Very High Res), 480 (HD), Higher than 480 (2HD). If not specified, the default value will be the value of the defaultSourceDPIInt theme constant, if specified, or 480, if not specified.

cn1-background-type

Used to explicitly specify the background-type that should be used for the class.

cn1-9patch

Used to explicitly specify the slices used when generating 9-piece borders. Deprecated - Use border-image and border-image-slice for 9-piece borders.

cn1-derive

Used to specify that this UIID should derive from an existing UIID.

5.6. CSS Variables

As of CodenameOne 7.0, you can use variables in your CSS file via the var() CSS function. E.g.

var(--header-color, blue);

The var() function can only be used inside property values. I.e. You cannot use it in property names or selectors.

Syntax:

var(<custom-property-name>, <declaration-value>?)

The <custom-property-name> must begin with two dashes (--).

The <declaration-value> is the fallback value that will be used if the variable hasn’t been defined in the CSS file. The fallback value may include commas.

Examples

Listing 13. Example defining and using a CSS variable
#Constants {
    --main-bg-color: red;
}

MyContainer {
    background-color: var(--main-bg-color);
}
Listing 14. Example using a fallback value
#Constants {
    --main-bg-color: red;
}

MyContainer {
    background-color: var(--main-bg-color, blue);
}

See the MDN docs for more details about the CSS variable spec.

5.7. CSS Properties

This section isn’t as comprehensive as it should be due to the breadth of CSS.

5.7.1. text-decoration

underline

Underlines text. E.g. text-decoration: underline;

overline

Overlines text. E.g. text-decoration: overline;

line-through

Strikes through text. E.g. text-decoration: line-through;

none

No text decoration. E.g. text-decoration: none;

cn1-3d

3D text. E.g. text-decoraton: cn1-3d; cn1-3d screenshot

cn1-3d-lowered

3D lowered text. E.g. text-decoration: cn1-3d-lowered; cn1-3d-lowered screenshot

cn1-3d-shadow-north

3D text with north shadow. E.g. text-decoration: cn1-3d-shadow-north; cn1-3d-shadow-north screenshot

For other CSS font settings see the Fonts section

5.7.2. border

This library supports the border property and most of its variants (e.g. border-width, border-style, and border-color. It will try to use native Codename One styles for generating borders if possible. If the border definition is too complex, it will fall-back to generating a 9-piece image border at compile-time. This has the effect of making the resulting resource file larger, but will produce good runtime performance, and a look that is faithful to the provided CSS.

The algorithm used to determine whether to use a native border or to generate a 9-piece image, is complex, but the following guidelines may help you if you wish to design borders that can be rendered natively in CN1:

  • Non-pixel units border-width. (Except with the cn1-round-border and cn1-pill-border styles)

  • Using the border-radius directive.

  • Using box-shadow (Except when using cn1-round-border or cn1-pill-border styles)

  • Using a background gradient in combination with a border or any kind

  • Using a different border-width, border-style, or border-color for different sides of the border

  • Using a filter

You can open the resulting theme file in the designer and inspect it to see if an image was generated

Generating the image triggers slower CSS compilation and a larger binary so we generally recommend tuning the CSS so it avoids this fallback.

Round Borders

Rounded borders can be achieved in a few different ways. The easiest methods are:

  • The cn1-round-border style. This will render a circular round border in the background natively. I.e. this doesn’t require generation of an image border

  • The cn1-pill-border style. This will render a pill-shaped border in the background natively. This also doesn’t require generation of an image border

  • The border-radius property. This will round the corners of the border. If the style can be achieved using the RoundRectBorder in CodenameOne, then it will use that border. If not, this will cause the style to be generated as an image border

Examples using cn1-round-border

RoundBorder {
    border: 1px #3399ff  cn1-round-border;
    text-align:center;
    margin:2mm;
    padding:3mm;
}

RoundBorderFilled {
    background: cn1-round-border;
    background-color: #ccc;
    text-align:center;
    margin:2mm;
    padding:3mm;
}

Examples using cn1-pill-border

PillBorder {
    border: 1pt #3399ff cn1-pill-border;
    text-align:center;
}

PillBorderFilled {
    background: cn1-pill-border;
    background-color: #3399ff;
    color:white;
    text-align:center;
}

Examples using border-radius

RoundRectLabel {
    background-color: red;
    border-radius: 2mm;
}

cn1-pill-border and cn1-round-border don’t support the standard CSS box-shadow property. This is because the box-shadow property parameters don’t map nicely onto the shadow parameters for the Codename One RoundBorder class. To get shadows on the cn1-pill-border, you should use one or more of the following CSS properties:

  • cn1-box-shadow-spread — Accepts values in any scalar unit (e.g. px, mm, cm, etc…​). This maps directly to the border’s shadowSpread property.

  • cn1-box-shadow-h — Accepts values in real values or integers (not a scalar unit). This maps directly to the border’s shadowX property.

  • cn1-box-shadow-v — Accepts values in real values or integers (not a scalar unit). This maps directly to the border’s shadowY property.

  • cn1-box-shadow-blur — Scalar value. Maps to the border’s shadowBlur property.

  • cn1-box-shadow-color — The shadow color

Currently using the regular CSS box-shadow in conjunction with border-radius will cause a 9-piece border to be generated rather than mapping to the RoundRectBorder. If, however, you use the cn1-box-* properties for the shadow instead, it will use the RoundRectBorder — assuming that no other styles are specified that trigger an image border to be generated.

5.7.3. background

The background property supports most standard CSS values for setting the background color, or background image.

9-piece Image borders always take precedence over background settings in Codename One. If your background directive seems to have no effect, it is likely because the theme has specified a 9-piece image border for the UIID. You can disable the image border using a directive like border: none
Background Images

See Images

Gradients

Both the linear-gradient and radial-gradient CSS functions are fully supported by this library. If Codename One is capable of rendering the gradient natively then the theme resource file generated will only include encoded parameters for the gradients. If the gradient is not supported by Codename One, then the module will fall back to an image background which it generates at compile-time. It is generally preferable to try to stick to gradients that Codename One supports natively. This will result in a smaller theme resource file since it doesn’t need to generate any images for the gradient.

Natively Supported linear-gradient Syntax

In order for a linear gradient to be natively supported by Codename One, the following properties must be met:

  1. The gradient function has exactly two color stops, and these colors have the same opacity.

  2. The gradient is either perfectly horizontal or perfectly vertical. (e.g Direction can be 0deg, 90deg, 180deg, or 270deg.

Examples

MyContainer {
   background: linear-gradient(0deg, #ccc, #666);
}
Native linear gradient 0 degrees
Figure 176. Native linear gradient 0 degrees
MyContainer {
    background: linear-gradient(to top, #ccc, #666);
}
Native linear gradient to top
Figure 177. Native linear gradient to top
MyContainer {
    background: linear-gradient(90deg, #ccc, #666);
}
Native linear gradient 90deg
Figure 178. Native linear gradient 90deg
MyContainer {
    background: linear-gradient(to left, #ccc, #666);
}
Native linear gradient to left
Figure 179. Native linear gradient to left

Unsupported linear-gradient syntax

The following are some examples of linear gradients that aren’t supported natively by Codename One, and will result in a background image to be generated at compile-time:

MyComponent {
    background: linear-gradient(45deg, #eaeaea, #666666);
}
45deg gradient rendered at compile-time - uses background image
Figure 180. 45deg gradient rendered at compile-time — uses background image

The above example is not supported natively because the gradient direction is 45 degrees. Codename One only supports 0, 90, 180, and 270 degrees natively. Therefore this would result in a background image being generated at compile-time with the appropriate gradient.

MyComponent {
    background: linear-gradient(90deg, rgba(255, 0, 0, 0.6), blue);
}
Linear gradient with different alpha
Figure 181. Linear gradient with different alpha

The above linear-gradient is not supported natively because the stop colors have different transparencies. The first color has an opacity of 0.5, and the second as an opacity of 1.0 (implicitly). Therefore, this would result in the gradient being generated as an image at compile-time.

Natively Supported radial-gradient Syntax

The following syntax is supported natively for radial gradients. Other syntaxes are also supported by the CSS library, but they will use compile-time image generation for the gradients rather than generating them at runtime.

background: radial-gradient(circle [closest-side] [at <position>], <color stop>, <color stop>)
  • <position> — The position using either offset keywords or percentages.

  • <color stop> — Either a color alone, or a color followed by a percentage. 0% indicates that color begins at center of the circle. 100% indicates that the color begins at the closest edge of the bounding box. Higher/lower values (>0%) will shift the color further or closer to circle’s center. If the first color stop is set to a non-zero value, the gradient cannot be rendered natively by Codename One, and an image of the gradient will instead be generated at compile-time.

More complex gradients are supported by this library, but they will be generated at compile-time. For more information about the radial-gradient CSS function see its MDN Wiki page.

Examples

MyContainer {
    background: radial-gradient(circle, gray, white);
}
Radial gradient 0 to 100
Figure 182. Radial gradient 0 to 100
MyContainer {
    background: radial-gradient(circle, gray, white 200%);
}
Radial gradient 0 to 200
Figure 183. Radial gradient 0 to 200
MyContainer {
    background: radial-gradient(circle at left, gray, white);
}
Radial gradient at left
Figure 184. Radial gradient at left
MyContainer {
    background: radial-gradient(circle at right, gray, white);
}
Radial gradient at right
Figure 185. Radial gradient at right

5.7.4. cn1-background-type

It also supports some special Codename One values, which are identifiers with a "cn1-" prefix. The following special values are available. They map to the standard Codename One values we discussed in the theming chapter:

  • cn1-image-scaled

  • cn1-image-scaled-fill

  • cn1-image-scaled-fit

  • cn1-image-tile-both

  • cn1-image-tile-valign-left

  • cn1-image-tile-valign-center

  • cn1-image-tile-valign-right

  • cn1-image-tile-halign-top

  • cn1-image-tile-halign-center

  • cn1-image-tile-halign-bottom

  • cn1-image-align-bottom

  • cn1-image-align-left

  • cn1-image-align-right

  • cn1-image-align-center

  • cn1-image-align-top-left

  • cn1-image-align-top-right

  • cn1-image-align-bottom-left

  • cn1-image-align-bottom-right

  • cn1-image-border

  • cn1-none

  • cn1-round-border

  • cn1-pill-border

5.8. Images

Images are supported as both "inputs" of the stylesheet, and as outputs to the compiled resource file. "Input" images are usually specified via the background-image property in a selector. "Output" images are always saved as multi-images inside the resource file.

5.8.1. Image DPI and Device Densities

In order to appropriately size the image, the CSS compiler needs to know what the source density of the image is. E.g. if an image is 160x160 pixels with a source density of 160dpi (i.e. medium density - or the same as an iPhone 3G), then the resulting multi-image will be sized at 160x160 for medium density devices and 320x320 on very high density devices (e.g. iPhone 4S Retina) - which will result in the same perceived size to the user of 1x1 inch.

However if the image has a source density of 320dpi, then the resulting multi-image would be 80x80 pixels on medium density devices and 160x160 pixels on very high density devices.

Some images have this density information embedded in the image itself so that the CSS processor will know how to resize the image properly. However, it is usually better to explicitly document your intentions by including the cn1-source-dpi property as follows:

SomeStyle {
    background-image: url(images/my-image.png);
    cn1-source-dpi: 160;
}
cn1-source-dpi values are meant to fall into threshold ranges. Values less than or equal to 120, are interpreted as low density. 121 - 160 are medium density (iPhone 3GS). 161 - 320, very high density (iPhone 4S). 321 - 480 == HD. 481 and higher == 2HD. In general, you should try to use images that are one of these DPIs exactly: 160, 320, or 480, then images will be scaled up or down to the other densities accordingly.

5.8.2. Multi-Images vs Regular Images

By default all images are imported as multi-images (unless you define the defaultSourceDPIInt theme constant). If you want to import an image as a "regular" image, you can simply set cn1-source-dpi to 0. E.g.

SomeStyle {
    background-image: url(images/my-image.png);
    cn1-source-dpi: 0;
}

You can change the default source DPI for the whole stylesheet by adding defaultSourceDPIInt: 0 to the theme constants. E.g.

#Constants {
  defaultSourceDPIInt: 0;
}

Most application templates in Codename One Initializr include this constant by default.

5.8.3. Multi-Images as Inputs

If you have already generated images in all of the appropriate sizes for all densities, you can provide them in the same file structure used by the Codename One XML resource files: The image path is a directory that contains images named after the density that they are intended to be used for. The possible names include:

  • verylow.png

  • low.png

  • medium.png

  • high.png

  • veryhigh.png

  • 560.png

  • hd.png

  • 2hd.png

  • 4k.png

E.g. Given the CSS directives:

MyStyle {
    background-image: url(images/mymultiimage.png);
}

The files would look like:

css/
 +--- mycssfile.css
 +--- images/
       +--- mymultiimage.png/
             +--- verylow.png
             +--- low.png
             +--- medium.png
              ... etc...
Multi-image inputs are only supported for local URLs. You cannot use remote (e.g. http://) urls with multi-image inputs

5.8.4. Image Constants

Theme constants can be images. The convention is to suffix the constant name with "Image" so that it will be treated as an image. In addition to the standard url() notation for specifying a constant image, you can provide a simple string name of the image, and the CSS processor will try to find an image by that name specified as a background image for one of the styles. If it cannot find one, it will look inside a special directory named "res" (located in the same directory as the CSS stylesheet), inside which it will look for a directory named the same as the stylesheet, inside which it will look for a directory with the specified multi-image. This directory structure is the same as used for Codename One’s XML resources directory.

E.g. In the CSS file "mycssfile.css":

radioSelectedFocusImage: "Radio_btn_Press.png";

Will look for a directory located at res/mycssfile.css/Radio_btn_Press.png/ with the following images:

  • verylow.png

  • low.png

  • medium.png

  • high.png

  • veryhigh.png

  • 560.png

  • hd.png

  • 2hd.png

  • 4k.png

It will then create a multi-image from these images and include them in the resource file.

5.9. Image Recipes

5.9.1. Import Multiple Images In Single Selector

It is quite useful to be able to embed images inside the resource file that is generated from the CSS stylesheet so that you can access the images using the Resources.getImage() method in your app and set it as an icon on a button or label. In this case, it is easier to simply create a dummy style that you don’t intend to use and include multiple images in the background-image property like so:

Images {
    background-image: url(images/NowLogo.png),
        url(images/Username-icon.png),
        url(images/Password-icon.png),
        url(images/Name-icon.png),
        url(images/Email-icon.png),
        url(images/SeaIce.png),
        url(images/Back-icon.png),
        url(images/Source-icon.png),
        url(images/Date-icon.png),
        url(images/Arrow-right.png),
        url(images/Share-icon.png),
        url(images/Text-icon.png),
        url(images/Comments-icon.png),
        url(images/RedPlanet.png),
        url(images/News-icon.png),
        url(images/Channels-icon.png),
        url(images/Bookmarks-icon.png),
        url(images/Overview-icon.png),
        url(images/Calendar-icon.png),
        url(images/Timeline-icon.png),
        url(images/Profile-icon.png),
        url(images/Widgets-icon.png),
        url(images/Settings-icon.png),
        url(images/Bookmark-icon.png);
}

Then in Java, I might do something like:

Resources theme = Resources.openLayered("/theme");

Label bookmark = new Label(theme.getImage("Bookmark-icon.png"));

5.9.2. Loading Images from URLs

You can also load images from remote URLs. E.g.

Images {
    background-image: url(http://solutions.weblite.ca/logo.png);
}

5.9.3. Generating 9-Piece Image Borders

Image 270320 112406.063

9-Piece image borders can be created using the image-border and image-border-slice properties.

E.g.

NinePiece {
	border-image:url('dashbg_landscape.png');
}

In the above example we omitted the border-image-slice property, so it defaults to "40%", which means that the image is sliced 40% from the top, 40% from the bottom, 40% from the left, and 40% from the right.

If you want more specific "slice" points, you can add the border-image-slice property. E.g.

NinePiece {
	border-image:url('dashbg_landscape.png');
	border-image-slice:10% 49%;  /*vertical horizontal*/
}

NinePiece2 {
	border-image:url('dashbg_landscape.png');
	border-image-slice:10% 49% 20%;  /*top horizontal bottom*/
}

NinePiece3 {
	border-image:url('dashbg_landscape.png');
	border-image-slice:10% 30% 40% 20%;  /*top right bottom left*/
}

NinePiece4 {
	border-image:url('dashbg_landscape.png');
	border-image-slice:10%;  /*all*/
}

5.9.4. Image Backgrounds

Component backgrounds in Codename One are a common source of confusion for newcomers because there are 3 different properties that can be used to define what a component’s background looks like, and they have priorities:

  1. Background Color - You can specify an RGB color to be used as the background for a component.

  2. Background Image - You can specify an image to be used as the background for a component. Codename One includes settings to define how the image is treated, e.g. scale/fill, tile, etc. If a background image is specified, it will override the background color setting - unless the image has transparent regions.

  3. Image Border - You can define a 9-piece image border which will effectively cover the entire background of the component. If an image border is specified, it will override the background image of the component.

A common scenario that I run into is trying to set the background color of a component and see no change when I preview my form because the style had an image background defined - which overrides my background color change.

The potential for confusion is mitigated somewhat, but still exists when using CSS. You can make your intentions explicit by adding the cn1-background-type property to your style. Possible values include:

  • cn1-image-scaled

  • cn1-image-scaled-fill

  • cn1-image-scaled-fit

  • cn1-image-tile-both

  • cn1-image-tile-valign-left

  • cn1-image-tile-valign-center

  • cn1-image-tile-valign-right

  • cn1-image-tile-halign-top

  • cn1-image-tile-halign-center

  • cn1-image-tile-halign-bottom

  • cn1-image-align-bottom

  • cn1-image-align-left

  • cn1-image-align-right

  • cn1-image-align-center

  • cn1-image-align-top-left

  • cn1-image-align-top-right

  • cn1-image-align-bottom-left

  • cn1-image-align-bottom-right

  • cn1-image-border

  • cn1-none

  • none

Example Setting Background Image to Scale Fill
MyContainer {
    background-image: url(myimage.png);
    cn1-background-type: cn1-image-scaled-fill;
}

5.10. Image Compression

CN1 resource files support both PNG and JPEG images, but PNG is the default. Multi-images that are generated by the CSS compiler will be PNG if they include alpha transparency, and JPEG otherwise. This is to try to reduce the file size as much as possible while not sacrificing quality.

5.11. Fonts

This library supports the font, font-size, font-family, font-style, font-weight, and text-decoration properties, as well at the @font-face CSS "at" rule for including TTF/OTF fonts.

5.11.1. font-family

By default, CN1’s native fonts are used. The appropriate native font is selected for the provided font-weight and font-style properties. You can also explicitly specify the native font you wish to use in the font-family property. E.g.

SideCommand {
    font-family:  "native:MainThin";
}

If you omit the font-family directive altogether, it will use native:MainRegular. The following native fonts are available:

  1. native:MainThin

  2. native:MainLight

  3. native:MainRegular

  4. native:MainBold

  5. native:MainBlack

  6. native:ItalicThin

  7. native:ItalicLight

  8. native:ItalicRegular

  9. native:ItalicBold

  10. native:ItalicBlack

Using TTF Fonts

If you want to use a font other than the built-in fonts, you’ll need to define the font using the @font-face rule. E.g.

@font-face {
    font-family: "Montserrat";
    src: url(res/Montserrat-Regular.ttf);
}

Then you’ll be able to reference the font using the specified font-family in any CSS element. E.g.

MyLabel {
    font-family: "Montserrat";
}

The @font-face directive’s src property will accept both local and remote URLs. E.g.

@font-face {
    font-family: "MyFont";
    src: url(http://example.com/path/to/myfont.ttf);
}

In this case, it will download the myfont.ttf file to the same directory as the CSS file. From then on it will use that locally downloaded version of the font so that it doesn’t have to make a network request for each build.

Fonts are automatically copied to the project’s "src" directory when the CSS file is compiled so that they will be distributed with the app and available at runtime.

Github URLs

Fonts hosted on GitHub are accessible using a special github: protocol to make it easier to reference such fonts. E.g. the following directive includes the "FontAwesome" font directly from Github

@font-face {
    font-family: "FontAwesome";
    src: url(github://FontAwesome/Font-Awesome/blob/master/fonts/fontawesome-webfont.ttf);
}
Apparently FontAwesome has removed its public repositories from Github so this URL no longer works.

5.11.2. font-size

It is best practice to size your fonts using millimetres (rem) (or another "real-world" measurement unit such as inches (in), centimetres (cm), millimetres (mm). This will allow the font to be sized appropriate for all display densities. If you specify size in pixels (px), it will treat it the same as if you sized it in points (pt), where 1pt == 1/72 inches (one seventy-second of an inch).

If you size your font in percentage units (e.g. 150%) it will set the font size relative to the medium font size of the platform. This is different than the standard behaviour of a web browser, which would size it relative to the parent element’s font size.

font-size: 150% is the same as font-size: 1.5rem.

You can use system fonts, true type fonts, and native fonts in your CSS stylesheet. True Type fonts need to be defined in a @font-face directive before they can be referenced. True-type fonts and native fonts have the advantage that you can specify their sizes in generic terms (e.g. small, medium, large) and in more specific units such as millimeters (mm) or pixels (px).

Normalizing Default Font Size

When trying to make a design look “good” across multiple platforms it can be difficult to deal with the differing default font sizes on different platforms. You may spend hours tweaking your UI to look perfect on iPhone X, only to find out that the fonts are too small when viewed on an android device. We have now added theme constants to explicitly set the the default font size in “screen-independent-pixels”.

Note In this case, 1 screen-independent-pixel is defined as 1/160th of an inch on a device, and 1/96th of an inch on desktop. These values correspond to Android’s definition on device, and Windows' definition on the desktop.

If you add the following to your stylesheet, it will set the default font size to 18 screen-independent pixels (or 18/160th of an inch), which corresponds to the Android native default “medium” font size.

#Constants {
    defaultFontSizeInt: 18;
}
I have found that a value of 18 here gives optimum results across devices.

On the desktop, you may find that 18 is too big. You can additionally define a default font size for for tablet and desktop using defaultDesktopFontSizeInt and defaultTabletFontSizeInt respectively. I have found that a defaultDesktopFontSizeInt gives results that closely match the Mac OS default font size.

#Constants {
    defaultFontSizeInt: 18;
    defaultDesktopFontSizeInt: 14;
}

5.11.3. text-decoration

See the text-decoration section in the "Supported Properties" page.

5.11.4. Some Sample CSS Directives

@font-face {
    font-family: "Montserrat";
    src: url(res/Montserrat-Regular.ttf);
}

@font-face {
    font-family: "Montserrat-Bold";
    src: url(res/Montserrat-Bold.ttf);
}

@font-face {
    font-family: "FontAwesome";
    src: url(github://FontAwesome/Font-Awesome/blob/master/fonts/fontawesome-webfont.ttf);
}

PlainText0p5mm {
    font-size: 0.5mm;
}

PlainText1mm {
    font-size: 1mm;
}

PlainText2mm {
    font-size: 2mm;
}

PlainText5mm {
    font-size: 5mm;
}

PlainText10mm {
    font-size: 10mm;
}

PlainText50mm {
    font-size: 50mm;
}

PlainTextSmall {
    font-size: small;
}

PlainTextMedium {
    font-size: medium;
}

PlainTextLarge {
    font-size: large;
}

PlainText3pt {
    font-size: 3pt;
}

PlainText6pt {
    font-size: 6pt;
}

PlainText12pt {
    font-size: 12pt;
}

PlainText20pt {
    font-size: 20pt;
}

PlainText36pt {
    font-size: 36pt;
}

BoldText {
    font-weight: bold;
}

BoldText1mm {
    font-weight: bold;
    font-size: 1mm;
}

BoldText2mm {
    font-weight: bold;
    font-size: 2mm;
}

BoldText3mm {
    font-weight: bold;
    font-size: 3mm;
}

BoldText5mm {
    font-weight: bold;
    font-size: 5mm;
}

ItalicText {
    font-style: italic;
}

ItalicText3mm {
    font-style: italic;
    font-size: 3mm;
}

ItalicBoldText {
    font-style: italic;
    font-weight: bold;
}

PlainTextUnderline {
    text-decoration: underline;
}

BoldTextUnderline {
    text-decoration: underline;
    font-weight: bold;
}

ItalicTextUnderline {
    text-decoration: underline;
    font-style: italic;
}

PlainText3d {
    text-decoration: cn1-3d;
    color:white;
    background-color: #3399ff
}

BoldText3d {
    text-decoration: cn1-3d;
    font-weight: bold;
    color:white;
    background-color: #3399ff;
}

ItalicText3d {
    text-decoration: cn1-3d;
    font-style: italic;
    color:white;
    background-color: #3399ff;
}

PlainText3dLowered {
    text-decoration: cn1-3d-lowered;
    color:black;
    background-color: #3399ff;
}

BoldText3dLowered {
    text-decoration: cn1-3d-lowered;
    font-weight: bold;
    color:black;
    background-color: #3399ff;
}

ItalicText3dLowered {
    text-decoration: cn1-3d-lowered;
    font-style: italic;
    color:black;
    background-color: #3399ff;
}

PlainText3dShadow {
    text-decoration: cn1-3d-shadow-north;
    color:white;
    background-color: #3399ff;
}

BoldText3dShadow {
    text-decoration: cn1-3d-shadow-north;
    font-weight: bold;
    color:white;
    background-color: #3399ff;
}

ItalicText3dShadow {
    text-decoration: cn1-3d-shadow-north;
    font-style: italic;
    color:white;
    background-color: #3399ff;
}


MainThin {

    font-size: 200%;
    background: radial-gradient(circle at top left, yellow, blue 100%);
}

MainRegular0001 {
    font-family: "native:MainRegular";
    /*background: cn1-pill-border;
    background-color: red;*/
    color: blue;
    border: 1px cn1-pill-border blue;
    /*box-shadow: 1mm 1mm 0 2mm rgba(0,0,0,1.0);*/
    padding: 2mm;
}

MainRegular0001.pressed {
    font-family: "native:MainRegular";
    background: cn1-pill-border blue;
    /*background-color: red;*/
    color: white;
    border: 1px solid white;
    /*box-shadow: 1mm 1mm 0 2mm rgba(0,0,0,1.0);*/
    padding: 2mm;
}

Heading {
    font-size: 4mm;
    font-family: "Montserrat-Bold";
    color: black;
    padding: 2mm;
    text-align: center;
}

XMLVIewIcon {
    font-family: "FontAwesome";
}

5.12. Media Queries

You can use media queries to target styles to specific platforms, devices, and device densities. Currently the following media queries are supported:

  1. platform-xxx - Target a specific platform. E.g. platform-and, platform-ios, platform-mac, platform-win.

  2. density-xxx - Target a specific device density. E.g. density-very-low, density-low, density-medium, density-high, density-very-high, density-hd, density-2hd, and density-560.

  3. device-xxx - Target a specific device type. E.g. device-desktop, device-tablet, device-phone.

Listing 15. Example: Different font colors on Android and iOS. On Android, labels will appear green. On iOS, they will appear red. On all other platforms, they will appear black.
Label {
    color: black;
}

@media platform-and {
    Label {
        color: green;
    }
}

@media platform-ios {
    Label {
        color: red;
    }
}
Listing 16. Example: Different font colors based on device density. On lower densities, labels will be green. On higher densities, labels will be red.
Label {
    color: black;
}

@media density-very-low, density-low, density-medium, density-high {
    Label {
        color: green;
    }
}

@media density-very-high, density-h2, density-2hd, density-560 {
    Label {
        color: red;
    }
}
Listing 17. Example: Different label colors based on device type.
Label {
    color: black;
}

@media device-desktop {
    Label {
        color: green;
    }
}

@media device-tablet, device-phone {
    Label {
        color: red;
    }
}
When deploying your app using the Javascript port, it will use a platform name derived from the "UserAgent" string in the browser, rather than the result of Display.getPlatformName(), which is used for other ports. When running on Android, then, the platform will be "and". When running on iOS, the platform will be "ios". Etc…​

5.12.1. Compound Media Queries

You can combine multiple media queries together, separated by a comma. Queries of the same type are "OR"ed together. Queries of different types are "AND"ed together. For example if you have a media query that specifies two different device densities (e.g. density-low and density-high) the query will match both devices with low density and high density. However, if the query specifies a device density and a platform (e.g. density-low and platform-and), then it will only match a device if it matches the platform and the density.

Listing 18. Example: Targeting styles to only Android devices with high density
@media platform-and, density-high {
    ....
}
Listing 19. Example: Targeting styles to iOS devices with high or low density
@media platform-ios, density-high, density-low {
    ....
}
Listing 20. Example: Targeting only Mac Desktop.
@media device-desktop, platform-mac {
    ....
}

5.12.2. Order or Precedence

The order of precendence when applying styles differs slightly from the way styles would be applied in standard CSS. The order of precendence is as follows:

  1. Styles defined inside @media blocks will always take precendence over styles defined outside of @media blocks.

  2. @media blocks with more query matches will take precendence over blocks with fewer query matches. E.g. A media block matching density, platform, and device will take precendence over a block that only matches the density and platform.

  3. If the same style is defined in two media blocks which contain the same number of query matches, then the order precedence is platform, device, density in decreasing order. I.e. the block that matches on platform will take precedence over the block that matches on density.

  4. If the same style is defined in two media blocks with identical query matches, then the order of precedence is undefined.

5.12.3. Font Scaling Constants

In some cases you may find that fonts are coming out too large or too small across the board on certain types of devices. You can use standard media queries to customize font sizes, but you can also use font-scaling constants to scale font sizes for the entires stylesheet based on platform, device, and/or density. In some cases you may find this approach easier.

For example, consider the following simple stylesheet that defines a font size of 2mm on labels:

Label {
    font-size: 3mm;
}

During testing, perhaps you find that, on desktop, the fonts are a little bit too small. In this case, you can apply a font-scale constant that only applies to the desktop.

#Constants {
    device-desktop-font-scale: "1.5";
}

Label {
    font-size: 3mm;
}

Now, on most devices the Label style will have 3mm fonts. But on desktop, it will have 4.5mm fonts.

The above would be roughly equivalent to:

Label {
    font-size: 3mm;
}

@media device-desktop {
    Label {
        font-size: 4.5mm;
    }
}
Listing 21. Example: Font-scaling based on device, platform, and density
#Constants {
    device-phone-font-scale: "1.5";
    device-tablet-font-scale: "1.2";
    device-desktop-font-scale: "1.4";
    platform-ios-font-scale: "0.9";
    density-low-font-scale: "1.2";
    platform-ios-density-low-font-scale: "1.3";
}
All matching font-scale constants will be applied to the styles. If you define 3 font-scale constants that all match the current runtime environment, they will all be applied. E.g. If there are 3 matching font-scale constants with "2.0", "3.0", and "4.0", then fonts will be scaled by 2*3*4=24!

6. 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.

6.1. 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 186. 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.

6.1.1. 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.

6.2. 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 187. 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.

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().

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

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.

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.

6.3. 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.

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.

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 188. Custom modal Dialog in the south position
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 189. Custom Dialog positioned absolutely
hi is the name of the parent Form in the sample above.

6.3.1. 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.

6.3.2. 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 190. 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!

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 191. 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.

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 192. 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 193. The blur effect is more pronounced when the tint is disabled

6.3.3. 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 194. 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.

This will change with a future update where all OS’s will align and iOS will use the lightweight popup too
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.

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.

6.4. 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.

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 195. 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.

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.

6.5. 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 196. Label positions

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

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.

6.5.1. 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.

6.5.2. 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 197. Automatically sizes the fonts of the buttons/labels based on text and available space

6.6. 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

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.

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.

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 198. Simple text component sample
The Toolbar section contains a very elaborate TextField search sample with DataChangeListener and rather unique styling.

6.6.1. 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.

6.6.2. 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).

The constraint property for the TextField/TextArea is crucial for a virtual keyboard.
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 199. Next virtual keyboard with toolbar
Done virtual keyboard without toolbar
Figure 200. Done virtual keyboard without toolbar
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.

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

6.6.3. 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.

6.7. 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 201. Text Input on iOS
TextModeLayout on Android with the same code
Figure 202. 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();
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.

6.7.1. 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 203. Error handling when the text is blank
Error handling when there is some input (notice red title label)
Figure 204. Error handling when there is some input (notice red title label)
On iOS the situation hasn’t changed much yet
Figure 205. 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");

6.7.2. 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 206. Picker component taking place in iOS
And in Android
Figure 207. 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.

6.7.3. 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

6.8. 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.

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.

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 208. 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 209. Button styled to look like a link

6.8.1. 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.

6.8.2. 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 210. 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 211. 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();

6.8.3. 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.

6.9. 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.

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 212. 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.

6.9.1. 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.

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 213. 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 214. Toggle button converted sample wrapped in ComponentGroup

6.10. 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 215. 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.

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

6.11. 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.

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 216. Multiple usage scenarios for the MultiButton

6.11.1. 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.

6.12. 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 217. The SpanButton Component
SpanButton is slower than both Button and MultiButton. We recommend using it only when there is a genuine need for its functionality.

6.13. 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 218. The SpanLabel Component
SpanLabel is significantly slower than Label. We recommend using it only when there is a genuine need for its functionality.

6.14. 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 219. 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.

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

6.14.1. 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 220. Validation & Regular Expressions

6.15. InfiniteProgress

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

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 221. 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.

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

6.16. 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 222. 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;
    }
}
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
Do not violate the EDT in the callback. It is invoked on the event dispatch thread and it is crucial

6.16.1. 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);

6.17. List, MultiList, Renderers & Models

6.17.1. 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.

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.

6.17.2. 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.

6.17.3. 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.

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!

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.

6.17.4. 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 223. Typical MVC Diagram [3]

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.

6.17.5. 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);

6.17.6. 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 224. 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 225. With cover images in place
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 226. It took ages to scroll this far…​ This goes to a million…​

6.17.7. 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();
}

6.17.8. 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.

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 [4]. 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 227. 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);

6.17.9. 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.

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 228. GRRM ComboBox

6.18. 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 229. 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 230. Star Slider set to 5 (its between 0 - 10)
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.

6.19. 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.

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 231. Simple Table usage
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 232. 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 233. Table with customize cells using the pinstripe effect
Picker table cell during edit
Figure 234. 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();
Notice that we don’t really need to do anything else as binding to the TextArea is builtin to the Table.
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 235. Multiline table cell in portrait mode
Multiline table cell in landscape mode. Notice the cell row count adapts seamlessly
Figure 236. Multiline table cell in landscape mode. Notice the cell row count adapts seamlessly

6.19.1. 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.

6.20. 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 237. Tree
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);
}
The try(Stream) syntax is a try with resources clogic that implicitly closes the stream.
XML Tree
Figure 238. 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;
    }
}

6.21. 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.).

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 239. The share button running on the simulator
ShareButton behaves very differently on the device…​
The share button running on the Android device and screenshot sent into twitter
Figure 240. The share button running on the Android device and screenshot sent into twitter
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…​

6.22. 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 241. 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 242. Swipeable Tabs with an iOS carousel effect page 1
Swipeable Tabs with an iOS carousel effect page 2
Figure 243. Swipeable Tabs with an iOS carousel effect page 2
Notice that we used setRadioButtonImages to explicitly set the radio button images to the look we want for the carousel.

6.23. MediaManager & MediaPlayer

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.

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 244. 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 245. Video playback running on an Android device. Notice the native playback controls that appear when the video is tapped
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.

6.24. 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).

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);
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 246. ImageViewer as the demo loads with the image from the default icon
ImageViewer zoomed in
Figure 247. 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 248. 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.

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 249. Dynamically fetching an image URL from the internet [5]

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.

6.25. 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.

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 250. ScaleImageLabel/Button, the top row includes scale to fit versions (the default) whereas the bottom row includes the scale to fill versions
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.

6.26. 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 251. The Toolbar
The default sidemenu of the Toolbar
Figure 252. The default sidemenu of the Toolbar
The overflow menu of the Toolbar
Figure 253. 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.

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 254. Search field within the toolbar
Search field after typing a couple of letters
Figure 255. Search field after typing a couple of letters

6.26.1. Search Mode

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

Builtin toolbar search functionality
Figure 256. 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();

6.26.2. 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.

6.26.3. 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 [6] 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 257. 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 258. 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 259. 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.

6.27. BrowserComponent & WebBrowser

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 260. Browser Component showing the Codename One website on the simulator
The scrollbars only appear in the simulator, device versions of the browser component act differently and support touch scrolling.
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");
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.

6.27.1. 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.

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.

6.27.2. 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.

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 261. Before the link is clicked for the "shouldNavigate" call
After the link is clicked for the "shouldNavigate" call
Figure 262. After the link is clicked for the "shouldNavigate" call
The JavaScript Bridge is implemented on top of the BrowserNavigationCallback.

6.27.3. JavaScript

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:

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.

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”.

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 263. JavaScript code was invoked to append text into the browser image above
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 5. Java to JavaScript
Java Type Javascript Type

String

String

Double/Integer/Float/Long

Number

Boolean

Boolean

JSObject

Object

null

null

Other

Not Allowed

Table 6. JavaScript to Java
Javascript Type Java Type

String

String

Number

Double

Boolean

Boolean

Object

JSObject

Function

JSObject

Array

JSObject

null

null

undefined

null

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 264. 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/.

6.27.4. 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.

6.28. 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 265. 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 266. Autocomplete Text Field with a webservice

6.28.1. 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 267. Auto complete with images

6.29. 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.

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 268. The various picker components
The date & time picker on the simulator
Figure 269. The date & time picker on the simulator
The date picker component on the Android device
Figure 270. 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 271. 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 272. String picker on the native Android device
Time picker on the Android device
Figure 273. Time picker on the Android device
Duration picker on Android device
Figure 274. Duration picker on Android device
Hours Duration picker on Android device
Figure 275. Hours Duration picker on Android device
Minutes Duration picker on Android device
Figure 276. 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.

6.30. 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 277. SwipableContainer showing a common use case of ranking on swipe

6.31. 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.

6.32. MapComponent

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 278. 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 279. 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();
       }
   }

6.33. 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.

6.33.1. 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

6.33.2. 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.

6.33.3. 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 280. Line Charts
Cubic Line Charts
Figure 281. Cubic Line Charts
Bar Charts
Figure 282. Bar Charts
Stacked Bar Charts
Figure 283. Stacked Bar Charts
Range Bar Charts
Figure 284. Range Bar Charts
Pie Charts
Figure 285. Pie Charts
Doughnut Charts
Figure 286. Doughnut Charts
Scatter Charts
Figure 287. Scatter Charts
Dial Charts
Figure 288. Dial Charts
Combined Charts
Figure 289. Combined Charts
Bubble Charts
Figure 290. Bubble Charts
Time Charts
Figure 291. Time Charts
The above screenshots were taken from the ChartsDemo app. You can start playing with this app by checking it out from our git repository.

6.33.4. 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;

}

6.34. Calendar

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

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 292. The Calendar component

6.35. 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 293. 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 294. 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 295. ToastBar with a multiline message

6.35.1. 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();

6.36. 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 296. The signature Component

6.37. 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 297. The Accordion Component

6.38. 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 298. The FloatingHint component with one component that contains text and another that doesn’t

6.39. 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 299. 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.

6.39.1. 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 300. Badge floating button in action

6.40. 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 301. 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).

7. Animations

There are many ways to animate and liven the data within a Codename One application, layout animations are probably chief among them. But first we need to understand some basics such as layout reflows.

7.1. Layout Reflow

Layout in tools such as HTML is implicit, when you add something into the UI it is automatically placed correctly. Other tools such as Codename One use explicit layout, that means you have to explicitly request the UI to layout itself after making changes!

Like many such rules exceptions occur. E.g. if the device is rotated or window size changes a layout will occur automatically.

When adding a component to a UI that is already visible, the component will not show by default.

When adding a component to a form which isn’t shown on the screen, there is no need to tell the UI to repaint or reflow. This happens implicitly.

The chief advantage of explicit layout is performance.

E.g. imagine adding 100 components to a form. If the form was laid out automatically, layout would have happened 100 times instead of once when adding was finished. In fact layout reflows are often considered the #1 performance issue for HTML/JavaScript applications.

Smart layout reflow logic can alleviate some of the pains of the automatic layout reflows however since the process is implicit it’s almost impossible to optimize complex usages across browsers/devices. A major JavaScript performance tip is to use absolute positioning which is akin to not using layouts at all!

That is why, when you add components to a form that is already showing, you should invoke revalidate() or animate the layout appropriately. This also enables the layout animation behavior explained below.

7.2. Layout Animations

To understand animations you need to understand a couple of things about Codename One components. When we add a component to a container, it’s generally just added but not positioned anywhere. A novice might notice the setX/setY/setWidth/setHeight methods on a component and just try to position it absolutely.

This won’t work since these methods are meant for the layout manager, which is implicitly invoked when a form is shown (internally in Codename One). The layout manager uses these methods to position/size the components based on the hints given to it.

If you add components to a form that is currently showing, it is your responsibility to invoke revalidate/layoutContainer to arrange the newly added components (see Layout Reflows).

animateLayout() method is a fancy form of revalidate that animates the components into their laid out position. After changing the layout & invoking this method the components move to their new sizes/positions seamlessly.

This sort of behavior creates a special case where setting the size/position makes sense. When we set the size/position in the demo code here we are positioning the components at the animation start position above the frame.

Form hi = new Form("Layout Animations", new BoxLayout(BoxLayout.Y_AXIS));
Button fall = new Button("Fall"); (1)
fall.addActionListener((e) -> {
    for(int iter = 0 ; iter < 10 ; iter++) {
        Label b = new Label ("Label " + iter);
        b.setWidth(fall.getWidth());
        b.setHeight(fall.getHeight());
        b.setY(-fall.getHeight());
        hi.add(b);
    }
    hi.getContentPane().animateLayout(20000); (2)
});
hi.add(fall);

There are a couple of things that you should notice about this example:

1 We used a button to do the animation rather than doing it on show. Since show() implicitly lays out the components it wouldn’t have worked correctly.
2 We used hi.getContentPane().animateLayout(20000); & not hi.animateLayout(20000);. You need to animate the "actual" container and Form is a special case.

This results in:

Frame 1 Frame 2 Frame 3 Frame 4 Frame 5 Frame 6 Frame 7

7.2.1. Unlayout Animations

While layout animations are really powerful effects for adding elements into the UI and drawing attention to them. The inverse of removing an element from the UI is often more important. E.g. when we delete or remove an element we want to animate it out.

Layout animations don’t really do that since they will try to bring the animated item into place. What we want is the exact opposite of a layout animation and that is the "unlayout animation".

The "unlayout animation" takes a valid laid out state and shifts the components to an invalid state that we defined in advance. E.g. we can fix the example above to flip the "fall" button into a "rise" button when the buttons come into place and this will allow the buttons to float back up to where they came from in the exact reverse order.

An unlayout animation always leaves the container in an invalidated state.
Form hi = new Form("Layout Animations", new BoxLayout(BoxLayout.Y_AXIS));
Button fall = new Button("Fall");
fall.addActionListener((e) -> {
    if(hi.getContentPane().getComponentCount() == 1) {
        fall.setText("Rise");
        for(int iter = 0 ; iter < 10 ; iter++) {
            Label b = new Label ("Label " + iter);
            b.setWidth(fall.getWidth());
            b.setHeight(fall.getHeight());
            b.setY(-fall.getHeight());
            hi.add(b);
        }
        hi.getContentPane().animateLayout(20000);
    } else {
        fall.setText("Fall");
        for(int iter = 1 ; iter < hi.getContentPane().getComponentCount() ; iter++) { (1)
            Component c = hi.getContentPane().getComponentAt(iter);
            c.setY(-fall.getHeight()); (2)
        }
        hi.getContentPane().animateUnlayoutAndWait(20000, 255); (3)
        hi.removeAll(); (4)
        hi.add(fall);
        hi.revalidate();
    }
});
hi.add(fall);

You will notice some similarities with the unlayout animation but the differences represent the exact opposite of the layout animation:

1 We loop over existing components (not newly created ones)
2 We set the desired end position not the desired starting position
3 We used the AndWait variant of the animate unlayout call. We could have used the async call as well.
4 After the animation completes we need to actually remove the elements since the UI is now in an invalid position with elements outside of the screen but still physically there!

7.2.2. Hiding & Visibility

A common trick for animating Components in Codename One is to set their preferred size to 0 and then invoke animateLayout() thus triggering an animation to hide said Component. There are several issues with this trick but one of the biggest ones is the fact that setPreferredSize has been deprecated for quite a while.

Instead of using that trick you can use setHidden/isHidden who effectively encapsulate this functionality and a bit more.

One of the issues setHidden tries to solve is the fact that preferred size doesn’t include the margin in the total and thus a component might still occupy space despite being hidden. To solve this the margin is set to 0 when hiding and restored to its original value when showing the component again by resetting the UIID (which resets all style modifications).

This functionality might be undesirable which is why there is a version of the setHidden method that accepts a boolean flag indicating whether the margin/UIID should be manipulated. You can effectively hide/show a component without deprecated code using something like this:

Button toHide = new Button("Will Be Hidden");
Button hide = new Button("Hide It");
hide.addActionListener((e) -> {
    hide.setEnabled(false);
    boolean t = !toHide.isHidden();
    toHide.setHidden(t);
    toHide.getParent().animateLayoutAndWait(200);
    toHide.setVisible(!t);
    hide.setEnabled(true);
});
Notice that the code above uses setVisible(), which shouldn’t be confused with setHidden. setVisible() just toggles the visibility of the component it would still occupy the same amount of space

7.2.3. Synchronicity In Animations

Most animations have two or three variants:

  • Standard animation e.g. animateLayout(int)

  • And wait variant e.g. animateLayoutAndWait(int)

  • Callback variant e.g. animateUnlayout(int, int, Runnable)

The standard animation is invoked when we don’t care about the completion of the animation. We can do this for a standard animation.

The unlayout animations don’t have a standard variant. Since they leave the UI in an invalid state we must always do something once the animation completes so a standard variant makes no sense

The AndWait variant blocks the calling thread until the animation completes. This is really useful for sequencing animations one after the other e.g this code from the kitchen sink demo:

arrangeForInterlace(effects);
effects.animateUnlayoutAndWait(800, 20);
effects.animateLayoutFade(800, 20);

First the UI goes thru an "unlayout" animation, once that completes the layout itself is performed.

The AndWait calls needs to be invoked on the Event Dispatch Thread despite being "blocking". This is a common convention in Codename One powered by a unique capability of Codename One: invokeAndBlock.
You can learn more about invokeAndBlock in the EDT section.

The callback variant is similar to the invokeAndBlock variant but uses a more conventional callback semantic which is more familiar to some developers. It accepts a Runnable callback that will be invoked after the fact. E.g. we can change the unlayout call from before to use the callback semantics as such:

hi.getContentPane().animateUnlayout(20000, 255, () -> {
    hi.removeAll();
    hi.add(fall);
    hi.revalidate();
});
Animation Fade and Hierarchy

There are several additional variations on the standard animate methods. Several methods accept a numeric fade argument. This is useful to fade out an element in an "unlayout" operation or fade in a regular animation.

The value for the fade argument is a number between 0 and 255 where 0 represents full transparency and 255 represents full opacity.

Some animate layout methods are hierarchy based. They work just like the regular animateLayout methods but recurse into the entire Container hierarchy. These methods work well when you have components in a nested hierarchy that need to animate into place. This is demonstrated in the opening sequence of the kitchen sink demo:

for(int iter = 0 ; iter < demoComponents.size() ; iter++) {
    Component cmp = (Component)demoComponents.elementAt(iter);
    if(iter < componentsPerRow) {
        cmp.setX(-cmp.getWidth());
    } else {
        if(iter < componentsPerRow * 2) {
            cmp.setX(dw);
        } else {
            cmp.setX(-cmp.getWidth());
        }
    }
}
boxContainer.setShouldCalcPreferredSize(true);
boxContainer.animateHierarchyFade(3000, 30);

The demoComponents Vector contains components from separate containers and this code would not work with a simple animate layout.

We normally recommend avoiding the hierarchy version. Its slower but more importantly, it’s flaky. Since the size/position of the Container might be affected by the layout the animation could get clipped and skip. These are very hard issues to debug.

7.2.4. Sequencing Animations Via AnimationManager

All the animations go thru a per-form queue: the AnimationManager. This effectively prevents two animations from mutating the UI in parallel so we won’t have collisions between two conflicting sides. Things get more interesting when we try to do something like this:

cnt.add(myButton);
int componentCount = cnt.getComponentCount();
cnt.animateLayout(300);
cnt.removeComponent(myButton);
if(componentCount == cnt.getComponentCount()) {
    // this will happen...
}

The reason this happens is that the second remove gets postponed to the end of the animation so it won’t break the animation. This works for remove and add operations on a Container as well as other animations.

The simple yet problematic fix would be:

cnt.add(myButton);
int componentCount = cnt.getComponentCount();
cnt.animateLayoutAndWait(300);
cnt.removeComponent(myButton);
if(componentCount == cnt.getComponentCount()) {
    // this probably won't happen...
}

So why that might still fail?

Events come in constantly during the run of the EDT [7], so an event might come in that might trigger an animation in your code. Even if you are on the EDT keep in mind that you don’t actually block it and an event might come in.

In those cases an animation might start and you might be unaware of that animation and it might still be in action when you expect remove to work.

Animation Manager to the Rescue

AnimationManager has builtin support to fix this exact issue.

We can flush the animation queue and run synchronously after all the animations finished and before new ones come in by using something like this:

cnt.add(myButton);
int componentCount = cnt.getComponentCount();
cnt.animateLayout(300);
cnt.getAnimationManager().flushAnimation(() -> {
    cnt.removeComponent(myButton);
    if(componentCount == cnt.getComponentCount()) {
        // this shouldn't happen...
    }
});

7.3. Low Level Animations

The Codename One event dispatch thread has a special animation “pulse” allowing an animation to update its state and draw itself. Code can make use of this pulse to implement repetitive polling tasks that have very little to do with drawing.

This is helpful since the callback will always occur on the event dispatch thread.

Every component in Codename One contains an animate() method that returns a boolean value, you can also implement the Animation interface in an arbitrary component to implement your own animation. In order to receive animation events you need to register yourself within the parent form, it is the responsibility of the parent for to call animate().

If the animate method returns true then the animation will be painted (the paint method of the Animation interface would be invoked).

It is important to deregister animations when they aren’t needed to conserve battery life.

If you derive from a component, which has its own animation logic you might damage its animation behavior by deregistering it, so tread gently with the low level API’s.

E.g. you can add additional animation logic using code like this:

myForm.registerAnimated(this);

private int spinValue;
public boolean animate() {
   if(userStatusPending) {
       spinValue++;
       super.animate();
       return true;
   }
   return super.animate();
}

7.3.1. Why Not Just Write Code In Paint?

Animations are comprised of two parts, the logic (deciding the position etc) and the painting. The paint method should be dedicated to painting only, not to the actual moving of the components.

The separation of concerns allows us to avoid redundant painting e.g. if animate didn’t trigger a change just return false to avoid the overhead related to animations.

We discuss low level animations in more details within the animation section of the clock demo.

7.4. Transitions

Transitions allow us to replace one component with another, most typically forms or dialogs are replaced with a transition however a transition can be applied to replace any arbitrary component.

Developers can implement their own custom transition and install it to components by deriving the Transition class, although most commonly the built in CommonTransitions class is used for almost everything.

You can define transitions for forms/dialogs/menus globally either via the theme constants or via the LookAndFeel class. Alternatively you can install a transition on top-level components via setter methods.

In/Out Transitions

When defining a transition we define the entering transition and the exiting transition. For most cases only one of those is necessary and we default to the exiting (out transition) as a convention.

So for almost all cases the method setFormTransitonIn should go unused. That API exists for some elaborate custom transitions that might need to have a special effect both when transitioning in and out of a specific form. However, most of these effects are easier to achieve with layout animations (e.g. components dropping into place etc.).

In the case of Dialog the transition in shows its appearance and the transition out shows its disposal. So in that case both transitions make a lot of sense.

Back/Forward Transitions

Transitions have a direction and can all be played either in incoming or outgoing direction. A transition can be flipped (played in reverse) when we use an RTL language [8] or when we simply traverse backwards in the form navigation hierarchy.

Normally Form.show() displays the next Form with an incoming transition based on the current RTL mode. If we use Form.showBack() it will play the transition in reverse.

When working with high level animations you can select Slow Motion option in the simulator to slow down animations and inspect their details

Themes define the default transitions used when showing a form, these differ based on the OS. In most platforms the default is Slide whereas in iOS the default is SlideFade which slides the content pane and title while fading in/out the content of the title area.

SlideFade is problematic without a title area. If you have a Form that lacks a title area we would recommend to disable SlideFade at least for that Form.

Check out the full set of theme constants in the Theme Constants Section.

7.4.1. Replace

To apply a transition to a component we can just use the Container.replace() method as such:

Form hi = new Form("Replace", new BoxLayout(BoxLayout.Y_AXIS));
Button replace = new Button("Replace Pending");
Label replaceDestiny = new Label("Destination Replace");
hi.add(replace);
replace.addActionListener((e) -> {
    replace.getParent().replaceAndWait(replace, replaceDestiny, CommonTransitions.createCover(CommonTransitions.SLIDE_VERTICAL, true, 800));
    replaceDestiny.getParent().replaceAndWait(replaceDestiny, replace, CommonTransitions.createUncover(CommonTransitions.SLIDE_VERTICAL, true, 800));
});
Replace even works when you have a layout constraint in place e.g. replacing a component in a border layout will do the "right thing". However, some layouts such as TableLayout might be tricky in such cases so we recommend wrapping a potentially replaceable Component in a border layout and replacing the content.

Container.replace() can also be used with a null transition at which point it replaces instantly with no transition.

7.4.2. Slide Transitions

The slide transitions are used to move the Form/Component in a sliding motion to the side or up/down. There are 4 basic types of slide transitions:

  1. Slide - the most commonly used transition

  2. Fast Slide - historically this provided better performance for old device types. It is no longer recommended for newer devices

  3. Slide Fade - the iOS default where the title area features a fade transition

  4. Cover/Uncover - a type of slide transition where only the source or destination form slides while the other remains static in place

The code below demonstrates the usage of all the main transitions:

Toolbar.setGlobalToolbar(true);
Form hi = new Form("Transitions", new BoxLayout(BoxLayout.Y_AXIS));
Style bg = hi.getContentPane().getUnselectedStyle();
bg.setBgTransparency(255);
bg.setBgColor(0xff0000);
Button showTransition = new Button("Show");
Picker pick = new Picker();
pick.setStrings("Slide", "SlideFade", "Cover", "Uncover", "Fade", "Flip");
pick.setSelectedString("Slide");
TextField duration = new TextField("10000", "Duration", 6, TextArea.NUMERIC);
CheckBox horizontal = CheckBox.createToggle("Horizontal");
pick.addActionListener((e) -> {
    String s = pick.getSelectedString().toLowerCase();
    horizontal.setEnabled(s.equals("slide") || s.indexOf("cover") > -1);
});
horizontal.setSelected(true);
hi.add(showTransition).
    add(pick).
    add(duration).
    add(horizontal);

Form dest = new Form("Destination");
bg = dest.getContentPane().getUnselectedStyle();
bg.setBgTransparency(255);
bg.setBgColor(0xff);
dest.setBackCommand(
        dest.getToolbar().addCommandToLeftBar("Back", null, (e) -> hi.showBack()));

showTransition.addActionListener((e) -> {
    int h = CommonTransitions.SLIDE_HORIZONTAL;
    if(!horizontal.isSelected()) {
        h = CommonTransitions.SLIDE_VERTICAL;
    }
    switch(pick.getSelectedString()) {
        case "Slide":
            hi.setTransitionOutAnimator(CommonTransitions.createSlide(h, true, duration.getAsInt(3000)));
            dest.setTransitionOutAnimator(CommonTransitions.createSlide(h, true, duration.getAsInt(3000)));
            break;
        case "SlideFade":
            hi.setTransitionOutAnimator(CommonTransitions.createSlideFadeTitle(true, duration.getAsInt(3000)));
            dest.setTransitionOutAnimator(CommonTransitions.createSlideFadeTitle(true, duration.getAsInt(3000)));
            break;
        case "Cover":
            hi.setTransitionOutAnimator(CommonTransitions.createCover(h, true, duration.getAsInt(3000)));
            dest.setTransitionOutAnimator(CommonTransitions.createCover(h, true, duration.getAsInt(3000)));
            break;
        case "Uncover":
            hi.setTransitionOutAnimator(CommonTransitions.createUncover(h, true, duration.getAsInt(3000)));
            dest.setTransitionOutAnimator(CommonTransitions.createUncover(h, true, duration.getAsInt(3000)));
            break;
        case "Fade":
            hi.setTransitionOutAnimator(CommonTransitions.createFade(duration.getAsInt(3000)));
            dest.setTransitionOutAnimator(CommonTransitions.createFade(duration.getAsInt(3000)));
            break;
        case "Flip":
            hi.setTransitionOutAnimator(new FlipTransition(-1, duration.getAsInt(3000)));
            dest.setTransitionOutAnimator(new FlipTransition(-1, duration.getAsInt(3000)));
            break;
    }
    dest.show();
});
hi.show();
The slide transition moves both incoming and outgoing forms together
Figure 302. The slide transition moves both incoming and outgoing forms together
The slide transition can be applied vertically as well
Figure 303. The slide transition can be applied vertically as well
Slide fade fades in the destination title while sliding the content pane its the default on iOS
Figure 304. Slide fade fades in the destination title while sliding the content pane it is the default on iOS
SlideFade is problematic without a title area. If you have a Form that lacks a title area we would recommend to disable SlideFade at least for that Form.
With cover transitions the source form stays in place as it is covered by the destination. This transition can be played both horizontally and vertically
Figure 305. With cover transitions the source form stays in place as it is covered by the destination. This transition can be played both horizontally and vertically
Uncover is the inverse of cover. The destination form stays in place while the departing form moves away
Figure 306. Uncover is the inverse of cover. The destination form stays in place while the departing form moves away

7.4.3. Fade and Flip Transitions

The fade transition is pretty trivial and only accepts a time value since it has no directional context.

Fade transition is probably the simplest one around
Figure 307. Fade transition is probably the simplest one around

The FlipTransition is also pretty simple but unlike the others it isn’t a part of the CommonTransitions. It has its own FlipTransition class.

This transition looks very different on devices as it uses native perspective transforms available only there
Fade transition is probably the simplest one around
Figure 308. Fade transition is probably the simplest one around

7.4.4. Bubble Transition

BubbleTransiton morphs a component into another component using a circular growth motion.

The BubbleTransition accepts the component that will grow into the bubble effect as one of its arguments. It’s generally designed for Dialog transitions although it could work for more creative use cases:

The code below manipulates styles and look. This is done to make the code more "self contained". Real world code should probably use the theme
Form hi = new Form("Bubble");
Button showBubble = new Button("+");
showBubble.setName("BubbleButton");
Style buttonStyle = showBubble.getAllStyles();
buttonStyle.setBorder(Border.createEmpty());
buttonStyle.setFgColor(0xffffff);
buttonStyle.setBgPainter((g, rect) -> {
    g.setColor(0xff);
    int actualWidth = rect.getWidth();
    int actualHeight = rect.getHeight();
    int xPos, yPos;
    int size;
    if(actualWidth > actualHeight) {
        yPos = rect.getY();
        xPos = rect.getX() + (actualWidth - actualHeight) / 2;
        size = actualHeight;
    } else {
        yPos = rect.getY() + (actualHeight - actualWidth) / 2;
        xPos = rect.getX();
        size = actualWidth;
    }
    g.setAntiAliased(true);
    g.fillArc(xPos, yPos, size, size, 0, 360);
});
hi.add(showBubble);
hi.setTintColor(0);
showBubble.addActionListener((e) -> {
    Dialog dlg = new Dialog("Bubbled");
    dlg.setLayout(new BorderLayout());
    SpanLabel sl = new SpanLabel("This dialog should appear with a bubble transition from the button", "DialogBody");
    sl.getTextUnselectedStyle().setFgColor(0xffffff);
    dlg.add(BorderLayout.CENTER, sl);
    dlg.setTransitionInAnimator(new BubbleTransition(500, "BubbleButton"));
    dlg.setTransitionOutAnimator(new BubbleTransition(500, "BubbleButton"));
    dlg.setDisposeWhenPointerOutOfBounds(true);
    dlg.getTitleStyle().setFgColor(0xffffff);

    Style dlgStyle = dlg.getDialogStyle();
    dlgStyle.setBorder(Border.createEmpty());
    dlgStyle.setBgColor(0xff);
    dlgStyle.setBgTransparency(0xff);
    dlg.showPacked(BorderLayout.NORTH, true);
});

hi.show();
Bubble transition converting a circular button to a Dialog
Figure 309. Bubble transition converting a circular button to a Dialog

7.4.5. Morph Transitions

Android’s material design has a morphing effect where an element from the previous form (activity) animates into a different component on a new activity. Codename One has a morph effect in the Container class but it doesn’t work as a transition between forms and doesn’t allow for multiple separate components to transition at once.

Morph Transition
Figure 310. Morph Transition

To support this behavior we have the MorphTransition class that provides this same effect coupled with a fade to the rest of the UI (see Figure 310, “Morph Transition”).

Since the transition is created before the form exists we can’t reference explicit components within the form when creating the morph transition (in order to indicate which component becomes which) so we need to refer to them by name. This means we need to use setName(String) on the components in the source/destination forms so the transition will be able to find them.

Form demoForm = new Form(currentDemo.getDisplayName());
demoForm.setScrollable(false);
demoForm.setLayout(new BorderLayout());
Label demoLabel = new Label(currentDemo.getDisplayName());
demoLabel.setIcon(currentDemo.getDemoIcon());
demoLabel.setName("DemoLabel");
demoForm.addComponent(BorderLayout.NORTH, demoLabel);
demoForm.addComponent(BorderLayout.CENTER, wrapInShelves(n));
....
demoForm.setBackCommand(backCommand);
demoForm.setTransitionOutAnimator(
    MorphTransition.create(3000).morph(
        currentDemo.getDisplayName(),
        "DemoLabel"));
f.setTransitionOutAnimator(
    MorphTransition.create(3000).
        morph(currentDemo.getDisplayName(),
        "DemoLabel"));
demoForm.show();

7.4.6. SwipeBackSupport

iOS7+ allows swiping back one form to the previous form, Codenmae One has an API to enable back swipe transition:

SwipeBackSupport.bindBack(Form currentForm, LazyValue<Form> destination);

That one command will enable swiping back from currentForm. LazyValue allows us to pass a value lazily:

public interface LazyValue<T> {
    public T get(Object... args);
}

This effectively allows us to pass a form and only create it as necessary (e.g. for a GUI builder app we don’t have the actual previous form instance), notice that the arguments aren’t used for this case but will be used in other cases.

The code below should work for the transition sample above. Notice that this API was designed to work with "Slide Fade" transition and might have issues with other transition types:

SwipeBackSupport.bindBack(dest, (args) -> hi);

8. The EDT - Event Dispatch Thread

8.1. What Is The EDT

Codename One allows developers to create as many threads as they want; however in order to interact with the Codename One user interface components a developer must use the EDT. The EDT stands for "Event Dispatch Thread" but it handles a lot more than just "events".

The EDT is the main thread of Codename One, by using just one thread Codename One can avoid complex synchronization code and focus on simple functionality that assumes only one thread.

This has huge advantages for your code. You can normally assume that all code will occur on a single thread and avoid complex synchronization logic.

You can visualize the EDT as a loop such as this:

while(codenameOneRunning) {
     performEventCallbacks();
     performCallSeriallyCalls();
     drawGraphicsAndAnimations();
     sleepUntilNextEDTCycle();
}

Normally, every call you receive from Codename One will occur on the EDT. E.g. every event, calls to paint(), lifecycle calls (start etc.) should all occur on the EDT.

This is pretty powerful, however it means that as long as your code is processing nothing else can happen in Codename One!

If your code takes too long to execute then no painting or event processing will occur during that time, so a call to Thread.sleep() will actually stop everything!

The solution is pretty simple, if you need to perform something that requires intensive CPU you can spawn a thread.

Codename One’s networking code automatically spawns its own network thread (see the NetworkManager). However, this also poses a problem…​

Codename One assumes all modifications to the UI are performed on the EDT but if we spawned a separate thread. How do we force our modifications back into the EDT?

Codename One includes 3 methods in the Display class to help in these situations: isEDT(), callSerially(Runnable) & callSeriallyAndWait(Runnable).

isEDT() is useful for generic code that needs to test whether the current code is executing on the EDT.

8.2. Call Serially (And Wait)

callSerially(Runnable) should normally be called off the EDT (in a separate thread), the run method within the submitted runnable will be invoked on the EDT.

The Runnable passed to the callSerially and callSeriallyAndWait methods is not a Thread. We just use the Runnable interface as a convenient callback interface.
// this code is executing in a separate thread
final String res = methodThatTakesALongTime();
Display.getInstance().callSerially(new Runnable() {
     public void run() {
          // this occurs on the EDT so I can make changes to UI components
          resultLabel.setText(res);
     }
});
You can write this code more concisely using Java 8 lambda code as such:
// this code is executing in a separate thread
String res = methodThatTakesALongTime();
Display.getInstance().callSerially(() -> resultLabel.setText(res));

This allows code to leave the EDT and then later on return to it to perform things within the EDT.

The callSeriallyAndWait(Runnable) method blocks the current thread until the method completes, this is useful for cases such as user notification e.g.:

// this code is executing in a separate thread
methodThatTakesALongTime();
Display.getInstance().callSeriallyAndWait(() -> {
  // this occurs on the EDT so I can make changes to UI components
  globalFlag = Dialog.show("Are You Sure?", "Do you want to continue?", "Continue", "Stop");
});
// this code is executing the separate thread
// global flag was already set by the call above
if(!globalFlag) {
   return;
}
otherMethod();
If you are unsure use callSerially. The use cases for callSeriallyAndWait are very rare.

8.2.1. callSerially On The EDT

One of the misunderstood topics is why would we ever want to invoke callSerially when we are still on the EDT. This is best explained by example. Say we have a button that has quite a bit of functionality tied to its events e.g.:

  1. A user added an action listener to show a Dialog.

  2. A framework the user installed added some logging to the button.

  3. The button repaints a release animation as its being released.

However, this might cause a problem if the first event that we handle (the dialog) might cause an issue to the following events. E.g. a dialog will block the EDT (using invokeAndBlock), events will keep happening but since the event we are in "already happened" the button repaint and the framework logging won’t occur. This might also happen if we show a form which might trigger logic that relies on the current form still being present.

One of the solutions to this problem is to just wrap the action listeners body with a callSerially. In this case the callSerially will postpone the event to the next cycle (loop) of the EDT and let the other events in the chain complete. Notice that you shouldn’t use this normally since it includes an overhead and complicates application flow, however when you run into issues in event processing we suggest trying this to see if its the cause.

You should never invoke callSeriallyAndWait on the EDT since this would effectively mean sleeping on the EDT. We made that method throw an exception if its invoked from the EDT.

8.3. Debugging EDT Violations

There are two types of EDT violations:

  1. Blocking the EDT thread so the UI performance is considerably slower.

  2. Invoking UI code on a separate thread

Codename One provides a tool to help you detect some of these violations some caveats may apply though…

It’s an imperfect tool. It might fire “false positives” meaning it might detect a violation for perfectly legal code and it might miss some illegal calls. However, it is a valuable tool in the process of detecting hard to track bugs that are sometimes only reproducible on the devices (due to race condition behavior).

To activate this tool just select the Debug EDT menu option in the simulator and pick the level of output you wish to receive:

Debug EDT
Figure 311. Debug EDT

Full output will include stack traces to the area in the code that is suspected in the violation.

8.4. Invoke And Block

Invoke and block is the exact opposite of callSeriallyAndWait(), it blocks the EDT and opens a separate thread for the runnable call. This functionality is inspired by the Foxtrot API, which is a remarkably powerful tool most Swing developers don’t know about.

This is best explained by an example. When we write typical code in Java we like that code is in sequence as such:

doOperationA();
doOperationB();
doOperationC();

This works well normally but on the EDT it might be a problem, if one of the operations is slow it might slow the whole EDT (painting, event processing etc.). Normally we can just move operations into a separate thread e.g.:

doOperationA();
new Thread() {
    public void run() {
         doOperationB();
    }
}).start();
doOperationC();

Unfortunately, this means that operation C will happen in parallel to operation B which might be a problem…​
E.g. instead of using operation names lets use a more "real world" example:

updateUIToLoadingStatus();
readAndParseFile();
updateUIWithContentOfFile();

Notice that the first and last operations must be conducted on the EDT but the middle operation might be really slow! Since updateUIWithContentOfFile needs readAndParseFile to occur before it starts doing the new thread won’t be enough.

A simplistic approach is to do something like this:

updateUIToLoadingStatus();
new Thread() {
    public void run() {
          readAndParseFile();
          updateUIWithContentOfFile();
    }
}).start();

But updateUIWithContentOfFile should be executed on the EDT and not on a random thread. So the right way to do this would be something like this:

updateUIToLoadingStatus();
new Thread() {
    public void run() {
          readAndParseFile();
          Display.getInstance().callSerially(new Runnable() {
               public void run() {
                     updateUIWithContentOfFile();
               }
          });
    }
}).start();

This is perfectly legal and would work reasonably well, however it gets complicated as we add more and more features that need to be chained serially after all these are just 3 methods!

Invoke and block solves this in a unique way you can get almost the exact same behavior by using this:

updateUIToLoadingStatus();
Display.getInstance().invokeAndBlock(new Runnable() {
    public void run() {
          readAndParseFile();
    }
});
updateUIWithContentOfFile();

Or this with Java 8 syntax:

updateUIToLoadingStatus();
Display.getInstance().invokeAndBlock(() -> readAndParseFile());
updateUIWithContentOfFile();

Invoke and block effectively blocks the current EDT in a legal way. It spawns a separate thread that runs the run() method and when that run method completes it goes back to the EDT.

All events and EDT behavior still work while invokeAndBlock is running, this is because invokeAndBlock() keeps calling the main thread loop internally.

Notice that invokeAndBlock comes at a slight performance penalty. Also notice that nesting invokeAndBlock calls (or over using them) isn’t recommended.
However, they are very convenient when working with multiple threads/UI.

Even if you never call invokeAndBlock directly you are probably using it indirectly in API’s such as Dialog that show a dialog while blocking the current thread e.g.:

public void actionPerformed(ActionEvent ev) {
  // will return true if the user clicks "OK"
  if(!Dialog.show("Question", "How Are You", "OK", "Not OK")) {
  // ask what went wrong...
  }
}

Notice that the dialog show method will block the calling thread until the user clicks OK or Not OK…​

Other API’s such as NetworkManager.addToQueueAndWait() also make use of this feature. Pretty much every "AndWait" method or blocking method uses this API internally!

To explain how invokeAndBlock works we can return to the sample above of how the EDT works:

while(codenameOneRunning) {
     performEventCallbacks();
     performCallSeriallyCalls();
     drawGraphicsAndAnimations();
     sleepUntilNextEDTCycle();
}

invokeAndBlock() works in a similar way to this pseudo code:

void invokeAndBlock(Runnable r) {
    openThreadForR(r);
    while(r is still running) {
         performEventCallbacks();
         performCallSeriallyCalls();
         drawGraphicsAndAnimations();
         sleepUntilNextEDTCycle();
    }
}

So the EDT is effectively "blocked" but we "redo it" within the invokeAndBlock method…​

As you can see this is a very simple approach for thread programming in UI, you don’t need to block your flow and track the UI thread. You can just program in a way that seems sequential (top to bottom) but really uses multi-threading correctly without blocking the EDT.

9. Monetization

Codename One tries to make the lives of software developers easier by integrating several forms of built-in monetization solutions such as ad network support, in-app-purchase etc.

A lot of the monetization options are available as 3rd party cn1lib’s that you can install thru the Codename One website.

9.1. Google Play Ads

The most effective network is the simplest banner ad support. To enable mobile ads just create an ad unit in Admob’s website, you should end up with the key similar to this:

ca-app-pub-8610616152754010/3413603324

To enable this for Android just define the android.googleAdUnitId=ca-app-pub-8610616152754010/3413603324 in the build arguments and for iOS use the same as in ios.googleAdUnitId. The rest is seamless, the right ad will be created for you at the bottom of the screen and the form should automatically shrink to fit the ad. This shrinking is implemented differently between iOS and Android due to some constraints but the result should be similar and this should work reasonably well with device rotation as well.

Google play ads
Figure 312. Google play ads

There’s a special ad unit id to use for test ads. If you specify ca-app-pub-3940256099942544/6300978111, you’ll get test ads for your development phase. This is important because you’re not allowed to click on your own ads. When it’s time to create a production release, you should replace this with the real value you generated in adMob.

9.2. In App Purchase

In-app purchase is a helpful tool for making app development profitable. Codename One supports in-app purchases of consumable/non-consumable products on Android & iOS. It also features support for subscriptions. For such a seemingly simple task, in-app purchase involves a lot of moving parts - especially when it comes to subscriptions.

9.2.1. The SKU

In-app purchase support centers around your set of SKUs that you want to sell. Each product that you sell, whether it be a 1-month subscription, an upgrade to the "Pro" version, "10 disco credits", will have a SKU (stock-keeping-unit). Ideally you will be able to use the same SKU across all the stores that you sell your app in.

9.2.2. Types of Products

There are generally 4 classifications for products:

  1. Non-consumable Product - This is a product that the user purchases once to "own". They cannot re-purchase it. One example is a product that upgrades your app to a "Pro" version.

  2. Consumable Product - This is a product that the user can buy more than once. E.g. You might have a product for "10 Credits" that allows the user to buy items in a game.

  3. Non-Renewable Subscription - A subscription that you buy once, and will not be "auto-renewed" by the app store. These are almost identical to consumable products, except that subscriptions need to be transferable across all the user’s devices. This means that non-renewable subscriptions require that you have a server that keeps track of the subscriptions.

  4. Renewable Subscriptions - A subscription that the app store manages. The user will be automatically billed when the subscription period ends, and the subscription will renew.

These subscription categories may not be explicitly supported by a given store, or they may carry a different name. You can integrate each product type in a cross platform way using Codename One. E.g. In Google Play there is no distinction between consumable products and non-renewable subscriptions, but in iTunes there is a distinction.

9.2.3. The "Hello World" of In-App Purchase

Let’s start with a simple example of an app that sells "Worlds". The first thing we do is pick the SKU for our product. I’ll choose "com.codename1.world" for the SKU.

public static final String SKU_WORLD = "com.codename1.world";
While we chose to use the package name convention for an SKU you can use any name you want e.g UA8879

Next, our app’s main class needs to implement the PurchaseCallback interface

public class HelloWorldIAP implements PurchaseCallback {
    ....

    @Override
    public void itemPurchased(String sku) {
        ...
    }

    @Override
    public void itemPurchaseError(String sku, String errorMessage) {
        ...
    }

    @Override
    public void itemRefunded(String sku) {
        ...
    }

    @Override
    public void subscriptionStarted(String sku) {
        ...
    }

    @Override
    public void subscriptionCanceled(String sku) {
        ...
    }

    @Override
    public void paymentFailed(String paymentCode, String failureReason) {
       ...
    }

    @Override
    public void paymentSucceeded(String paymentCode, double amount, String currency) {
        ...
    }

}

Using these callbacks, we’ll be notified whenever something changes in our purchases. For our simple app we’re only interested in itemPurchased() and itemPurchaseError().

Now in the start method, we’ll add a button that allows the user to buy the world:

    public void start() {
        if(current != null){
            current.show();
            return;
        }
        Form hi = new Form("Hi World");
        Button buyWorld = new Button("Buy World");
        buyWorld.addActionListener(e->{
            if (Purchase.getInAppPurchase().wasPurchased(SKU_WORLD)) {
                Dialog.show("Can't Buy It", "You already Own It", "OK", null);
            } else {
                Purchase.getInAppPurchase().purchase(SKU_WORLD);
            }
        });

        hi.addComponent(buyWorld);
        hi.show();
    }

At this point, we already have a functional app that will track the sale of the world. To make it more interesting, let’s add some feedback with the ToastBar to show when the purchase completes.

    @Override
    public void itemPurchased(String sku) {
        ToastBar.showMessage("Thanks.  You now own the world", FontImage.MATERIAL_THUMB_UP);
    }

    @Override
    public void itemPurchaseError(String sku, String errorMessage) {
        ToastBar.showErrorMessage("Failure occurred: "+errorMessage);
    }
You can test out this code in the simulator without doing any additional setup and it will work. If you want the code to work properly on Android and iOS, you’ll need to set up the app and in-app purchase settings in the Google Play and iTunes stores respectively as explained below

When the app first opens we see our button:

In-app purchase demo app
Figure 313. In-app purchase demo app

In the simulator, clicking on the "Buy World" button will bring up a prompt to ask you if you want to approve the purchase.

Approving the purchase in the simulator
Figure 314. Approving the purchase in the simulator

Now if I try to buy the product again, it pops up the dialog to let me know that I already own it.

In App purchase already owned
Figure 315. In App purchase already owned

9.2.4. Making it Consumable

In the "Buy World" example above, the "world" product was non-consumable, since we could only buy the world once. We could change it to a consumable product by disregarding whether it was purchased before & keeping track of how many times it had been purchased.

We’ll use storage to keep track of the number of worlds that the user purchased. We need two methods to manage this count. One method gets the number of worlds that we own, and another adds a world to this count.

private static final String NUM_WORLDS_KEY = "NUM_WORLDS.dat";
public int getNumWorlds() {
    synchronized (NUM_WORLDS_KEY) {
        Storage s = Storage.getInstance();
        if (s.exists(NUM_WORLDS_KEY)) {
            return (Integer)s.readObject(NUM_WORLDS_KEY);
        } else {
            return 0;
        }
    }
}

public void addWorld() {
    synchronized (NUM_WORLDS_KEY) {
        Storage s = Storage.getInstance();
        int count = 0;
        if (s.exists(NUM_WORLDS_KEY)) {
            count = (Integer)s.readObject(NUM_WORLDS_KEY);
        }
        count++;
        s.writeObject(NUM_WORLDS_KEY, new Integer(count));
    }
}

Now we’ll change our purchase code as follows:

buyWorld.addActionListener(e->{
    if (Dialog.show("Confirm", "You own "+getNumWorlds()+
           " worlds.  Do you want to buy another one?", "Yes", "No")) {
        Purchase.getInAppPurchase().purchase(SKU_WORLD);
    }
});

And our itemPurchased() callback will need to add a world:

@Override
public void itemPurchased(String sku) {
    addWorld();
    ToastBar.showMessage("Thanks.  You now own "+getNumWorlds()+" worlds", FontImage.MATERIAL_THUMB_UP);
}
When we set up the products in the iTunes store we will need to mark the product as a consumable product or iTunes will prevent us from purchasing it more than once

9.2.5. Non-Renewable Subscriptions

As we discussed before, there are two types of subscriptions:

  1. Non-renewable

  2. Auto-renewable

Non-renewable subscriptions are the same as consumable products, except that they are shareable across devices. Auto-renewable subscriptions will continue as long as the user doesn’t cancel the subscription. They will be re-billed automatically by the appropriate app-store when the chosen period expires, and the app-store handles the management details itself.

The concept of an "Non-renewable" subscription is unique to iTunes. Google Play has no formal similar option. In order to create a non-renewable subscription SKU that behaves the same in your iOS and Android apps you would create it as a regular product in Google play, and a Non-renewable subscription in the iTunes store. We’ll learn more about that in a later post when we go into the specifics of app store setup.
The Purchase class includes both a purchase() method and a subscribe() method. On some platforms it makes no difference which one you use, but on Android it matters. If the product is set up as a subscription in Google Play, then you must use subscribe() to purchase the product. If it is set up as a regular product, then you must use purchase(). Since we enter "Non-renewable" subscriptions as regular products in the play store, we would use the purchase() method.

9.2.6. The Server-Side

Since a subscription purchased on one user device needs to be available across the user’s devices (Apple’s rules for non-renewable subscriptions), our app will need to have a server-component. In this section, we’ll gloss over that & "mock" the server interface. We’ll go into the specifics of the server-side below.

The Receipts API

Subscriptions, in Codename One use the "Receipts" API. It’s up to you to register a receipt store with the In-App purchase instance, which allows Codename one to load receipts (from your server), and submit new receipts to your server. A Receipt includes information such as:

  1. Store code (since you may be dealing with receipts from itunes, google play & Microsoft)

  2. SKU

  3. Transaction ID (store specific)

  4. Expiry Date

  5. Cancellation date

  6. Purchase date

  7. Order Data (that you can use on the server-side to verify the receipt and load receipt details directly from the store it originated from).

The Purchase provides a set of methods for interacting with the receipt store, such as:

  1. isSubscribed([skus]) - Checks to see if the user is currently subscribed to any of the provided skus.

  2. getExpiryDate([skus]) - Checks the expiry date for a set of skus.

  3. synchronizeReceipts() - Synchronizes the receipts with the receipt store. This will attempt to submit any pending purchase receipts to the receipt store, and the reload receipts from the receipt store.

In order for any of this to work, you must implement the ReceiptStore interface, and register it with the Purchase instance. Your receipt store must implement two methods:

  1. fetchReceipts(SuccessCallback<Receipt[]> callback) - Loads all of the receipts from your receipt store for the current user.

  2. submitReceipt(Receipt receipt, SuccessCallback<Boolean> callback) - Submits a receipt to your receipt store. This gives you an opportunity to add details to the receipt such as an expiry date.

9.2.7. The "Hello World" of Non-Renewable Subscriptions

We’ll expand on the theme of "Buying" the world for this app, except, this time we will "Rent" the world for a period of time. We’ll have two products:

  1. A 1 month subscription

  2. A 1 year subscription

public static final String SKU_WORLD_1_MONTH = "com.codename1.world.subscribe.1month";
public static final String SKU_WORLD_1_YEAR = "com.codename1.world.subscribe.1year";

public static final String[] PRODUCTS = {
    SKU_WORLD_1_MONTH,
    SKU_WORLD_1_YEAR
};

Notice that we create two separate SKUs for the 1 month and 1 year subscription. Each subscription period must have its own SKU. I have created an array (PRODUCTS) that contains both of the SKUs. This is handy, as you’ll see in the examples ahead, because the APIs for checking status and expiry date of a subscription take the SKUs in a "subscription group" as input.

Different SKUs that sell the same service/product but for different periods form a "subscription group". Conceptually, customers are not subscribing to a particular SKU, they are subscribing to the subscription group of which that SKU is a member. As an example, if a user purchases a 1 month subscription to "the world", they are actually subscribing to "the world" subscription group.

It’s up to you to know the grouping of your SKUs. Any methods in the Purchase class that check subscription status or expiry date of a SKU should be passed all SKUs of that subscription group. E.g. If you want to know if the user is subscribed to the SKU_WORLD_1_MONTH subscription, it would not be sufficient to call iap.isSubscribed(SKU_WORLD_1_MONTH), because that wouldn’t take into account if the user had purchased a 1 year subscription. The correct way is to always call iap.isSubscribed(SKU_WORLD_1_MONTH, SKU_WORLD_1_YEAR), or simply iap.isSubscribed(PRODUCTS) since we have placed both SKUs into our PRODUCTS array.

Implementing the Receipt Store
The receipt store is intended to interface with a server so that the subscriptions can be synced with multiple devices, as required by Apple’s guidelines. For this post we’ll just store our receipts on device using internal storage. Moving the logic to a server is a simple matter that we will cover in a future post when we cover the server-side.
The Receipt store is a layer between your server and Codename One
Figure 316. The Receipt store is a layer between your server and Codename One

A basic receipt store needs to implement just two methods:

  1. fetchReceipts

  2. submitReceipt

Generally we’ll register it in our app’s init() method so that it’s always available.

public void init(Object context) {
    ...

    Purchase.getInAppPurchase().setReceiptStore(new ReceiptStore() {

        @Override
        public void fetchReceipts(SuccessCallback<Receipt[]> callback) {
            // Fetch receipts from storage and pass them to the callback
        }

        @Override
        public void submitReceipt(Receipt receipt, SuccessCallback<Boolean> callback) {
            // Save a receipt to storage.  Make sure to call callback when done.
        }
    });
}

These methods are designed to be asynchronous since real-world apps will always be connecting to some sort of network service. Therefore, instead of returning a value, both of these methods are passed instances of the SuccessCallback class. It’s important to make sure to call callback.onSuccess() ALWAYS when the methods have completed, even if there is an error, or the Purchase class will just assume that you’re taking a long time to complete the task, and will continue to wait for you to finish.

Once implemented, our fetchReceipts() method will look like:

// static declarations used by receipt store

// Storage key where list of receipts are stored
private static final String RECEIPTS_KEY = "RECEIPTS.dat";

@Override
public void fetchReceipts(SuccessCallback<Receipt[]> callback) {
    Storage s = Storage.getInstance();
    Receipt[] found;
    synchronized(RECEIPTS_KEY) {
        if (s.exists(RECEIPTS_KEY)) {
            List<Receipt> receipts = (List<Receipt>)s.readObject(RECEIPTS_KEY);
            found = receipts.toArray(new Receipt[receipts.size()]);
        } else {
            found = new Receipt[0];
        }
    }
    // Make sure this is outside the synchronized block
    callback.onSucess(found);
}

This is fairly straight forward. We’re checking to see if we already have a list of receipts stored. If so we return that list to the callback. If not we return an empty array of receipts.

Receipt implements Externalizable so you are able to write instances directly to Storage.

The submitReceipt() method is a little more complex, as it needs to calculate the new expiry date for our subscription.

@Override
public void submitReceipt(Receipt receipt, SuccessCallback<Boolean> callback) {
    Storage s = Storage.getInstance();
    synchronized(RECEIPTS_KEY) {
        List<Receipt> receipts;
        if (s.exists(RECEIPTS_KEY)) {
            receipts = (List<Receipt>)s.readObject(RECEIPTS_KEY);
        } else {
            receipts = new ArrayList<Receipt>();
        }
        // Check to see if this receipt already exists
        // This probably won't ever happen (that we'll be asked to submit an
        // existing receipt, but better safe than sorry
        for (Receipt r : receipts) {
            if (r.getStoreCode().equals(receipt.getStoreCode()) &&
                    r.getTransactionId().equals(receipt.getTransactionId())) {
                // If we've already got this receipt, we'll just this submission.
                return;
            }
        }

        // Now try to find the current expiry date
        Date currExpiry = new Date();
        List<String> lProducts = Arrays.asList(PRODUCTS);
        for (Receipt r : receipts) {
            if (!lProducts.contains(receipt.getSku())) {
                continue;
            }
            if (r.getCancellationDate() != null) {
                continue;
            }
            if (r.getExpiryDate() == null) {
                continue;
            }
            if (r.getExpiryDate().getTime() > currExpiry.getTime()) {
                currExpiry = r.getExpiryDate();
            }
        }

        // Now set the appropriate expiry date by adding time onto
        // the end of the current expiry date
        Calendar cal = Calendar.getInstance();
        cal.setTime(currExpiry);
        switch (receipt.getSku()) {
            case SKU_WORLD_1_MONTH:
                cal.add(Calendar.MONTH, 1);
                break;
            case SKU_WORLD_1_YEAR:
                cal.add(Calendar.YEAR, 1);
        }
        Date newExpiry = cal.getTime();

        receipt.setExpiryDate(newExpiry);
        receipts.add(receipt);
        s.writeObject(RECEIPTS_KEY, receipts);

    }
    // Make sure this is outside the synchronized block
    callback.onSucess(Boolean.TRUE);
}

The main logic of this method involves iterating through all of the existing receipts to find the latest current expiry date, so that when the user purchases a subscription, it’s added onto the end of the current subscription (if one exists) rather than going from today’s date. This enables users to safely renew their subscription before the subscription has expired.

In the real-world, we would implement this logic on the server-side.

The iTunes store and Play store have no knowledge of your subscription durations. This is why it’s up to you to set the expiry date in the submitReceipt method. Non-renewable subscriptions are essentially no different than regular consumable products. It’s up to you to manage the subscription logic - and Apple, in particular, requires you to do so using a server.
Synchronizing Receipts

In order for your app to provide you with current data about the user’s subscriptions and expiry dates, you need to synchronize the receipts with your receipt store. Purchase provides a set of methods for doing this. Generally I’ll call one of them inside the start() method, and I may resynchronize at other strategic times if I suspect that the information may have changed.

The following methods can be used for synchronization:

  1. synchronizeReceipts() - Asynchronously synchronizes receipts in the background. You won’t be notified when it’s complete.

  2. synchronizeReceiptsSync() - Synchronously synchronizes receipts, and blocks until it’s complete. This is safe to use on the EDT as it employs invokeAndBlock under the covers.

  3. synchronizeReceipts(final long ifOlderThanMs, final SuccessCallback<Boolean> callback) - Asynchronously synchronizes receipts, but only if they haven’t been synchronized in the specified time period. E.g. In your start() method you might decide that you only want to synchronize receipts once per day. This also includes a callback that will be called when synchronization is complete.

  4. synchronizeReceiptsSync(long ifOlderThanMs) - A synchronous version that will only refetch if data is older than given time.

In our hello world app we synchronize the subscriptions in a few places.

At the end of the start() method:

public void start() {

   ...

    // Now synchronize the receipts
    iap.synchronizeReceipts(0, res->{
        // Update the UI as necessary to reflect

    });
}

And we also provide a button to allow the user to manually synchronize the receipts.

Button syncReceipts = new Button("Synchronize Receipts");

syncReceipts.addActionListener(e->{

    iap.synchronizeReceipts(0, res->{
        // Update the UI
    });
});
Expiry Dates and Subscription Status

Now that we have a receipt store registered, and we have synchronized our receipts, we can query the Purchase instance to see if a SKU or set of SKUs is currently subscribed. There are three useful methods in this realm:

  1. boolean isSubscribed(String…​ skus) - Checks to see if the user is currently subscribed to any of the provided SKUs.

  2. Date getExpiryDate(String…​ skus) - Gets the latest expiry date of a set of SKUs.

  3. Receipt getFirstReceiptExpiringAfter(Date dt, String…​ skus) - This method will return the earliest receipt with an expiry date after the given date. This is needed in cases where you need to decide if the user should have access to some content based on its publication date. E.g. If you published an issue of your e-zine on March 1, and the user purchased a subscription on March 15th, then they should get access to the March 1st issue even though it doesn’t necessarily fall in the subscription period. Being able to easily fetch the first receipt after a given date makes it easier to determine if a particular issue should be covered by a subscription.

If you need to know more information about subscriptions, you can always just call getReceipts() to obtain a list of all of the current receipts and determine for yourself what the user should have access to.

In the hello world app we’ll use this information in a few different places. On our main form we’ll include a label to show the current expiry date, and we allow the user to press a button to synchronize receipts manually if they think the value is out of date.

// ...

SpanLabel rentalStatus = new SpanLabel("Loading rental details...");
Button syncReceipts = new Button("Synchronize Receipts");

syncReceipts.addActionListener(e->{

    iap.synchronizeReceipts(0, res->{
        if (iap.isSubscribed(PRODUCTS)) {
            rentalStatus.setText("World rental expires "+iap.getExpiryDate(PRODUCTS));
        } else {
            rentalStatus.setText("You don't currently have a subscription to the world");
        }
        hi.revalidate();
    });
});
Allowing the User to Purchase the Subscription

You should now have all of the background required to implement the Hello World Subscription app. So we’ll return to the code and see how the user purchases a subscription.

In the main form, we want two buttons to subscribe to the "World", for one month and one year respectively. They look like:

Purchase iap = Purchase.getInAppPurchase();
// ...
Button rentWorld1M = new Button("Rent World 1 Month");
rentWorld1M.addActionListener(e->{
    String msg = null;
    if (iap.isSubscribed(PRODUCTS)) {  (1)
        msg = "You are already renting the world until "
             +iap.getExpiryDate(PRODUCTS)  (2)
             +".  Extend it for one more month?";
    } else {
        msg = "Rent the world for 1 month?";
    }
    if (Dialog.show("Confirm", msg, "Yes", "No")) {
        Purchase.getInAppPurchase().purchase(SKU_WORLD_1_MONTH); (3)
        // Note: since this is a non-renewable subscription it is just a regular
        // product in the play store - therefore we use the purchase() method.
        // If it were a "subscription" product in the play store, then we
        // would use subscribe() instead.
    }
});

Button rentWorld1Y = new Button("Rent World 1 Year");
rentWorld1Y.addActionListener(e->{
    String msg = null;
    if (iap.isSubscribed(PRODUCTS)) {
        msg = "You are already renting the world until "+
               iap.getExpiryDate(PRODUCTS)+
              ".  Extend it for one more year?";
    } else {
        msg = "Rent the world for 1 year?";
    }
    if (Dialog.show("Confirm", msg, "Yes", "No")) {
        Purchase.getInAppPurchase().purchase(SKU_WORLD_1_YEAR);
        // Note: since this is a non-renewable subscription it is just a regular
        // product in the play store - therefore we use the purchase() method.
        // If it were a "subscription" product in the play store, then we
        // would use subscribe() instead.
    }
});
1 In the event handler we check if the user is subscribed by calling isSubscribed(PRODUCTS). Notice that we check it against the array of both the one month and one year subscription SKUs.
2 We are able to tell the user when the current expiry date is so that they can gauge whether to proceed.
3 Since this is a non-renewable subscription, we use the Purchase.purchase() method. See following note about subscribe() vs purchase()

9.2.8. subscribe() vs purchase()

The Purchase class includes two methods for initiating a purchase:

  1. purchase(sku)

  2. subscribe(sku)

Which one you use depends on the type of product that is being purchased. If your product is set up as a subscription in the Google Play store, then you should use subscribe(sku). Otherwise, you should use purchase(sku).

Handling Purchase Callbacks

The purchase callbacks are very similar to the ones that we implemented in the regular in-app purchase examples:

@Override
public void itemPurchased(String sku) {
    Purchase iap = Purchase.getInAppPurchase();

    // Force us to reload the receipts from the store.
    iap.synchronizeReceiptsSync(0);
    ToastBar.showMessage("Your subscription has been extended to "+iap.getExpiryDate(PRODUCTS), FontImage.MATERIAL_THUMB_UP);
}

@Override
public void itemPurchaseError(String sku, String errorMessage) {
    ToastBar.showErrorMessage("Failure occurred: "+errorMessage);
}

Notice that, in itemPurchased() we don’t need to explicitly create any receipts or submit anything to the receipt store. This is handled for you automatically. We do make a call to synchronizeReceiptsSync() but this is just to ensure that our toast message has the new expiry date loaded already.

9.2.9. Screenshots

Main form
Figure 317. Main form
Dialog shown when subscribing to a product
Figure 318. Dialog shown when subscribing to a product
Simulator confirm dialog when purchasing a subscription
Figure 319. Simulator confirm dialog when purchasing a subscription
Upon successful purchase
Figure 320. Upon successful purchase, the toastbar message is shown

9.2.10. Summary

This section demonstrated how to set up an app to use non-renewable subscriptions using in-app purchase. Non-renewable subscriptions are the same as regular consumable products except for the fact that they are shared by all of the user’s devices, and thus, require a server component. The app store has no knowledge of the duration of your non-renewable subscriptions. It’s up to you to specify the expiry date of purchased subscriptions on their receipts when they are submitted. Google play doesn’t formally have a "non-renewable" subscription product type. To implement them in Google play, you would just set up a regular product. It’s how you handle it internally that makes it a subscription, and not just a regular product.

Codename One uses the Receipt class as the foundation for its subscriptions infrastructure. You, as the developer, are responsible for implementing the ReceiptStore interface to provide the receipts. The Purchase instance will load receipts from your ReceiptStore, and use them to determine whether the user is currently subscribed to a subscription, and when the subscription expires.

9.2.11. Auto-Renewable Subscriptions

Auto-renewable subscriptions provide, arguably, an easier path to recurring revenue than non-renewable subscriptions because all of the subscription stuff is handled by the app store. You defer almost entirely to the app store (iTunes for iOS, and Play for Android) for billing and management.

If there is a down-side, it would be that you are also subject to the rules of each app store - and they take their cut of the revenue.

  1. For more information about Apple’s auto-renewable subscription features and rules see this document.

  2. For more information about subscriptions in Google play, see this document.

9.2.12. Auto-Renewable vs Non-Renewable. Best Choice?

When deciding between auto-renewable and non-renewable subscriptions, as always, the answer will depend on your needs and preferences. Auto-renewables are nice because it takes the process completely out of your hands. You just get paid. On the other hand, there are valid reasons to want to use non-renewables. E.g. You can’t cancel an auto-renewable subscription for a user. They have to do that themselves. You may also want more control over the subscription and renewal process, in which case a non-renewable might make more sense.

There are some developers that are opposed to auto-renewable subscriptions, we don’t have enough information to make a solid recommendation on this matter

On a practical level, if you are using auto-renewable subscriptions (and therefore subscription products in the Google play store) you must use the Purchase.subscribe(sku) method for initiating the purchase workflow. For non-renewable subscriptions (and therefore regular products in the Google play store), you must use the Purchase.purchase(sku) method.

9.2.13. Learning By Example

In this section we’ll describe the general workflow of subscription management on the server. We also demonstrate how use Apple’s and Google’s web services to validate receipts and stay informed of important events (such as when users cancel or renew their subscriptions).

9.2.14. Building the IAP Demo Project

To aid in this process, we’ve created a fully-functional in-app purchase demo project that includes both a client app and a server app.

Setting up the Client Project
  1. Create a new Codename One project in Netbeans, and choose the "Bare-bones Hello World Template". You should make your package name something unique so that you are able to create real corresponding apps in both Google Play and iTunes connect.

  2. Once the project is created, copy this source file contents into your main class file. Then change the package name, and class name in the file to match your project settings. E.g. change package ca.weblite.iapdemo; to package <your.package.name.here>; and class IAPDemo implements PurchaseCallback to class YourClassName implements PurchaseCallback.

  3. Add the Generic Web Service Client library to your project by going to "Codename Settings" > "Extensions", finding that library, and click "Download". Then "Refresh CN1 libs" as it suggests.

  4. Change the localHost property to point to your local machine’s network address. Using "http://localhost" is not going to cut it here because when the app is running on a phone, it needs to be able to connect to your web server over the network. This address will be your local network address (e.g. 192.168.0.9, or something like that).

    private static final String localHost = "http://10.0.1.32";
  5. Add the ios.plistInject build hint to your project with the value "<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>". This is so that we can use http urls in iOS. Since we don’t intend to fully publish this app, we can cut corners like this. If you were creating a real app, you would use proper secure URLs.

Setting up the Server Project

Download the CN1-IAP-Server demo project from Github, and run its "install-deps" ANT task in order to download and install its dependencies to your local Maven repo.

For the following commands to work, make sure you have "ant", "mvn", and "git" in your environment PATH.
$ git clone https://github.com/shannah/cn1-iap-demo-server
$ cd cn1-iap-demo-server
$ ant install-deps

Open the project in Netbeans

Setting up the Database
  1. Create a new database in your preferred DBMS. Call it anything you like.

  2. Create a new table named "RECEIPTS" in this database with the following structure:

    create TABLE RECEIPTS
    (
    	TRANSACTION_ID VARCHAR(128) not null,
    	USERNAME VARCHAR(64) not null,
    	SKU VARCHAR(128) not null,
    	ORDER_DATA VARCHAR(32000),
    	PURCHASE_DATE BIGINT,
    	EXPIRY_DATE BIGINT,
    	CANCELLATION_DATE BIGINT,
    	LAST_VALIDATED BIGINT,
    	STORE_CODE VARCHAR(20) default '' not null,
    	primary key (TRANSACTION_ID, STORE_CODE)
    )
  3. Open the "persistence.xml" file in the server netbeans project.

    Persistence File
  4. Change the data source to the database you just created.

    Edit persistence.xml file data source

If you’re not sure how to create a data source, see my previous tutorial on connecting to a MySQL database.

Testing the Project

At this point we should be able to test out the project in the Codename One simulator to make sure it’s working.

  1. Build and Run the server project in Netbeans. You may need to tell it which application server you wish to run it on. I am running it on the Glassfish 4.1 that comes bundled with Netbeans.

  2. Build and run the client project in Netbeans. This should open the Codename One simulator.

When the app first opens you’ll see a screen as follows:

First screen of app
Figure 321. First screen of app

This screen is for testing consumable products, so we won’t be making use of this right now.

Open the hamburger menu and select "Subscriptions". You should see something like this:

Subscriptions form
Figure 322. Subscriptions form

Click on the "Subscribe 1 Month No Ads" button. You will be prompted to accept the purchase:

Approve purchase dialog
Figure 323. Approve purchase dialog

Upon completion, the app will submit the purchase to your server, and if all went well, it will retrieve the updated list of receipts from your server also, and update the label on this form to say "No Ads. Expires <some date>":

After successful purchase
Figure 324. After successful purchase
This project is set up to use an expedited expiry date schedule for purchases from the simulator. 1 month = 5 minutes. 3 months = 15 minutes. This helps for testing. That is why your expiry date may be different than expected.

Just to verify that the receipt was inserted correctly, you should check the contents of your "RECEIPTS" table in your database. In Netbeans, I can do this easily from the "Services" pane. Expand the database connection down to the RECEIPTS table, right click "RECEIPTS" and select "View Data". This will open a data table similar the the following:

Receipts table after insertion
Figure 325. Receipts table after insertion
Table view
Figure 326. Table view

A few things to mention here:

  1. The "username" was provided by the client. It’s hard-coded to "admin", but the idea is that you would have the user log in and you would have access to their real username.

  2. All dates are stored as unix timestamps in milliseconds.

If you delete the receipt from your database, then press the "Synchronize Receipts" button in your app, the app will again say "No subscriptions." Similarly if you wait 5 minutes and hit "Synchronize receipts" the app will say no subscriptions found, and the "ads" will be back.

Troubleshooting

Let’s not pretend that everything worked for you on the first try. There’s a lot that could go wrong here. If you make a purchase and nothing appears to happen, the first thing you should do is check the Network Monitor in the simulator ("Simulate" > "Network" > "Network Monitor"). You should see a list of network requests. Some will be GET requests and there will be at least one POST request. Check the response of these requests to see if they succeeded.

Also check the Glassfish server log to see if there is an exception.

Common problems would be that the URL you have set in the client app for endpointURL is incorrect, or that there is a database connection problem.

9.2.15. Looking at the Source of the App

Now that we’ve set up and built the app, let’s take a look at the source code so you can see how it all works.

Client Side

I use the Generic Webservice Client Library from inside my ReceiptStore implementation to load receipts from the web service, and insert new receipts to the database.

The source for my ReceiptStore is as follows:

private ReceiptStore createReceiptStore() {
    return new ReceiptStore() {

        RESTfulWebServiceClient client = createRESTClient(receiptsEndpoint);

        @Override
        public void fetchReceipts(SuccessCallback<Receipt[]> callback) {
            RESTfulWebServiceClient.Query query = new RESTfulWebServiceClient.Query() {

                @Override
                protected void setupConnectionRequest(RESTfulWebServiceClient client, ConnectionRequest req) {
                    super.setupConnectionRequest(client, req);
                    req.setUrl(receiptsEndpoint);
                }

            };
            client.find(query, rowset->{
                List<Receipt> out = new ArrayList<Receipt>();
                for (Map m : rowset) {
                    Result res = Result.fromContent(m);
                    Receipt r = new Receipt();
                    r.setTransactionId(res.getAsString("transactionId"));
                    r.setPurchaseDate(new Date(res.getAsLong("purchaseDate")));
                    r.setQuantity(1);
                    r.setStoreCode(m.getAsString("storeCode"));
                    r.setSku(res.getAsString("sku"));

                    if (m.containsKey("cancellationDate") && m.get("cancellationDate") != null) {
                        r.setCancellationDate(new Date(res.getAsLong("cancellationDate")));
                    }
                    if (m.containsKey("expiryDate") && m.get("expiryDate") != null) {
                        r.setExpiryDate(new Date(res.getAsLong("expiryDate")));
                    }
                    out.add(r);

                }
                callback.onSucess(out.toArray(new Receipt[out.size()]));
            });
        }

        @Override
        public void submitReceipt(Receipt r, SuccessCallback<Boolean> callback) {
            Map m = new HashMap();
            m.put("transactionId", r.getTransactionId());
            m.put("sku", r.getSku());
            m.put("purchaseDate", r.getPurchaseDate().getTime());
            m.put("orderData", r.getOrderData());
            m.put("storeCode", r.getStoreCode());
            client.create(m, callback);
        }

    };
}

Notice that we are not doing any calculation of expiry dates in our client app, as we did in the previous post (on non-renewable receipts). Since we are using a server now, it makes sense to move all of that logic over to the server.

The createRESTClient() method shown there simply creates a RESTfulWebServiceClient and configuring it to use basic authentication with a username and password. The idea is that your user would have logged into your app at some point, and you would have a username and password on hand to pass back to the web service with the receipt data so that you can connect the subscription to a user account. The source of that method is listed here:

/**
 * Creates a REST client to connect to a particular endpoint.  The REST client
 * generated here will automatically add the Authorization header
 * which tells the service what platform we are on.
 * @param url The url of the endpoint.
 * @return
 */
private RESTfulWebServiceClient createRESTClient(String url) {
    return new RESTfulWebServiceClient(url) {

        @Override
        protected void setupConnectionRequest(ConnectionRequest req) {
            try {
                req.addRequestHeader("Authorization", "Basic " + Base64.encode((getUsername()+":"+getPassword()).getBytes("UTF-8")));
            } catch (Exception ex) {}
        }

    };
}
Server-Side

On the server-side, our REST controller is a standard JAX-RS REST interface. I used Netbeans web service wizard to generate it and then modified it to suit my purposes. The methods of the ReceiptsFacadeREST class pertaining to the REST API are shown here:

@Stateless
@Path("com.codename1.demos.iapserver.receipts")
public class ReceiptsFacadeREST extends AbstractFacade<Receipts> {

    // ...

    @POST
    @Consumes({"application/xml", "application/json"})
    public void create(Receipts entity) {

        String username = credentialsWithBasicAuthentication(request).getName();
        entity.setUsername(username);

        // Save the receipt first in case something goes wrong in the validation stage
        super.create(entity);

        // Let's validate the receipt
        validateAndSaveReceipt(entity);
            // validates the receipt against appropriate web service
            // and updates database if expiry date has changed.
    }

    // ...
    @GET
    @Override
    @Produces({"application/xml", "application/json"})
    public List<Receipts> findAll() {
        String username = credentialsWithBasicAuthentication(request).getName();
        return getEntityManager()
                .createNamedQuery("Receipts.findByUsername")
                .setParameter("username", username)
                .getResultList();
    }
}

The magic happens inside that validateAndSaveReceipt() method, which I’ll cover in detail soon.

Notifications

It’s important to note that you will not be notified by apple or google when changes are made to subscriptions. It’s up to you to periodically "poll" their web service to find if any changes have been made. Changes we would be interested in are primarily renewals and cancellations. In order to deal with this, set up a method to run periodically (once-per day might be enough). For testing, I actually set it up to run once per minute as shown below:

private static final long ONE_DAY = 24 * 60 * 60 * 1000;
private static final long ONE_DAY_SANDBOX = 10 * 1000;
@Schedule(hour="*", minute="*")
public void validateSubscriptionsCron() {
    System.out.println("----------- DOING TIMED TASK ---------");
    List<Receipts> res = null;
    final Set<String> completedTransactionIds = new HashSet<String>();
    for (String storeCode : new String[]{Receipt.STORE_CODE_ITUNES, Receipt.STORE_CODE_PLAY}) {
        while (!(res = getEntityManager().createNamedQuery("Receipts.findNextToValidate")
                .setParameter("threshold", System.currentTimeMillis() - ONE_DAY_SANDBOX)
                .setParameter("storeCode", storeCode)
                .setMaxResults(1)
                .getResultList()).isEmpty() &&
                !completedTransactionIds.contains(res.get(0).getTransactionId())) {

            final Receipts curr = res.get(0);
            completedTransactionIds.add(curr.getTransactionId());
            Receipts[] validatedReceipts =  validateAndSaveReceipt(curr);
            em.flush();
            for (Receipts r : validatedReceipts) {
                completedTransactionIds.add(r.getTransactionId());
            }

        }
    }
}

That method simply finds all of the receipts in the database that haven’t been validated in some period of time, and validates it. Again, the magic happens inside the validateAndSaveReceipt() method which we cover later.

In this example we only validate receipts from the iTunes and Play stores because those are the only ones that we currently support auto-renewing subscriptions on.

9.2.16. The CN1-IAP-Validator Library

For the purpose of this tutorial, I created a library to handle receipt validation in a way that hides as much of the complexity as possible. It supports both Google Play receipts and iTunes receipts.

The general usage is as follows:

IAPValidator validator = IAPValidator.getValidatorForPlatform(receipt.getStoreCode());
if (validator == null) {
   // no validators were found for this store
   // Do custom validation
} else {
    validator.setAppleSecret(APPLE_SECRET);
    validator.setGoogleClientId(GOOGLE_DEVELOPER_API_CLIENT_ID);
    validator.setGooglePrivateKey(GOOGLE_DEVELOPER_PRIVATE_KEY);
    Receipt[] result = validator.validate(receipt);
    ...
}

As you can see from this snippet, the complexity of receipt validation has been reduced to entering three configuration strings:

  1. APPLE_SECRET - This is a "secret" string that you will get from iTunes connect when you set up your in-app products.

  2. GOOGLE_DEVELOPER_API_CLIENT_ID - A client ID that you’ll get from the google developer API console when you set up your API service credentials.

  3. GOOGLE_DEVELOPER_PRIVATE_KEY - A PKCS8 encoded string with an RSA private key that you’ll receive at the same time as the GOOGLE_DEVELOPER_API_CLIENT_ID.

I will go through the steps to obtain these values soon.

9.2.17. The validateAndSaveReceipt() Method

You are now ready to see the full magic of the validateAndSaveReceipt() method in all its glory:

/**
 * Validates a given receipt, updating the expiry date,
 * @param receipt The receipt to be validated
 * @param forInsert If true, then an expiry date will be calculated even if there is no validator.
 */
private Receipts[] validateAndSaveReceipt(Receipts receipt) {
    EntityManager em = getEntityManager();
    Receipts managedReceipt = getManagedReceipt(receipt);
        // managedReceipt == receipt if receipt is in database or null otherwise

    if (Receipt.STORE_CODE_SIMULATOR.equals(receipt.getStoreCode())) { (1)
        if (receipt.getExpiryDate() == null && managedReceipt == null) {
            //Not inserted yet and no expiry date set yet
            Date dt = calculateExpiryDate(receipt.getSku(), true);
            if (dt != null) {
                receipt.setExpiryDate(dt.getTime());
            }
        }
        if (managedReceipt == null) {
            // Receipt is not in the database yet.  Add it
            em.persist(receipt);
            return new Receipts[]{receipt};
        } else {
            // The receipt is already in the database.  Update it.
            em.merge(managedReceipt);
            return new Receipts[]{managedReceipt};
        }
    } else {
        // It's not a simulator receipt
        IAPValidator validator = IAPValidator.getValidatorForPlatform(receipt.getStoreCode());
        if (validator == null) {
            // Receipt must have come from a platform other than iTunes or Play
            // Because there is no validator

            if (receipt.getExpiryDate() == null && managedReceipt == null) {
                // No expiry date.
                // Generate one.
                Date dt = calculateExpiryDate(receipt.getSku(), false);
                if (dt != null) {
                    receipt.setExpiryDate(dt.getTime());
                }

            }
            if (managedReceipt == null) {
                em.persist(receipt);
                return new Receipts[]{receipt};
            } else {
                em.merge(managedReceipt);
                return new Receipts[]{managedReceipt};
            }

        }

        // Set credentials for the validator
        validator.setAppleSecret(APPLE_SECRET);
        validator.setGoogleClientId(GOOGLE_DEVELOPER_API_CLIENT_ID);
        validator.setGooglePrivateKey(GOOGLE_DEVELOPER_PRIVATE_KEY);

        // Create a dummy receipt with only transaction ID and order data to pass
        // to the validator.  Really all it needs is order data to be able to validate
        Receipt r2 = Receipt();
        r2.setTransactionId(receipt.getTransactionId());
        r2.setOrderData(receipt.getOrderData());
        try {
            Receipt[] result = validator.validate(r2);
            // Depending on the platform, result may contain many receipts or a single receipt
            // matching our receipt.  In the case of iTunes, none of the receipt transaction IDs
            // might match the original receipt's transactionId because the validator
            // will set the transaction ID to the *original* receipt's transaction ID.
            // If none match, then we should remove our receipt, and update each of the returned
            // receipts in the database.
             Receipt matchingValidatedReceipt = null;
            for (Receipt r3 : result) {
                if (r3.getTransactionId().equals(receipt.getTransactionId())) {
                    matchingValidatedReceipt = r3;
                    break;
                }
            }

            if (matchingValidatedReceipt == null) {
                // Since the validator didn't find our receipt,
                // we should remove the receipt.  The equivalent
                // is stored under the original receipt's transaction ID
                if (managedReceipt != null) {
                    em.remove(managedReceipt);
                    managedReceipt = null;
                }
            }
            List<Receipts> out = new ArrayList<Receipts>();
            // Now go through and
            for (Receipt r3 : result) {
                if (r3.getOrderData() == null) {
                    // No order data found in receipt.  Setting it to the original order data
                    r3.setOrderData(receipt.getOrderData());
                }
                Receipts eReceipt = new Receipts();
                eReceipt.setTransactionId(r3.getTransactionId());
                eReceipt.setStoreCode(receipt.getStoreCode());
                Receipts eManagedReceipt = getManagedReceipt(eReceipt);
                if (eManagedReceipt == null) {
                    copy(eReceipt, r3);
                    eReceipt.setUsername(receipt.getUsername());
                    eReceipt.setLastValidated(System.currentTimeMillis());
                    em.persist(eReceipt);
                    out.add(eReceipt);
                } else {

                    copy(eManagedReceipt, r3);
                    eManagedReceipt.setUsername(receipt.getUsername());
                    eManagedReceipt.setLastValidated(System.currentTimeMillis());
                    em.merge(eManagedReceipt);
                    out.add(eManagedReceipt);
                }
            }

            return out.toArray(new Receipts[out.size()]);

        } catch (Exception ex) {
            // We should probably store some info about the failure in the
            // database to make it easier to find receipts that aren't validating,
            // but for now we'll just log it.
            Log.p("Failed to validate receipt "+r2);
            Log.p("Reason: "+ex.getMessage());
            Log.e(ex);
            return new Receipts[]{receipt};

        }
    }
}
1 We need to handle the case where the app is being used in the CN1 simulator. We’ll treat this as a non-renewable receipt, and we’ll calculate the expiry date using an "accelerated" clock to assist in testing.
In many of the code snippets for the Server-side code, you’ll see references to both a Receipts class and a Receipt class. I know this is slightly confusing. The Receipts class is a JPA entity the encapsulates a row from the "receipts" table of our SQL database. The Receipt class is com.codename1.payment.Receipt. It’s used to interface with the IAP validation library.

9.2.18. Google Play Setup

Creating the App in Google Play

In order to test out in-app purchase on an Android device, you’ll need to create an app the Google Play Developer Console. I won’t describe the process in this section, but there is plenty of information around the internet on how to do this. Some useful references for this include:

  1. Getting Started With Publishing - If you don’t already have an account with Google to publish your apps.

  2. Launch Checklist

Graphics, Icons, etc..

You are required to upload some screenshots and feature graphics. Don’t waste time making these perfect. For the screenshots, you can just use the "Screenshot" option in the simulator. (Use the Nexus 5 skin). For the feature graphics, I used this site that will generate the graphics in the correct dimensions for Google Play. You can also just leave the icon as the default Codename One icon.

Creating Test Accounts
You cannot purchase in-app products from your app using your publisher account. You need to set up at least one test account for the purpose of testing the app.

In order to test your app, you need to set up a test account. A test account must be associated with a real gmail email address. If you have a domain that is managed by Google apps, then you can also use an address from that domain.

The full process for testing in-app billing can be found in this google document. However, I personally found this documentation difficult to follow.

For your purposes, you’ll need to set up a tester list in Google Play. Choose "Settings" > "Tester Lists". Then create a list with all of the email address that you want to have treated as test accounts. Any purchases made by these email addresses will be treated as "Sandbox" purchases, and won’t require real money to change hands.

Alpha Channel Distribution

In order to test in-app purchase on Android, you must first publish your app. You can’t just build and install your app manually. The app needs to be published on the Play store, and it must be installed through the play store for in-app purchase to work. Luckily you can publish to an Alpha channel so that your app won’t be publicly available.

For more information about setting up alpha testing on Google play see this Google support document on the subject.

Once you have set your app up for alpha testing, you can send an invite link to your test accounts. You can find the link in the Google Play console under the APK section, under the "Alpha" tab (and assuming you’ve enabled alpha testing.

Alpha testing tab in google play
Figure 327. Alpha testing tab in google play

The format of the link is https://play.google.com/apps/testing/your-app-id in case you can’t find it. You can email this to your alpha testers. Make sure that you have added all testers to your tester lists so that their purchases will be made in the sandbox environment.

Also, before proceeding with testing in-app purchases, you need to add the in-app products in Google Play.

Adding In-App Products

After you have published your APK to the alpha channel, you can create the products. For the purposes of this tutorial, we’ll just add two products:

  1. iapdemo.noads.month.auto - The 1 month subscription.

  2. iapdemo.noads.3month.auto - The 3 month subscription.

Since we will be adding products as "Subscriptions" in the pay store, your app must use the Purchase.subscribe(sku) method for initiating a purchase on these products, and not the Purchase.purchase(sku) method. If you accidentally use purchase() to purchase a subscription on Android, the payment will go through, but your purchase callback will receive an error.

Adding 1 month Subscription

  1. Open Google Play Developer Console, and navigate to your app.

  2. Click on "In-app Products" in the menu. Then click the "Add New Product" button.

  3. Select "Subscription", and enter "iapdemo.noads.month.auto" for the Product ID. Then click "Continue"

Add new product dialog
Figure 328. Add new product dialog

Now fill in the form. You can choose your own price and name for the product. The following is a screenshot of the options I chose.

Add product to google
Figure 329. Add product to google

Adding 3 month Subscription

Follow the same process as for the 1 month subscription except use "iapdemo.noads.3month.auto" for the product ID, and select "3 months" for the billing period instead of "Monthly".

Testing The App

At this point we should be ready to test our app. Assuming you’ve installed the app using the invite link you sent yourself from Google play, as a test account that is listed on your testers list, you should be good to go.

Open the app, click on "Subscriptions", and try to purchase a 1-month subscription. If all goes well, it should insert the subscription into your database. But with no expiry date, since we haven’t yet implemented receipt validation yet. We’ll do that next.

Creating Google Play Receipt Validation Credentials

Google play receipt validation is accomplished via the android-publisher Purchases: get API. The CN1-IAP-Validation library shields you from most of the complexities of using this API, but you still need to obtain a "private key" and a "client id" to access this API. Both of these are provided when you set up an OAuth2 Service Account for your app.

The following steps assume that you have already created your app in Google play and have published it to at least the alpha channel. See my previous post on this topic here (Link to be provided).

Steps:

  1. Open the Google API Developer Console, and select your App from the the menu.

  2. Click on the "Library" menu item in the left menu, and then click the "Google Play Developer API" link.

    Google Play Developer API Link
    Figure 330. Google Play Developer API Link
  3. Click on the button that says "Enable". (If you already have it enabled, then just proceed to the next step).

    Enable API button
    Figure 331. Enable API button
  4. Click on the "Credentials" menu item in the left menu.

  5. In the "Credentials" drop-down menu, select the "Service Account Key" option.

    Credentials dropdown
    Figure 332. Credentials dropdown
  6. You will be presented with a new form. In the "Service Account" drop-down, select "New Service Account". This will give you some additional options.

    Create service account key
    Figure 333. Create service account key
  7. Enter anything you like for the "Service account name". For the role, we’ll select "Project" > "Owner" for now just so we don’t run into permissions issues. You’ll probably want to investigate further to fine a more limited role that only allows receipt verification, but for now, I don’t want any unnecessary road blocks for getting this to work. We’re probably going to run into "permission denied" errors at first anyways, so the fewer reasons for this, the better.

  8. It will auto-generate an account ID for you.

  9. Finally, for the "Key type", select "JSON". Then click the "Create" button.

This should prompt the download of a JSON file that will have contents similar to the following:

{
  "type": "service_account",
  "project_id": "iapdemo-152500",
  "private_key_id": "1b1d39f2bc083026b164b10a444ff7d839826b8a",
  "private_key": "-----BEGIN PRIVATE KEY----- ... some private key string -----END PRIVATE KEY-----\n",
  "client_email": "[email protected]",
  "client_id": "117601572633333082772",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://accounts.google.com/o/oauth2/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/iapdemo%40iapdemo-152500.iam.gserviceaccount.com"
}

This is where we get the information we’re looking for. The "client_email" is what we’ll use for your googleClientId, and the "private_key" is what we’ll use for the googlePrivateKey.

Use the "client_email" value as our client ID, not the "client_id" value as you might be tempted to do.

We’ll set these in our constants:

public static final String GOOGLE_DEVELOPER_API_CLIENT_ID="[email protected]";
public static final String GOOGLE_DEVELOPER_PRIVATE_KEY="-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----\n";

...

validator.setGoogleClientId(GOOGLE_DEVELOPER_API_CLIENT_ID);
validator.setGooglePrivateKey(GOOGLE_DEVELOPER_PRIVATE_KEY);

NOT DONE YET

Before we can use these credentials to verify receipts for our app, we need to link our app to this new service account from within Google Play.

Steps:

  1. Open the Google Play Developer Console, then click on "Settings" > "API Access".

  2. You should see your app listed on this page. Click the "Link" button next to your app.

    Link to API
    Figure 334. Link to API
  3. This should reveal some more options on the page. You should see a "Service Accounts" section with a list of all of the service accounts that you have created. Find the one we just created, and click the "Grant Access" button in its row.

    Grant access
    Figure 335. Grant access
  4. This will open a dialog titled "Add New User". Leave everything default, except change the "Role" to "Administrator". This provides "ALL" permissions to this account, which probably isn’t a good idea for production. Later on, after everything is working, you can circle back and try to refine permissions. For the purpose of this tutorial, I just want to pull out all of the potential road blocks.

    New User
    Figure 336. New User
  5. Press the "Add User" button.

At this point, the service account should be active so we can try to validate receipts.

Testing Receipt Validation

The ReceiptsFacadeREST class includes a flag to enable/disable play store validation. By default it’s disabled. Let’s enable it:

public static final boolean DISABLE_PLAY_STORE_VALIDATION=true;

Change this to false.

Then build and run the server app. The validateSubscriptionsCron() method is set to run once per minute, so we just need to wait for the timer to come up and it should try to validate all of the play store receipts.

I’m assuming you’ve already added a receipt in the previous test that we did. If necessary, you should purchase the subscription again in your app.

After a minute or so, you should see "----------- VALIDATING RECEIPTS ---------" written in the Glassfish log, and it will validate your receipts. If it works, your receipt’s expiry date will get populated in the database, and you can press "Synchronize Receipts" in your app to see this reflected. If it fails, there will like be a big ugly stack trace and exception readout with some clues about what went wrong.

Realistically, your first attempt will fail for some reason. Use the error codes and stack traces to help lead you to the problem. And feel free to post questions here.

9.2.19. iTunes Connect Setup

The process for setting up and testing your app on iOS is much simpler than on Android (IMHO). It took me a couple hours to get the iTunes version working, vs a couple days on the Google Play side of things. One notable difference that makes things simpler is that you don’t need to actually upload your app to the store to test in-app purchase. You can just use your debug build on your device. It’s also much easier to roll a bunch of test accounts than on Google Play. You don’t need to set up an alpha program, you just create a few "test accounts" (and this is easy to do) in your iTunes connect account, and then make sure to use one of these accounts when making a purchase. You can easily switch accounts on your device from the "Settings" app, where you can just log out of the iTunes store - which will cause you to be prompted in your app the next time you make a purchase.

Setting up In-App Products

The process to add products in iTunes connect is outlined in this apple developer document. We’ll add our two SKUs:

  1. iapdemo.noads.month.auto - The 1 month subscription.

  2. iapdemo.noads.3month.auto - The 3 month subscription.

Just make sure you add them as auto-renewable subscriptions, and that you specify the appropriate renewal periods. Use the SKU as the product ID. Both of these products will be added to the same subscription group. Call the group whatever you like.

Creating Test Accounts

In order to test purchases, you need to create some test accounts. See this apple document for details on how to create these test accounts. Don’t worry, the process is much simpler than for Android. It should take you under 5 minutes.

Once you have the test accounts created, you should be set to test the app.

  1. Make sure your server is running.

  2. Log out from the app store. The process is described here.

  3. Open your app.

  4. Try to purchase a 1-month subscription

If all went well, you should see the receipt listed in the RECEIPTS table of your database. But the expiry date will be null. We need to set up receipt verification in order for this to work.

Setting up Receipt Verification

In order for receipt verification to work we simply need to generate a shared secret in iTunes connect. The process is described here.

Once you have a shared secret, update the ReceiptsFacadeREST class with the value:

public static final String APPLE_SECRET = "your-shared-secret-here";

And enable iTunes store validation:

public static final boolean DISABLE_ITUNES_STORE_VALIDATION=true;

Change this to false.

If you rebuild and run the server project, and wait for the validateSubscriptionsCron() method to run, it should validate the receipt. After about a minute (or less), you’ll see the text "----------- VALIDATING RECEIPTS ---------" written to the Glassfish log file, followed by some output from connecting to the iTunes validation service. If all went well, you should see your receipt expiration date updated in the database. If not, you’ll likely see some exception stack traces in the Glassfish log.

Sandbox receipts in the iTunes store are set to run on an accelerated schedule. A 1 month subscription is actually 5 minutes, 3 months is 15 minutes etc…​ Also sandbox subscriptions don’t seem to persist in perpetuity until the user has cancelled it. I have found that they usually renew only 4 or 5 times before they are allowed to lapse by Apple.

10. Graphics, Drawing, Images & Fonts

Drawing is considered a low level API that might introduce some platform fragmentation.

10.1. Basics - Where & How Do I Draw Manually?

The Graphics class is responsible for drawing basics, shapes, images and text, it is never instantiated by the developer and is always passed on by the Codename One API.

You can gain access to a Graphics using one of the following methods:

  • Derive Component or a subclass of Component - within Component there are several methods that allow developers to modify the drawing behavior. These can be overridden to change the way the component is drawn:

    • paint(Graphics) - invoked to draw the component, this can be overridden to draw the component from scratch.

    • paintBackground(Graphics) / paintBackgrounds(Graphics) - these allow overriding the way the component background is painted although you would probably be better off implementing a painter (see below).

    • paintBorder(Graphics) - allows overriding the process of drawing a border, notice that border drawing might differ based on the style of the component.

    • paintComponent(Graphics) - allows painting only the components contents while leaving the default paint behavior to the style.

    • paintScrollbars(Graphics), paintScrollbarX(Graphics), paintScrollbarY(Graphics) allows overriding the behavior of scrollbar painting.

  • Implement the painter interface, this interface can be used as a GlassPane or a background painter.

    The painter interface is a simple interface that includes 1 paint method, this is a useful way to allow developers to perform custom painting without subclassing Component. Painters can be chained together to create elaborate paint behavior by using the PainterChain class.

    • Glass pane - a glass pane allows developers to paint on top of the form painting. This allows an overlay effect on top of a form.

      For a novice it might seem that a glass pane is similar to overriding the Form’s paint method and drawing after super.paint(g) completed. This isn’t the case. When a component repaints (by invoking the repaint() method) only that component is drawn and Form’s paint() method wouldn’t be invoked. However, the glass pane painter is invoked for such cases and would work exactly as expected.

      Container has a glass pane method called paintGlass(Graphics), which can be overridden to provide a similar effect on a Container level. This is especially useful for complex containers such as Table which draws its lines using such a methodology.

    • Background painter - the background painter is installed via the style, by default Codename installs a custom background painter of its own. Installing a custom painter allows a developer to completely define how the background of the component is drawn.
      Notice that a lot of the background style behaviors can be achieved using styles alone.

A common mistake developers make is overriding the paint(Graphics) method of Form. The problem with that is that form has child components which might request a repaint. To prevent that either place a paintable component in the center of the Form, override the glasspane or the background painter.

A paint method can be implemented as such:

// hide the title
Form hi = new Form("", new BorderLayout());
hi.add(BorderLayout.CENTER, new Component() {
    @Override
    public void paint(Graphics g) {
        // red color
        g.setColor(0xff0000);

        // paint the screen in red
        g.fillRect(getX(), getY(), getWidth(), getHeight());

        // draw hi world in white text at the top left corner of the screen
        g.setColor(0xffffff);
        g.drawString("Hi World", getX(), getY());
    }
});
hi.show();
Hi world demo code
Figure 337. Hi world demo code, notice that the blue bar on top is the iOS7+ status bar

10.2. Glass Pane

The GlassPane `in Codename One is inspired by the Swing `GlassPane & LayeredPane with quite a few twists. We tried to imagine how Swing developers would have implemented the glass pane knowing what they do now about painters and Swings learning curve. But first: what is the glass pane?

A typical Codename One application is essentially composed of 3 layers (this is a gross simplification though), the background painters are responsible for drawing the background of all components including the main form. The component draws its own content (which might overrule the painter) and the glass pane paints last…​

Form layout graphic
Figure 338. Form layout graphic

Essentially the glass pane is a painter that allows us to draw an overlay on top of the Codename One application.

Overriding the paint method of a form isn’t a substitute for glasspane as it would appear to work initially, when you enter a Form. However, when modifying an element within the form only that element gets repainted not the entire Form!

So if we have a form with a Button and text drawn on top using the Form’s paint method it would get erased whenever the button gets focus.

The glass pane is called whenever a component gets painted, it only paints within the clipping region of the component hence it won’t break the rest of the components on the Form which weren’t modified.

You can set a painter on a form using code like this:

hi.setGlassPane(new Painter() {
    @Override
    public void paint(Graphics g, Rectangle rect) {
    }
});

Or you can use Java 8 lambdas to tighten the code a bit:

hi.setGlassPane((g, rect) -> {
});

PainterChain allows us to chain several painters together to perform different logical tasks such as a validation painter coupled with a fade out painter. The sample below shows a crude validation panel that allows us to draw error icons next to components while exceeding their physical bounds as is common in many user interfaces

Form hi = new Form("Glass Pane", new BoxLayout(BoxLayout.Y_AXIS));
Style s = UIManager.getInstance().getComponentStyle("Label");
s.setFgColor(0xff0000);
s.setBgTransparency(0);
Image warningImage = FontImage.createMaterial(FontImage.MATERIAL_WARNING, s).toImage();
TextField tf1 = new TextField("My Field");
tf1.getAllStyles().setMarginUnit(Style.UNIT_TYPE_DIPS);
tf1.getAllStyles().setMargin(5, 5, 5, 5);
hi.add(tf1);
hi.setGlassPane((g, rect) -> {
    int x = tf1.getAbsoluteX() + tf1.getWidth();
    int y = tf1.getAbsoluteY();
    x -= warningImage.getWidth() / 2;
    y += (tf1.getHeight() / 2 - warningImage.getHeight() / 2);
    g.drawImage(warningImage, x, y);
});
hi.show();
The glass pane draws the warning sign on the border of the component partially peeking out
Figure 339. The glass pane draws the warning sign on the border of the component partially peeking out

10.3. Shapes & Transforms

The graphics API provides a high performance shape API that allows drawing arbitrary shapes by defining paths and curves and caching the shape drawn in the GPU.

10.4. Device Support

Shapes and transforms are available on most smartphone platforms with some caveats for the current Windows Phone port.

Notice that perspective transform is missing from the desktop/simulator port. Unfortunately there is no real equivalent to perspective transform in JavaSE that we could use.

10.5. A 2D Drawing App

We can demonstrate shape drawing with a simple example of a drawing app, that allows the user to tap the screen to draw a contour picture.

The app works by simply keeping a GeneralPath in memory, and continually adding points as bezier curves. Whenever a point is added, the path is redrawn to the screen.

The center of the app is the DrawingCanvas class, which extends Component.

public class DrawingCanvas extends Component {
    GeneralPath p = new GeneralPath();
    int strokeColor = 0x0000ff;
    int strokeWidth = 10;

    public void addPoint(float x, float y){
        // To be written
    }

    @Override
    protected void paintBackground(Graphics g) {
        super.paintBackground(g);
            Stroke stroke = new Stroke(
                strokeWidth,
                Stroke.CAP_BUTT,
                Stroke.JOIN_ROUND, 1f
            );
            g.setColor(strokeColor);

            // Draw the shape
            g.drawShape(p, stroke);

    }

    @Override
    public void pointerPressed(int x, int y) {
        addPoint(x-getParent().getAbsoluteX(), y-getParent().getAbsoluteY());
    }
}

Conceptually this is very basic component. We will be overriding the paintBackground() method to draw the path. We keep a reference to a GeneralPath object (which is the concrete implementation of the Shape interface in Codename One) to store each successive point in the drawing. We also parametrize the stroke width and color.

The implementation of the paintBackground() method (shown above) should be fairly straight forward. It creates a stroke of the appropriate width, and sets the color on the graphics context. Then it calls drawShape() to render the path of points.

10.5.1. Implementing addPoint()

The addPoint method is designed to allow us to add points to the drawing. A simple implementation that uses straight lines rather than curves might look like this:

private float lastX = -1;
private float lastY = -1;

public void addPoint(float x, float y) {
    if (lastX == -1) {
        // this is the first point... don't draw a line yet
        p.moveTo(x, y);
    } else {
        p.lineTo(x, y);
    }
    lastX = x;
    lastY = y;

    repaint();
}

We introduced a couple house-keeping member vars (lastX and lastY) to store the last point that was added so that we know whether this is the first tap or a subsequent tap. The first tap triggers a moveTo() call, whereas subsequent taps trigger lineTo() calls, which draw lines from the last point to the current point.

A drawing might look like this:

lineTo example
Figure 340. lineTo example

10.5.2. Using Bezier Curves

Our previous implementation of addPoint() used lines for each segment of the drawing. Let’s make an adjustment to allow for smoother edges by using quadratic curves instead of lines.

Codename One’s GeneralPath class includes two methods for drawing curves:

  1. quadTo() : Appends a quadratic bezier curve. It takes 2 points: a control point, and an end point.

  2. curveTo() : Appends a cubic bezier curve, taking 3 points: 2 control points, and an end point.

See the General Path javadocs for the full API.

We will make use of the quadTo() method to append curves to the drawing as follows:

private boolean odd=true;
public void addPoint(float x, float y){
    if ( lastX == -1 ){
        p.moveTo(x, y);

    } else {
        float controlX = odd ? lastX : x;
        float controlY = odd ? y : lastY;
        p.quadTo(controlX, controlY, x, y);
    }
    odd = !odd;
    lastX = x;
    lastY = y;
    repaint();
}

This change should be fairly straight forward except, perhaps, the business with the odd variable. Since quadratic curves require two points (in addition to the implied starting point), we can’t simply take the last tap point and the current tap point. We need a point between them to act as a control point. This is where we get the curve from. The control point works by exerting a sort of "gravity" on the line segment, to pull the line towards it. This results in the line being curved. I use the odd marker to alternate the control point between positions above the line and below the line.

A drawing from the resulting app looks like:

Result of quadTo example
Figure 341. Result of quadTo example

10.5.3. Detecting Platform Support

The DrawingCanvas example is a bit naive in that it assumes that the device supports the shape API. If I were to run this code on a device that doesn’t support the Shape API, it would just draw a blank canvas where I expected my shape to be drawn. You can fall back gracefully if you make use of the Graphics.isShapeSupported() method. E.g.

@Override
protected void paintBackground(Graphics g) {
    super.paintBackground(g);
    if ( g.isShapeSupported() ){
       // do my shape drawing code here
    } else {
        // draw an alternate representation for device
        // that doesn't support shapes.
        // E.g. you could defer to the Pisces
        // library in this case
    }

}

10.6. Transforms

The Graphics class has included limited support for 2D transformations for some time now including scaling, rotation, and translation:

  • scale(x,y) : Scales drawing operations by a factor in each direction.

  • translate(x,y) : Translates drawing operations by an offset in each direction.

  • rotate(angle) : Rotates about the origin.

  • rotate(angle, px, py) : Rotates about a pivot point.

scale() and rotate() methods are only available on platforms that support Affine transforms. See table X for a compatibility list.

10.6.1. Device Support

As of this writing, not all devices support transforms (i.e. scale() and rotate()). The following is a list of platforms and their respective levels of support.

Table 7. Transforms Device Support

Platform

Affine Supported

Simulator

Yes

iOS

Yes

Android

Yes

JavaScript

Yes

J2ME

No

BlackBerry (4.2 & 5)

No

Windows Phone

No (pending)

You can check if a particular Graphics context supports rotation and scaling using the isAffineSupported() method.

e.g.

public void paint(Graphics g){
    if ( g.isAffineSupported() ){
        // Do something that requires rotation and scaling

    } else {
        // Fallback behavior here
    }
}

10.7. Example: Drawing an Analog Clock

In the following sections, I will implement an analog clock component. This will demonstrate three key concepts in Codename One’s graphics:

  1. Using the GeneralPath class for drawing arbitrary shapes.

  2. Using Graphics.translate() to translate our drawing position by an offset.

  3. Using Graphics.rotate() to rotate our drawing position.

There are three separate things that need to be drawn in a clock:

  1. The tick marks. E.g. most clocks will have a tick mark for each second, larger tick marks for each hour, and sometimes even larger tick marks for each quarter hour.

  2. The numbers. We will draw the clock numbers (1 through 12) in the appropriate positions.

  3. The hands. We will draw the clock hands to point at the appropriate points to display the current time.

10.7.1. The AnalogClock Component

Our clock will extend the Component class, and override the paintBackground() method to draw the clock as follows:

public class AnalogClock extends Component {
    Date currentTime = new Date();

    @Override
    public void paintBackground(Graphics g) {
        // Draw the clock in this method
    }
}

10.7.2. Setting up the Parameters

Before we actually draw anything, let’s take a moment to figure out what values we need to know in order to draw an effective clock. Minimally, we need two values:

  1. The center point of the clock.

  2. The radius of the clock.

In addition, I am adding the following parameters to to help customize how the clock is rendered:

  1. The padding (i.e. the space between the edge of the component and the edge of the clock circle.

  2. The tick lengths. I will be using 3 different lengths of tick marks on this clock. The longest ticks will be displayed at quarter points (i.e. 12, 3, 6, and 9). Slightly shorter ticks will be displayed at the five-minute marks (i.e. where the numbers appear), and the remaining marks (corresponding with seconds) will be quite short.

// Hard code the padding at 10 pixels for now
double padding = 10;

// Clock radius
double r = Math.min(getWidth(), getHeight())/2-padding;

// Center point.
double cX = getX()+getWidth()/2;
double cY = getY()+getHeight()/2;

//Tick Styles
int tickLen = 10;  // short tick
int medTickLen = 30;  // at 5-minute intervals
int longTickLen = 50; // at the quarters
int tickColor = 0xCCCCCC;
Stroke tickStroke = new Stroke(2f, Stroke.CAP_BUTT, Stroke.JOIN_ROUND, 1f);

10.7.3. Drawing the Tick Marks

For the tick marks, we will use a single GeneralPath object, making use of the moveTo() and lineTo() methods to draw each individual tick.

// Draw a tick for each "second" (1 through 60)
for ( int i=1; i<= 60; i++){
    // default tick length is short
    int len = tickLen;
    if ( i % 15 == 0 ){
        // Longest tick on quarters (every 15 ticks)
        len = longTickLen;
    } else if ( i % 5 == 0 ){
        // Medium ticks on the '5's (every 5 ticks)
        len = medTickLen;
    }

    double di = (double)i; // tick num as double for easier math

    // Get the angle from 12 O'Clock to this tick (radians)
    double angleFrom12 = di/60.0*2.0*Math.PI;

    // Get the angle from 3 O'Clock to this tick
        // Note: 3 O'Clock corresponds with zero angle in unit circle
        // Makes it easier to do the math.
    double angleFrom3 = Math.PI/2.0-angleFrom12;

    // Move to the outer edge of the circle at correct position
    // for this tick.
    ticksPath.moveTo(
            (float)(cX+Math.cos(angleFrom3)*r),
            (float)(cY-Math.sin(angleFrom3)*r)
    );

    // Draw line inward along radius for length of tick mark
    ticksPath.lineTo(
            (float)(cX+Math.cos(angleFrom3)*(r-len)),
            (float)(cY-Math.sin(angleFrom3)*(r-len))
    );
}

// Draw the full shape onto the graphics context.
g.setColor(tickColor);
g.drawShape(ticksPath, tickStroke);
This example uses a little bit of trigonometry to calculate the (x,y) coordinates of the tick marks based on the angle and the radius. If math isn’t your thing, don’t worry. This example just makes use of the identities: x=r*cosθ and y=r*sinθ.

At this point our clock should include a series of tick marks orbiting a blank center as shown below:

Drawing tick marks on the watch face
Figure 342. Drawing tick marks on the watch face

10.7.4. Drawing the Numbers

The Graphics.drawString(str, x, y) method allows you to draw text at any point of a component. The tricky part here is calculating the correct x and y values for each string so that the number appears in the correct location.

For the purposes of this tutorial, we will use the following strategy. For each number (1 through 12):

  1. Use the Graphics.translate(x,y) method to apply a translation from the clock’s center point to the point where the number should appear.

  2. Draw number (using drawString()) at the clock’s center. It should be rendered at the correct point due to our translation.

  3. Invert the translation performed in step 1.

for ( int i=1; i<=12; i++){
    // Calculate the string width and height so we can center it properly
    String numStr = ""+i;
    int charWidth = g.getFont().stringWidth(numStr);
    int charHeight = g.getFont().getHeight();

    double di = (double)i;  // number as double for easier math

    // Calculate the position along the edge of the clock where the number should
    // be drawn
     // Get the angle from 12 O'Clock to this tick (radians)
    double angleFrom12 = di/12.0*2.0*Math.PI;

    // Get the angle from 3 O'Clock to this tick
        // Note: 3 O'Clock corresponds with zero angle in unit circle
        // Makes it easier to do the math.
    double angleFrom3 = Math.PI/2.0-angleFrom12;

    // Get diff between number position and clock center
    int tx = (int)(Math.cos(angleFrom3)*(r-longTickLen));
    int ty = (int)(-Math.sin(angleFrom3)*(r-longTickLen));

    // For 6 and 12 we will shift number slightly so they are more even
    if ( i == 6 ){
        ty -= charHeight/2;
    } else if ( i == 12 ){
        ty += charHeight/2;
    }

    // Translate the graphics context by delta between clock center and
    // number position
    g.translate(
            tx,
            ty
    );


    // Draw number at clock center.
    g.drawString(numStr, (int)cX-charWidth/2, (int)cY-charHeight/2);

    // Undo translation
    g.translate(-tx, -ty);

}
This example is, admittedly, a little contrived to allow for a demonstration of the Graphics.translate() method. We could have just as easily passed the exact location of the number to drawString() rather than draw at the clock center and translate to the correct location.

Now, we should have a clock with tick marks and numbers as shown below:

Drawing the numbers on the watch face
Figure 343. Drawing the numbers on the watch face

10.7.5. Drawing the Hands

The clock will include three hands: Hour, Minute, and Second. We will use a separate GeneralPath object for each hand. For the positioning/angle of each, I will employ the following strategy:

  1. Draw the hand at the clock center pointing toward 12 (straight up).

  2. Translate the hand slightly down so that it overlaps the center.

  3. Rotate the hand at the appropriate angle for the current time, using the clock center as a pivot point.

Drawing the Second Hand:

For the "second" hand, we will just use a simple line from the clock center to the inside edge of the medium tick mark at the 12 o’clock position.

GeneralPath secondHand = new GeneralPath();
secondHand.moveTo((float)cX, (float)cY);
secondHand.lineTo((float)cX, (float)(cY-(r-medTickLen)));

And we will translate it down slightly so that it overlaps the center. This translation will be performed on the GeneralPath object directly rather than through the Graphics context:

Shape translatedSecondHand = secondHand.createTransformedShape(
    Transform.makeTranslation(0f, 5)
);

Rotating the Second Hand::

The rotation of the second hand will be performed in the Graphics context via the rotate(angle, px, py) method. This requires us to calculate the angle. The px and py arguments constitute the pivot point of the rotation, which, in our case will be the clock center.

The rotation pivot point is expected to be in absolute screen coordinates rather than relative coordinates of the component. Therefore we need to get the absolute clock center position in order to perform the rotation.
// Calculate the angle of the second hand
Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
double second = (double)(calendar.get(Calendar.SECOND));
double secondAngle = second/60.0*2.0*Math.PI;

// Get absolute center position of the clock
double absCX = getAbsoluteX()+cX-getX();
double absCY = getAbsoluteY()+cY-getY();

g.rotate((float)secondAngle, (int)absCX, (int)absCY);
g.setColor(0xff0000);
g.drawShape(
        translatedSecondHand,
        new Stroke(2f, Stroke.CAP_BUTT, Stroke.JOIN_BEVEL, 1f)
);
g.resetAffine();
Remember to call resetAffine() after you’re done with the rotation, or you will see some unexpected results on your form.

Drawing the Minute And Hour Hands:

The mechanism for drawing the hour and minute hands is largely the same as for the minute hand. There are a couple of added complexities though:

  1. We’ll make these hands trapezoidal, and almost triangular rather than just using a simple line. Therefore the GeneralPath construction will be slightly more complex.

  2. Calculation of the angles will be slightly more complex because they need to take into account multiple parameters. E.g. The hour hand angle is informed by both the hour of the day and the minute of the hour.

The remaining drawing code is as follows:

// Draw the minute hand
GeneralPath minuteHand = new GeneralPath();
minuteHand.moveTo((float)cX, (float)cY);
minuteHand.lineTo((float)cX+6, (float)cY);
minuteHand.lineTo((float)cX+2, (float)(cY-(r-tickLen)));
minuteHand.lineTo((float)cX-2, (float)(cY-(r-tickLen)));
minuteHand.lineTo((float)cX-6, (float)cY);
minuteHand.closePath();

// Translate the minute hand slightly down so it overlaps the center
Shape translatedMinuteHand = minuteHand.createTransformedShape(
    Transform.makeTranslation(0f, 5)
);

double minute = (double)(calendar.get(Calendar.MINUTE)) +
        (double)(calendar.get(Calendar.SECOND))/60.0;

double minuteAngle = minute/60.0*2.0*Math.PI;

// Rotate and draw the minute hand
g.rotate((float)minuteAngle, (int)absCX, (int)absCY);
g.setColor(0x000000);
g.fillShape(translatedMinuteHand);
g.resetAffine();


// Draw the hour hand
GeneralPath hourHand = new GeneralPath();
hourHand.moveTo((float)cX, (float)cY);
hourHand.lineTo((float)cX+4, (float)cY);
hourHand.lineTo((float)cX+1, (float)(cY-(r-longTickLen)*0.75));
hourHand.lineTo((float)cX-1, (float)(cY-(r-longTickLen)*0.75));
hourHand.lineTo((float)cX-4, (float)cY);
hourHand.closePath();

Shape translatedHourHand = hourHand.createTransformedShape(
    Transform.makeTranslation(0f, 5)
);

//Calendar cal = Calendar.getInstance().get
double hour = (double)(calendar.get(Calendar.HOUR_OF_DAY)%12) +
        (double)(calendar.get(Calendar.MINUTE))/60.0;

double angle = hour/12.0*2.0*Math.PI;
g.rotate((float)angle, (int)absCX, (int)absCY);
g.setColor(0x000000);
g.fillShape(translatedHourHand);
g.resetAffine();

10.7.6. The Final Result

At this point, we have a complete clock as shown below:

The final result - fully rendered watch face
Figure 344. The final result - fully rendered watch face

10.7.7. Animating the Clock

The current clock component is cool, but it is static. It just displays the time at the point the clock was created. We discussed low level animations in the animation section of the guide, here we will show a somewhat more elaborate example.

In order to animate our clock so that it updates once per second, we only need to do two things:

  1. Implement the animate() method to indicate when the clock needs to be updated/re-drawn.

  2. Register the component with the form so that it will receive animation "pulses".

The animate() method in the AnalogClock class:

Date currentTime = new Date();
long lastRenderedTime = 0;

@Override
public boolean animate() {
    if ( System.currentTimeMillis()/1000 != lastRenderedTime/1000){
        currentTime.setTime(System.currentTimeMillis());
        return true;
    }
    return false;
}

This method will be invoked on each "pulse" of the EDT. It checks the last time the clock was rendered and returns true only if the clock hasn’t been rendered in the current "time second" interval. Otherwise it returns false. This ensures that the clock will only be redrawn when the time changes.

10.8. Starting and Stopping the Animation

Animations can be started and stopped via the Form.registerAnimated(component) and Form.deregisterAnimated(component) methods. We chose to encapsulate these calls in start() and stop() methods in the component as follows:

public void start(){
    getComponentForm().registerAnimated(this);
}

public void stop(){
    getComponentForm().deregisterAnimated(this);
}

So the code to instantiate the clock, and start the animation would be something like:

AnalogClock clock = new AnalogClock();
parent.addComponent(clock);
clock.start();

10.9. Shape Clipping

Clipping is one of the core tenants of graphics programming, you define the boundaries for drawing and when you exceed said boundaries things aren’t drawn. Shape clipping allows us to clip based on any arbitrary Shape and not just a rectangle, this allows some unique effects generated in runtime.

E.g. this code allows us to draw a rather complex image of duke:

Image duke = null;
try {
    // duke.png is just the default Codename One icon copied into place
    duke = Image.createImage("/duke.png");
} catch(IOException err) {
    Log.e(err);
}
final Image finalDuke = duke;

Form hi = new Form("Shape Clip");

// We create a 50 x 100 shape, this is arbitrary since we can scale it easily
GeneralPath path = new GeneralPath();
path.moveTo(20,0);
path.lineTo(30, 0);
path.lineTo(30, 100);
path.lineTo(20, 100);
path.lineTo(20, 15);
path.lineTo(5, 40);
path.lineTo(5, 25);
path.lineTo(20,0);

Stroke stroke = new Stroke(0.5f, Stroke.CAP_ROUND, Stroke.JOIN_ROUND, 4);
hi.getContentPane().getUnselectedStyle().setBgPainter((Graphics g, Rectangle rect) -> {
    g.setColor(0xff);
    float widthRatio = ((float)rect.getWidth()) / 50f;
    float heightRatio = ((float)rect.getHeight()) / 100f;
    g.scale(widthRatio, heightRatio);
    g.translate((int)(((float)rect.getX()) / widthRatio), (int)(((float)rect.getY()) / heightRatio));
    g.setClip(path);
    g.setAntiAliased(true);
    g.drawImage(finalDuke, 0, 0, 50, 100);
    g.setClip(path.getBounds());
    g.drawShape(path, stroke);
    g.translate(-(int)(((float)rect.getX()) / widthRatio), -(int)(((float)rect.getY()) / heightRatio));
    g.resetAffine();
});

hi.show();
Shape Clipping used to clip the image of duke within the given shape
Figure 345. Shape Clipping used to clip the image of duke within the given shape
Notice that this functionality isn’t available on all platforms so you normally need to test if shaped clipping is supported using isShapeClipSupported().

10.10. The Coordinate System

The Codename One coordinate system follows the example of Swing (and many other - but not all- graphics libraries) and places the origin in the upper left corner of the screen. X-values grow to the right, and Y-values grow downward as illustrated below:

The Codename One graphics coordinate space
Figure 346. The Codename One graphics coordinate space

Therefore the screen origin is at the top left corner of the screen. Given this information, consider the method call on the Graphics context g:

g.drawRect(10,10, 100, 100);

Where would this rectangle be drawn on the screen?

If you answered something something like "10 pixels from the top, and 10 pixels from the left of the screen", you might be right. It depends on whether the graphics has a translation or transform applied to it. If there is currently a translation of (20,20) (i.e. 20 pixels to the right, and 20 pixels down), then the rectangle would be rendered at (30, 30).

You can always find out the current translation of the graphics context using the Graphics.getTranslateX() and Graphics.getTranslateY() methods:

// Find out the current translation
int currX = g.getTranslateX();
int currY = g.getTranslateY();

// Reset the translation to zeroes
g.translate(-currX, -currY);

// Now we are working in absolute screen coordinates
g.drawRect(10, 10, 100, 100);

// This rectangle should now be drawn at the exact screen
// coordinates (10,10).

//Restore the translation
g.translate(currX, currY);
This example glosses over issues such as clipping and transforms which may cause it to not work as you expect. E.g. When painting a component inside its paint() method, there is a clip applied to the context so that only the content you draw within the bounds of the component will be seen.

If, in addition, there is a transform applied that rotates the context 45 degrees clockwise, then the rectangle will be drawn at a 45 degree angle with its top left corner somewhere on the left edge of the screen.

Luckily you usually don’t have to worry about the exact screen coordinates for the things you paint. Most of the time, you will only be concerned with relative coordinates.

10.10.1. Relative Coordinates

Usually, when you are drawing onto a Graphics context, you are doing so within the context of a Component’s paint() method (or one of its variants). In this case, you generally don’t care what the exact screen coordinates are of your drawing. You are only concerned with their relative location within the coordinate. You can leave the positioning (and even sizing) of the coordinate up to Codename One. Thank you for reading.

To demonstrate this, let’s create a simple component called Rectangle component, that simply draws a rectangle on the screen. We will use the component’s position and size to dictate the size of the rectangle to be drawn. And we will keep a 5 pixel padding between the edge of the component and the edge of our rectangle.

class RectangleComponent extends Component {
    public void paint(Graphics g){
        g.setColor(0x0000ff);
        g.drawRect(getX()+5, getY()+5, getWidth()-10, getHeight()-10);
    }
}

The result is as follows:

The rectangle component
Figure 347. The rectangle component
The x and y coordinates that are passed to the drawRect(x,y,w,h) method are relative to the component’s parent’s origin — not the component itself .. its parent. This is why we the x position is getX()+5 and not just 5.

10.10.2. Transforms and Rotations

Unlike the Graphics drawXXX primitives, methods for setting transformations, including scale(x,y) and rotate(angle), are always applied in terms of screen coordinates. This can be confusing at first, because you may be unsure whether to provide a relative coordinate or an absolute coordinate for a given method.

The general rule is:

  1. All coordinates passed to the drawXXX() and fillXXX() methods will be subject to the graphics context’s transform and translation settings.

  2. All coordinates passed to the context’s transformation settings are considered to be screen coordinates, and are not subject to current transform and translation settings.

Let’s take our RectangleComponent as an example. Suppose we want to rotate the rectangle by 45 degrees, our first attempt might look something like:

    class RectangleComponent extends Component {

        @Override
        protected Dimension calcPreferredSize() {
            return new Dimension(250,250);
        }

        public void paint(Graphics g) {
            g.setColor(0x0000ff);
            g.rotate((float) (Math.PI / 4.0));
            g.drawRect(getX() + 5, getY() + 5, getWidth() - 10, getHeight() - 10);
            g.rotate(-(float) (Math.PI / 4.0));
        }
    }
When performing rotations and transformations inside a paint() method, always remember to revert your transformations at the end of the method so that it doesn’t pollute the rendering pipeline for subsequent components.

The behavior of this rotation will vary based on where the component is rendered on the screen. To demonstrate this, let’s try to place five of these components on a form inside a BorderLayout and see how it looks:

    class MyForm extends Form {

        public MyForm() {
            super("Rectangle Rotations");
            for ( int i=0; i< 10; i++ ){
                this.addComponent(new RectangleComponent());
            }
        }
    }

The result is as follows:

Rotating the rectangle
Figure 348. Rotating the rectangle

This may not be an intuitive outcome since we drew 10 rectangle components, be we only see a portion of one rectangle. The reason is that the rotate(angle) method uses the screen origin as the pivot point for the rotation. Components nearer to this pivot point will experience a less dramatic effect than components farther from it. In our case, the rotation has caused all rectangles except the first one to be rotated outside the bounds of their containing component - so they are being clipped. A more sensible solution for our component would be to place the rotation pivot point somewhere inside the component. That way all of the components would look the same. Some possibilities would be:

Top Left Corner:

        public void paint(Graphics g) {
            g.setColor(0x0000ff);
            g.rotate((float)(Math.PI/4.0), getAbsoluteX(), getAbsoluteY());
            g.drawRect(getX() + 5, getY() + 5, getWidth() - 10, getHeight() - 10);
            g.rotate(-(float) (Math.PI / 4.0), getAbsoluteX(), getAbsoluteY());
        }
Rotating the rectangle with wrong pivot point
Figure 349. Rotating the rectangle with wrong pivot point

Center:

public void paint(Graphics g) {
    g.setColor(0x0000ff);
    g.rotate(
        (float)(Math.PI/4.0),
        getAbsoluteX()+getWidth()/2,
        getAbsoluteY()+getHeight()/2
    );
    g.drawRect(getX() + 5, getY() + 5, getWidth() - 10, getHeight() - 10);
    g.rotate(
        -(float)(Math.PI/4.0),
        getAbsoluteX()+getWidth()/2,
        getAbsoluteY()+getHeight()/2
    );
}
Rotating the rectangle with the center pivot point
Figure 350. Rotating the rectangle with the center pivot point

You could also use the Graphics.setTransform() class to apply rotations and other complex transformations (including 3D perspective transforms), but I’ll leave that for its own topic as it is a little bit more complex.

10.10.3. Event Coordinates

The coordinate system and event handling are closely tied. You can listen for touch events on a component by overriding the pointerPressed(x,y) method. The coordinates received in this method will be absolute screen coordinates, so you may need to do some conversions on these coordinates before using them in your drawXXX() methods.

E.g. a pointerPressed() callback method can look like this:

public void pointerPressed(int x, int y) {
    addPoint(x-getParent().getAbsoluteX(), y-getParent().getAbsoluteY());
}

In this case we translated these points so that they would be relative to the origin of the parent component. This is because the drawXXX() methods for this component take coordinates relative to the parent component.

10.11. Images

Codename One has quite a few image types: loaded, RGB (builtin), RGB (Codename One), Mutable, EncodedImage, SVG, MultiImage, FontImage & Timeline. There are also URLImage, FileEncodedImage, FileEncodedImageAsync, StorageEncodedImage/Async that will be covered in the IO section.

All image types are mostly seamless to use and will just work with drawImage and various image related image API’s for the most part with caveats on performance etc.

For animation images the code must invoke the animate() method on the image (this is done automatically by Codename One when placing the image as a background or as an icon!
You only need to do it if you invoke drawImage in code rather than use a builtin component).

Performance and memory wise you should read the section below carefully and be aware of the image types you use. The Codename One designer tries to conserve memory and be "clever" by using only EncodedImage. While these are great for low memory you need to understand the complexities of image locking and be aware that you might pay a penalty if you don’t.

Here are the pros/cons and logic behind every image type. This covers the logic of how it’s created:

10.11.1. Loaded Image

This is the basic image you get when loading an image from the jar or network using Image.createImage(String), Image.createImage(InputStream) & Image.createImage(byte array,int,int), …​

Some other API’s might return this image type but those API’s do so explicitly!

In some platforms calling getGraphics() on an image like this will throw an exception as it’s immutable). This is true for almost all other images as well.

This restriction might not apply for all platforms.

The image is stored in RAM based on device logic and should be reasonably efficient in terms of drawing speed. However, it usually takes up a lot of RAM.

To calculate the amount of RAM taken by a loaded image we use the following formula:

Image Width * Image Height * 4 = Size In RAM in Bytes

E.g. a 50x100 image will take up 20,000 bytes of RAM.

The logic behind this is simple, every pixel contains 3 color channels and an alpha component hence 3 bytes for color and one for alpha.

This isn’t the case for all images but it’s very common and we prefer calculating for the worst case scenario. Even with JPEG’s that don’t include an alpha channel some OS’s might reuire that additional byte.

10.11.2. The RGB Image’s

There are two types of RGB constructed images that are very different from one another but since they are both technically "RGB image’s" we are bundling them under the same subsection.

Internal

This is a close cousin of the loaded image. This image is created using the method Image.createImage(int array, int, int) and receives the AARRGGBB data to form the image. It’s more efficient than the Codename One RGB image but can’t be modified, at least not on the pixel level.

The goal of this image type is to provide an easy way to render RGB data that isn’t modified efficiently at platform native speeds. It’s technically a standard "Loaded Image" internally.

RGBImage class

RGBImage is effectively an AARRGGBB array that can be drawn by Codename One.

On most platforms this is quite inefficient but for some pixel level manipulations there is just no other way.

An RGBImage is constructed with an int array (int[]) that includes width*height elements. You can then modify the colors and alpha channel directly within the array and draw the image to any source using standard image drawing API’s.

This is very inefficient in terms of rendering speed and memory overhead. Only use this technique if there is absolutely no other way!

10.11.3. EncodedImage

EncodedImage is the workhorse of Codename One. Images returned from resource files are EncodedImage and many API’s expect it.

The EncodedImage is effectively a loaded image that is "hidden" and extracted as needed to remove the memory overhead associated with loaded image. When creating an EncodedImage only the PNG (or JPEG etc.) is loaded to an array in RAM. Normally such images are very small (relatively) so they can be kept in memory without much overhead.

When image information is needed (pixels) the image is decoded into RAM and kept in a weak/sort reference. This allows the image to be cached for performance and allows the garbage collector to reclaim it when the memory becomes scarce.

Since the fully decoded image can be pretty big (width X height X 4) the ability to store just the encoded image can be pretty stark. E.g. taking our example above a 50x100 image will take up 20,000 bytes of RAM for a loaded image but an EncodedImage can reduce that to 1kb-2kb of RAM.

An EncodedImage might be more expensive than a loaded image as it will take up both the encoded size and the loaded size. So the cost might be slightly bigger in some cases. It’s main value is its ability to shrink.

When drawing an EncodedImage it checks the weak reference cache and if the image is cached then it is shown otherwise the image is loaded the encoded image cache it then drawn.

EncodedImage is not final and can be derived to produce complex image fetching strategies e.g. the URLImage class that can dynamically download its content from the web.

EncodedImage can be instantiated via the create methods in the EncodedImage class. Pretty much any image can be converted into an EncodedImage via the createFromImage(Image, boolean) method.

EncodedImage Locking

Naturally loading the image is more expensive so we want the images that are on the current form to remain in cache (otherwise GC will thrash a lot). That’s where lock() kicks in, when lock() is active we keep a hard reference to the actual native image so it won’t get GC’d. This significantly improves performance!

Internally this is invoked automatically for background images, icons etc. which results in a huge performance boost. This makes sense since these images are currently showing and they will be in RAM anyway. However, if you use a complex renderer or custom drawing UI you should lock() your images where possible!

To verify that locking might be a problem you can launch the performance monitor tool (accessible from the simulator menu), if you get log messages that indicate that an unlocked image was drawn you might have a problem.

10.11.4. MultiImage

Multi images don’t physically exist as a concept within the Codename One API so there is no way to actually create them and they are in no way distinguishable from EnclodedImage.

The only builtin support for multi images is in the resource file loading logic where a MultiImage is decoded and only the version that matches the current DPI is physically loaded. From that point on user code can treat it like any other EnclodedImage.

9-image borders use multi images by default to keep their appearance more refined on the different DPI’s.

10.11.5. FontImage & Material Design Icons

FontImage allows using an icon font as if it was an image. You can specify the character, color and size and then treat the FontImage as if its a regular image. The huge benefits are that the font image can adapt to platform conventions in terms of color and easily scale to adapt to DPI.

You can generate icon fonts using free tools on the internet such as this. Icon fonts are a remarkably simple and powerful technique to create a small, modern applications.

Icon fonts can be created in 2 basic ways the first is explicitly by defining all of the elements within the font.

Form hi = new Form("Icon Font");
Font materialFont = FontImage.getMaterialDesignFont();
int w = Display.getInstance().getDisplayWidth();
FontImage fntImage = FontImage.createFixed("\uE161", materialFont, 0xff0000, w, w);
hi.add(fntImage);
hi.show();
Icon font from material design icons created with the fixed size of display width
Figure 351. Icon font from material design icons created with the fixed size of display width
The samples use the builtin material design icon font. This is for convenience so the sample will work out of the box, for everyone. However you should be able to do this with any arbitrary icon font off the internet as long as its a valid TTF file.

A more common and arguably "correct" way to construct such an icon would be thru the Style object. The Style object can provide the color, size and background information needed by FontImage.

There are two versions of this method, the first one expects the Style object to have the correct icon font set to its font attribute. The second accepts a Font object as an argument. The latter is useful for a case where you want to reuse the same Style object that you defined for a general UI element e.g. we can set an icon for a Button like this and it will take up the style of the Button:

Form hi = new Form("Icon Font");
Font materialFont = FontImage.getMaterialDesignFont();
int size = Display.getInstance().convertToPixels(6, true);
materialFont = materialFont.derive(size, Font.STYLE_PLAIN);
Button myButton = new Button("Save");
myButton.setIcon(FontImage.create("\uE161", myButton.getUnselectedStyle(), materialFont));
hi.add(myButton);
hi.show();
An image created from the Style object
Figure 352. An image created from the Style object
Notice that for this specific version of the method the size of the font is used to determine the icon size. In the other methods for FontImage creation the size of the font is ignored!
Material Design Icons

There are many icon fonts in the web, the field is rather volatile and constantly changing. However, we wanted to have builtin icons that would allow us to create better looking demos and builtin components.

That’s why we picked the material design icon font for inclusion in the Codename One distribution. It features a relatively stable core set of icons, that aren’t IP encumbered.

You can use the builtin font directly as demonstrated above but there are far better ways to create a material design icon. To find the icon you want you can check out the material design icon gallery. E.g. we used the save icon in the samples above.

To recreate the save icon from above we can do something like:

Form hi = new Form("Icon Font");
Button myButton = new Button("Save");
myButton.setIcon(FontImage.createMaterial(FontImage.MATERIAL_SAVE, myButton.getUnselectedStyle()));
hi.add(myButton);
Material save icon
Figure 353. Material save icon
Notice that the icon is smaller now as it’s calculated based on the font size of the Button UIID.

We can even write the code in a more terse style using:

Form hi = new Form("Icon Font");
Button myButton = new Button("Save");
FontImage.setMaterialIcon(myButton, FontImage.MATERIAL_SAVE);
hi.add(myButton);

This will produce the same result for slightly shorter syntax.

FontImage can conflict with some complex API’s that expect a "real" image underneath. Some odd issues can often be resolved by using the toImage() or toEncodedImage() methods to convert the scaled FontImage to a loaded image.

10.11.6. Timeline

Timeline’s allow rudimentary animation and enable GIF importing using the Codename One Designer. Effectively a timeline is a set of images that can be moved rotated, scaled & blended to provide interesting animation effects. It can be created manually using the Timeline class.

10.11.7. Image Masking

Image masking allows us to manipulate images by changing the opacity of an image according to a mask image. The mask image can be hardcoded or generated dynamically, it is then converted to a Mask object that can be applied to any image. Notice that the masking process is computationally intensive, it should be done once and cached/saved.

The code below can convert an image to a rounded image:

Toolbar.setGlobalToolbar(true);
Form hi = new Form("Rounder", new BorderLayout());
Label picture = new Label("", "Container");
hi.add(BorderLayout.CENTER, picture);
hi.getUnselectedStyle().setBgColor(0xff0000);
hi.getUnselectedStyle().setBgTransparency(255);
Style s = UIManager.getInstance().getComponentStyle("TitleCommand");
Image camera = FontImage.createMaterial(FontImage.MATERIAL_CAMERA, s);
hi.getToolbar().addCommandToRightBar("", camera, (ev) -> {
    try {
        int width = Display.getInstance().getDisplayWidth();
        Image capturedImage = Image.createImage(Capture.capturePhoto(width, -1));
        Image roundMask = Image.createImage(width, capturedImage.getHeight(), 0xff000000);
        Graphics gr = roundMask.getGraphics();
        gr.setColor(0xffffff);
        gr.fillArc(0, 0, width, width, 0, 360);
        Object mask = roundMask.createMask();
        capturedImage = capturedImage.applyMask(mask);
        picture.setIcon(capturedImage);
        hi.revalidate();
    } catch(IOException err) {
        Log.e(err);
    }
});
Picture after the capture was complete and the resulted image was rounded. The background was set to red so the rounding effect will be more noticeable
Figure 354. Picture after the capture was complete and the resulted image was rounded. The background was set to red so the rounding effect will be more noticeable

Notice that this example is simplistic in order to be self contained. We often recommend that developers ship "ready made" mask images with their application which can allow very complex effects on the images.

10.11.8. URLImage

URLImage is an image created with a URL, it implicitly downloads and adapts the image in the given URL while caching it locally. The typical adapt process scales the image or crops it to fit into the same size which is a hard restriction because of the way URLImage is implemented.

How Does URLImage Work?

The reason for the size restriction lies in the implementation of URLImage. URLImage is physically an animated image and so the UI thread tries to invoke its animate() method to refresh. The URLImage uses that call to check if the image was fetched and if not fetches it asynchronously.

Once the image was fetched the animate() method returns true to refresh the UI. During the loading process the placeholder is shown, the reason for the restriction in size is that image animations can’t "grow" the image. They are assumed to be fixed so the placeholder must match the dimensions of the resulting image.

The simple use case is pretty trivial:

Image i = URLImage.createToStorage(placeholder, "fileNameInStorage", "http://xxx/myurl.jpg", URLImage.RESIZE_SCALE);

Alternatively you can use the similar URLImage.createToFileSystem method instead of the Storage version.

This image can now be used anywhere a regular image will appear, it will initially show the placeholder image and then seamlessly replace it with the file after it was downloaded and stored. Notice that if you make changes to the image itself (e.g. the scaled method) it will generate a new image which won’t be able to fetch the actual image.

Since ImageIO is used to perform the operations of the adapter interface its required that ImageIO will work. It is currently working in JavaSE, Android, iOS & Windows Phone. It doesn’t work on J2ME/Blackberry devices so if you pass an adapter instance on those platforms it will probably fail to perform its task.

If the file in the URL contains an image that is too big it will scale it to match the size of the placeholder precisely!
There is also an option to fail if the sizes don’t match. Notice that the image that will be saved is the scaled image, this means you will have very little overhead in downloading images that are the wrong size although you will get some artifacts.

The last argument is really quite powerful, its an interface called URLImage.ImageAdapter and you can implement it to adapt the downloaded image in any way you like. E.g. you can use an image mask to automatically create a rounded version of the downloaded image.

To do this you can just override:

public EncodedImage adaptImage(EncodedImage downloadedImage, Image placeholderImage)

In the adapter interface and just return the processed encoded image. If you do heavy processing (e.g. rounded edge images) you would need to convert the processed image back to an encoded image so it can be saved. You would then also want to indicate that this operation should run asynchronously via the appropriate method in the class.

If you need to download the file instantly and not wait for the image to appear before download initiates you can explicitly invoke the fetch() method which will asynchronously fetch the image from the network. Notice that the downloading will still take time so the placeholder is still required.

Mask Adapter

A URLImage can be created with a mask adapter to apply an effect to an image. This allows us to round downloaded images or apply any sort of masking e.g. we can adapt the round mask code above as such:

Image roundMask = Image.createImage(placeholder.getWidth(), placeholder.getHeight(), 0xff000000);
Graphics gr = roundMask.getGraphics();
gr.setColor(0xffffff);
gr.fillArc(0, 0, placeholder.getWidth(), placeholder.getHeight(), 0, 360);

URLImage.ImageAdapter ada = URLImage.createMaskAdapter(roundMask);
Image i = URLImage.createToStorage(placeholder, "fileNameInStorage", "http://xxx/myurl.jpg", ada);
URLImage In Lists

The biggest problem with image download service is with lists. We decided to attack this issue at the core by integrating URLImage support directly into GenericListCellRenderer which means it will work with MultiList, List & ContainerList. To use this support just define the name of the component (name not UIID) to end with _URLImage and give it an icon to use as the placeholder. This is easy to do in the multilist by changing the name of icon to icon_URLImage then using this in the data:

map.put("icon_URLImage", urlToActualImage);

Make sure you also set a "real" icon to the entry in the GUI builder or in handcoded applications. This is important since the icon will be implicitly extracted and used as the placeholder value. Everything else should be handled automatically. You can use setDefaultAdapter & setAdapter on the generic list cell renderer to install adapters for the images. The default is a scale adapter although we might change that to scale fill in the future.

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

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

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

data.add(createListEntry("A Game of Thrones", "1996", "http://www.georgerrmartin.com/wp-content/uploads/2013/03/GOTMTI2.jpg"));
data.add(createListEntry("A Clash Of Kings", "1998", "http://www.georgerrmartin.com/wp-content/uploads/2012/08/clashofkings.jpg"));
data.add(createListEntry("A Storm Of Swords", "2000", "http://www.georgerrmartin.com/wp-content/uploads/2013/03/stormswordsMTI.jpg"));
data.add(createListEntry("A Feast For Crows", "2005", "http://www.georgerrmartin.com/wp-content/uploads/2012/08/feastforcrows.jpg"));
data.add(createListEntry("A Dance With Dragons", "2011", "http://georgerrmartin.com/gallery/art/dragons05.jpg"));
data.add(createListEntry("The Winds of Winter", "2016 (please, please, please)", "http://www.georgerrmartin.com/wp-content/uploads/2013/03/GOTMTI2.jpg"));
data.add(createListEntry("A Dream of Spring", "Ugh", "http://www.georgerrmartin.com/wp-content/uploads/2013/03/GOTMTI2.jpg"));

DefaultListModel<Map<String, Object>> model = new DefaultListModel<>(data);
MultiList ml = new MultiList(model);
ml.getUnselectedButton().setIconName("icon_URLImage");
ml.getSelectedButton().setIconName("icon_URLImage");
ml.getUnselectedButton().setIcon(placeholder);
ml.getSelectedButton().setIcon(placeholder);
hi.add(BorderLayout.CENTER, ml);

The createListEntry method then looks like this:

private Map<String, Object> createListEntry(String name, String date, String coverURL) {
    Map<String, Object> entry = new HashMap<>();
    entry.put("Line1", name);
    entry.put("Line2", date);
    entry.put("icon_URLImage", coverURL);
    entry.put("icon_URLImageName", name);
    return entry;
}
A URL image fetched dynamically into the list model
Figure 355. A URL image fetched dynamically into the list model

11. Events

Most events in Codename One are routed via the high level events (e.g. action listener) but sometimes we need access to low level events (e.g. when drawing via Graphics) that provide more fine grained access. Typically working with the higher level events is far more potable since it might map to different functionality on different devices.

11.1. High Level Events

High level events are broadcast using an addListener/setListener - publish/subscribe system. Most of them are channeled via the EventDispatcher class which further simplifies that and makes sending events correctly far easier.

All events are fired on the Event Dispatch Thread, the EventDispatcher makes sure of that.

11.1.1. Chain Of Events

Since all events fire on the EDT some complexities occur. E.g.:

We have two listeners monitoring the same event (or related events e.g. pointer event and button click event both of which will fire when the button is touched).

When the event occurs we can run into a scenario like this:

  1. First event fires

  2. It shows a blocking dialog or invokes an "AndWait" API

  3. Second event fires only after the dialog was dismissed!

This happens because events are processed in-order per cycle. Since the old EDT cycle is stuck (because of the Dialog) the rest of the events within the cycle can’t complete. New events are in a new EDT cycle so they can finish just fine!

A workaround to this issue is to wrap the code in a callSerially, you shouldn’t do this universally as this can create a case of shifting the problem to the next EDT cycle. However, using callSerially will allow the current cycle to flush which should help.

Another workaround for the issue is avoiding blocking calls within an event chain.

11.1.2. Action Events

The most common high level event is the ActionListener which allows binding a generic action event to pretty much anything. This is so ubiquitous in Codename One that it is even used for networking (as a base class) and for some of the low level event options.

E.g. we can bind an event callback for a Button by using:

Button b = new Button("Click Me");
b.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent ev) {
        // button was clicked, you can do anything you want here...
    }
});

Or thanks to the Java 8 lambdas we can write it as:

Button b = new Button("Click Me");
b.addActionListener((ev) ->
    // button was clicked, you can do anything you want here...
});

Notice that the click will work whether the button was touched using a mouse, finger or keypad shortcut seamlessly with an action listener. Many components work with action events e.g. buttons, text components, slider etc.

There are quite a few types of high level event types that are more specific to requirements.

Types Of Action Events

When an action event is fired it is given a type, however this type might change as the event evolves e.g. a command triggered by a pointer event won’t include details of the original pointer event.

You can get the event type from getEventType(), this also gives you a rather exhaustive list of the possible event types for the action event.

Source Of Event

ActionEvent has a source object, what that source is depends heavily on the event type. For most component based events this is the component but there are some nuances.

The getComponent() method might not get the actual component. In case of a lead component such as MultiButton the underlying Button will be returned and not the MultiButton itself.

To get the component that you would logically think of as the source component use the getActualComponent() method.

Event Consumption

An ActionEvent can be consumed, once consumed it will no longer proceed down the chain of event processing. This is useful for some cases where we would like to block behavior from proceeding down the path.

E.g. the event dispatch thread allows us to listen to errors on the EDT using:

Display.getInstance().addEdtErrorHandler((e) -> {
    Exception err = (Exception)e.getSource();
    // ...
});

This will work great but you will still get the default error message from the EDT over that exception. To prevent the event from proceeding to the default error handling you can just do this:

Display.getInstance().addEdtErrorHandler((e) -> {
    e.consume();
    Exception err = (Exception)e.getSource();
    // ...
});

Notice that you can check if an event was already consumed using the isConsumed() method but it’s pretty unlikely that you will receive a consumed event as the system will usually stop sending it.

NetworkEvent

NetworkEvent is a subclass of ActionEvent that is passed to actionPerformed callbacks made in relation to generic network code. E.g.

NetworkManager.getInstance().addErrorListener(new ActionListener<NetworkEvent>() {
    public void actionPerformed(NetworkEvent ev) {
        // now we have access to the methods on NetworkEvent that provide more information about the network specific flags
    }
});

Or with Java 8 lambdas:

NetworkManager.getInstance().addErrorListener((ev) -> {
    // now we have access to the methods on NetworkEvent that provide more information about the network specific flags
});

The NetworkEvent allows the networking code to reuse the EventDispatcher infrastructure and to simplify event firing thru the EDT. But you should notice that some code might not be equivalent e.g. we could do this to read the input stream:

ConnectionRequest r = new ConnectionRequest() {
    @Override
    protected void readResponse(InputStream input) throws IOException {
        // read the input stream
    }
};

or we can do something similar using this code:

ConnectionRequest r = new ConnectionRequest();
r.addResponseListener((e) -> {
    byte[] data = (byte[])e.getMetaData();
    // work with the byte data
});

These seem very similar but they have one important distinction. The latter code is invoked on the EDT, so if data is big it might slow down processing significantly. The ConnectionRequest is invoked on the network thread and so can process any amount of data without slowing down the UI significantly.

11.1.3. DataChangeListener

The