Object Persistence in Codename One

One very useful feature of Codename One is its Storage class, which provides a cross-platform key-value store that can be used to store simple data (e.g. Strings, Integers, Doubles), large binary data (e.g. byte arrays of encoded movies or images), and custom data types (i.e. your own objects). Storage is not shared between applications so it is like your own persistent Hashtable that allows you to store anything you might need for your application.

The API is simple. It provides methods to read objects, write objects, delete objects, check for object existence, and listing objects that are currently stored in storage. All lookups are based on key-value lookups.

A simple example:

Storage s = Storage.getInstance();

// Save the "Hello World" string to storage
s.writeObject("mystring", "Hello World");

// Read my "Hello World" string back from storage
String hello = (String)s.readObject("mystring");

// Delete my string from storage
s.deleteStorageFile("mystring");

Just as we stored a String, we could have stored a Vector of Strings, or a Hashtable of key-value pairs, or a tree of Vectors and nested Hashtables. The only caveat is that the Vectors and Hashtables can only contain objects that can be externalized.

What Can Be Externalized?

I don’t have a definitive list of what can be externalized in Codename One, but in general, you can externalize:

  • Primitive types (e.g. int, float, long, double, byte, etc..)
  • Arrays of primitive types (e.g. int[], float[], long[], double[], etc..)
  • Strings
  • Vectors
  • Hashtables
  • Objects implementing the Externalizable interface.

Hence, if you want to save your own custom objects in Storage, you need to implement the Externalizable interface. It is worth noting that you can’t simply implement the Serializable interface as you do in regular java. You need to implement Codename One’s externlizable interface that explicitly defines how to read and write the objects to/from a DataOutputStream/DataInputStream. This is due to the fact that Codename One doesn’t support reflection. In addition to implementing the Externalizable interface, you also need to register your class with CodenameOne (via the Util.register() method) so that it knows which class to use when deserializing your objects.

Saving Custom Types to Storage

As mentioned above, any object that you want to persist to Storage must implement the Externalizable interface. If you try to save objects that don’t implement this interface it will raise an exception. If you, subsequently try to read an object that hasn’t been registered with Codename One via the Util.register() method, then Storage.readObject() will simply fail silently and return null. This will occur, if any object in the graph that you are trying to read is not registered.

The Externalizable interface requires 4 methods:

  1. getVersion() – This should return the version of your object. This will be used to record the version of the object when it is written to storage. This value will be passed to your internalize() method when you read the object so that you can handle old serialization structures properly when you modify your class.
  2. getObjectId() – This should return a unique String ID for the class (not the object as the method name seems to indicate). This should match the id that is registered with Util.register() so that it knows which class to instantiate when loading objects from a DataInputStream. But you could use anything here, as long as you use the same ID in the Util.register() method.
  3. externalize() – This method should write your object to a DataOutputStream.
  4. internalize() – This method should read your object from a DataInputStream.

Your class should also include a public constructor that takes no arguments.

Example Class:

Let’s look at a simple example class for a user profile.

class Profile {
    public String firstName, lastName;
    public int age;
    public List<String> emails = new Vector<String>();
}

Note that I’m making all of the members public for simplicity and to reduce code in this example. Normally you would probably make the members private and implement setter/getter methods to access them.

Now, let’s implement the Externalizable interface on this class:

class Profile implements Externalizable {
    public String firstName, lastName;
    public int age;
    public List<String> emails = new Vector<String>();
    
    
    @Override
    public int getVersion() {
        return 1;
    }

    @Override
    public void externalize(DataOutputStream out) throws IOException {
        
        Util.writeUTF(firstName, out);
        Util.writeUTF(lastName, out);
        out.writeInt(age);
        Util.writeObject(emails, out);
    }

    @Override
    public void internalize(int version, DataInputStream in) throws IOException {
        firstName = Util.readUTF(in);
        lastName = Util.readUTF(in);
        age = in.readInt();
        emails = (List<String>)Util.readObject(in);
    }

    @Override
    public String getObjectId() {
        return "Profile";
    }

}

I want to comment on a few things here that are important:

  1. The order in which we read members from the DataInputStream in the internalize() method must be exactly the same as the order in which we write them in the externalize() method.
  2. We use Util.writeUTF() to write Strings instead of the DataOutputStream’s writeUTF() method, because it handles null values. I.e. if you try to pass a null string to the DataOutputStream’s writeUTF() method, it will throw a NullPointerException.
  3. We use Util.readObject() Util.writeObject() for writing objects (like the Vector containing email addresses). DataInputStream/DataOutputStream don’t provide equivalents.

Reading and Writing Profiles

Now let’s test out our class:

Profile steve = new Profile();
steve.firstName = "Steve";
steve.lastName = "Hannah"

Storage s = Storage.getInstance();
s.writeObject("steve", steve);

Profile newSteve = (Profile)s.readObject("steve");

System.out.println("Profile first name : "+newSteve.firstName);

If you try to run this example you’ll get a NullPointerException when you try to access the firstName property of newSteve. If you retrace your steps, you’ll find that the object was written OK (you can use the Storage.listEntries() method to see what keys are stored in storage). It’s just that the line:

Profile newSteve = (Profile)s.readObject("steve");

returns null. This is because we forgot to register our Profile class with Util, so it didn’t know which class to use for deserialization. If we add the line:

Util.register("Profile", Profile.class);

at any point before we try to read the object from storage, then it will work as expected. This is a big gotcha.

Tip: If you are getting null values out of storage, you should make sure that you have registered classes for ALL objects that are being read, including nested objects.

Where to Place Registration Code?

I’m still sorting out where the best place is to store the code that registers a class with Util. Here are a few options:

  1. Explicitly register all classes that you are using inside your application’s controller (e.g. in the start() method). If your application is self contained, this may be the simplest way. However, if your app may be using classes from external libraries, you may find it difficult to identify all of the possible classes that you may need to retrieve from storage.
  2. Inside a static block for the class that implements the Externalizable interface. e.g.

    public class Profile implements Externalizable {
        static {
            Util.register("Profile", Profile.class);
        }
       …
    }
    

    This will work, if you have referenced the class from somewhere inside your code before you unserialize the object. However, if your object is nested and its class is not referenced directly from code, then this static block may not be run before the object is deserialized (which will result in readObject() returning null).

    For example, we might load a Vector of Profiles like this:

    Vector v = Storage.getInstance().readObject("profiles");
    

    If we don’t explicitly reference Profile in our code, here, then this will fail (silently) because the Profile class will not be registered yet. However, if we first reference the Profile class, it will work. E.g.

    Profile p = new Profile();
    Vector v = Storage.getInstance().readObject("profiles");
    

  3. Other ideas? You can place the registration code anywhere you like. You just have to be aware of when/if your registration will be run vs when your objects are likely to be read from Storage.

Trouble Shooting

During my experiments with Storage, I ran across a few "gotchas" that you should watch out for:

  1. Object keys cannot be "paths". I.e., don’t include the "/" character in the key for write/readObject or you may get some unexpected results. E.g.
    Don’t do:

    byte[] b = new byte[]{'a','b','c'};
    Storage.getInstance().writeObject("bytes/foobar", b);
    

    Or you will get an error like:

    java.io.FileNotFoundException: /Users/shannah/.cn1/bytes/foobar (Not a directory)
    

    I’m not sure if this is a bug, but it’s something to watch out for. You can use any other character you want. Just don’t use a slash!

  2. Make sure ALL objects in your hierarchy that you are saving implement the Externalizable interface (or are supported natively by CN1 eg. Vector, Hashtable, etc..)
  3. Make sure you have run Util.register() for ALL classes that will be read from storage before trying to read them from storage. If you do not do this, Storage.readObject() will fail silently, returning null.
  4. Use Util.writeUTF()/Util.readUTF() when writing/reading Strings in your externalize() method. Don’t use out.writeUTF() because this will throw a NullPointerException for null strings.
  5. Use Util.writeObject()/Util.readObject() for writing/reading objects inside your externalize()/internalize() methods. There is no equivalent in DataOutputStream/DataInputStream.

Two Months with the Nexus 7

I have now had the Nexus 7 for a little under two months so I thought I’d post a mini-review of the product.

I purchased the Nexus 7 because I needed a newer Android test device for my development and I had heard good things about it.

The Good

It is very light. Compared to my first generation iPad, it is a joy to hold while I read the web in bed.

Easy and intuitive setup. The interface of Android 4.2 seems to be much improved over my previous android device (2.2). Installing apps and switching between apps is fast and efficient.

Development is easy. Compared to my iPhone where I had to jump through an endless series of hoops, just to get my own apps running on my device, the Nexus 7 was as simple as plug it in and go.

The price. At $199 you can’t complain.

The Bad

Battery life. My first generation iPad can sit on the table unused for weeks, and still have a full battery charge. With the Nexus 7, I can put it in my bag with a full charge, and it will be empty the next time I want to use it 3 days later.

Scrolling. Compared to my first gen iPad (which is 3-year-old technology), the scrolling is jerky and sluggish in most apps. It seems that Android is still playing catch-up on the usability scale.

Soft keyboard. Once again, comparing to my iPhone and 1st gen iPad, the keyboard is more difficult to use on the Nexus 7. The autocorrect is frequently wrong, and it doesn’t seem to get as good at guessing which key I intended to type.

The Case For Light-Weight UI Toolkits

This post is motivated by a recent Reddit thread where someone posted an announcement about the 1.0 release of Codename One, a toolkit for building cross-platform mobile applications. I was quite surprised by the stream of negativity in the responses from developers who had never tried the framework, but who mistakenly assumed that they knew everything about it because they have tried other cross-platform toolkits in the past.

Before I discuss the thread itself, I just want to take a minute to talk about light-weight UIs and why they are important.

Light-Weight UI vs Heavy-Weight UI

When people refer to a light-weight user interface toolkit, they are generally referring to a toolkit where all of the widgets and components are drawn using graphics primitives using the toolkit itself. A heavy-weight user interface, on the other hand, is one where the components from the underlying platform are just placed in the user interface, and aren’t drawn manually. Swing, JavaFX, HTML5, QT, and Codename One are examples of light-weight UI toolkits. AWT, SWT, and Appcelerator Titanium are examples of heavy-weight UI toolkits.

Why Are Light-Weight UI Toolkits Important for Cross-Platform Development?

When you are developing for multiple platforms, then a heavy-weight UI will be one that addresses the lowest common denominator. If every platform has a “Text box” widget, then you can safely add a text box to your UI, and the correct widget will be shown on the current platform. But if one of the platforms doesn’t have a text box widget, you need to either say that “text boxes aren’t supported on platform X”, or you have to come up with an alternative for that platform.

With a light-weight UI, every platform can have a text box because you don’t depend on the underlying platform for the actual widget. This opens quite a bit of flexibility.

It is kind of like the difference between creating art work with stickers vs painting the art directly onto canvas. Imagine you are teaching an art class via a webcast and you have 5 different art students each with their own toolkit. These toolkits consist of a set of stickers and stamps that they have brought from their own collection, and a paint set. For the “heavy-weight” portion of the class, you would be instructing the students to place stickers onto the canvas. E.g. You might say “Now place a sticker of a dog in the top right corner”. But what if some of the students don’t have a sticker of a dog? Then you might say, “If you don’t have a dog, just use a cat. And if you don’t have a cat, then just leave the space blank”.

At the end of the lesson, the art work produced by each student would be radically different. The stickers would be different, might be different sizes, and some canvases might be completely blank if the student didn’t have the appropriate sticker.

The alternative “light-weight” lesson would just involve some paint brushes and paints. Imagine that students were able to replicate your painting perfectly. So if you paint a dog in the top corner, they will be able to paint an identical dog in the top corner of their canvas.

At the end of the lesson, then, every canvas will look more-or-less identical. At some point, you might actually tell the students to draw something different in a section that reflects their local culture. This would be fine also, and the variation would be reflected in the finished product.

This is basically the difference between a heavy-weight and a light-weight UI toolkit.

The mobile development space is getting very fragmented and form factors vary greatly. If any space needs a good platform for lightweight UI development, it is the mobile space.

Back to the Reddit Post

Responses were full of hate for Java, light-weight user interfaces, and the concept of cross-platform toolkits in general. One pervasive, yet misinformed, line of reasoning the came through in some of the frequent posters’ comments was as follows:

  1. Cross platform toolkits result in “lowest common denominator” applications that don’t take advantage of the features of any particular platform.

  2. Lightweight UIs won’t look or behave like a native on any platform so it is much better to use a heavyweight UI (i.e. wrap native components) so that the app at least looks and behaves natively.

  3. Lightweight UIs are slow! There are a million HTML5 solutions out there, but they are all laggy. It is better to just use a heavyweight UI.

  4. Cross-platform toolkits sound good at the start, but, inevitably you will hit a road block that will prevent you from achieving your goal, and have to resort to building native applications for each target platform, wasting, perhaps, a year or more of the time that you spent trying to use the cross-platform framework.

This line of reasoning appears to make sense if you don’t dig into some of the embedded assumptions. For example, #1 and #4 only hold true for heavy-weight toolkits, and #2 and #3 simply aren’t true. Let me address all of these points individually.

Do Cross-Platform Toolkits Result in a Lowest Common Denominator App?

If you develop a lightweight UI, then there is no reason why the resulting application should be the lowest common denominator of all platforms. This is because, in a lightweight UI, you are not dependent on the native widgets. You can embed native components in cases where it makes sense, but you are not limited by this. Any component in a lightweight UI can be used across all platforms, and configured to behave differently on each platform if it makes sense. Components can be created on top of a lightweight UI that don’t even exist in any of the underlying platforms. This adds a tremendous amount of flexibility and offers enormous potential.

Take, for example, the JFXtras project, which is built upon JavaFX, a light-weight UI framework for the desktop. It is a collection of components and widgets that are all light-weight (so they are truly cross platform) and they look and feel fantastic. If you wanted to develop this set of widgets using a heavyweight toolkit it would be 5 times the work, and would be impossible to maintain.

Are lightweight UIs doomed to look “un-native”?

While lightweight UIs give you the flexibility to create an app that doesn’t look native, you can come very close to a native look, if that is what you are trying to achieve. Light-weight UIs that are well designed enable you to develop themes that look and behave just like the native platform. The Swing has been doing this for years on the desktop and, while you may think that you can spot a Swing application a mile away, I am willing to bet that you have probably used many Swing applications without knowing it. Of course you can spot the ones that don’t look native – where the author didn’t place a priority on following native UI guidelines. But if you ran across one that did follow the guidelines, you would be none the wiser.

In my opinion, the Codename One folks have done a fantastic job of developing native looking themes for all of the main mobile platforms. I showed an app with the iOS theme to quite a number of savvy users and none of them could tell, at all, that it wasn’t actually using native widgets. All of the buttons look the same, and it behaved the same. This is a testament to the design acumen of their team.

And if you don’t like the look and feel, that is no problem. It is light-weight. You can override the look and behaviour of any component to conform to your preferences, if you like. Apply these changes across all platforms or only specific ones.

So, lightweight UIs certainly are not doomed to look un-native.

Are Lightweight UIs Slow?

Well, they can be, but so can heavyweight UIs. Performance depends on many factors, and if you have tried any of the HTML5 toolkits out there for building mobile apps you have probably noticed that these apps are indeed sluggish. So you might be tempted to apply the logic that since HTML5 apps are slow, and HTML5 is a lightweight UI toolkit, that all lightweight UI toolkits are slow. This is certainly incorrect. HTML5 requires very complex layout rules, and it relies on Javascript as its language that is quite a bit slower than a native executable would be. This is very hard for low-powered mobile devices to handle in a performant way. Ultimately as device performance improves (maybe in a couple years), the HTML5 performance problems will dissipate.

Codename One uses a different strategy that is much more similar to Swing than to HTML. It uses 2D drawing technology of the host platform to build its components (OpenGL on iOS, etc..) which is very fast and flexible. This allows for the creation of complex user interfaces without barely any performance hit.

So, no, lightweight UIs don’t have to be slow, and in Codename One’s case, it is not slow.

Are You Destined to Hit a Wall If You Use a Cross-Platform Framework?

I have been burned. You have probably been burned. I think everyone has been burned once or twice when they start a project with a toolkit, then sometime later, they hit a wall and the toolkit just can’t be used to take the next step. Is this inevitable with a Cross-Platform framework?

The answer is, maybe. But if you choose your framework carefully, they you shouldn’t have a problem. Some key items to look for would include:

  1. Is it open source? If it isn’t open source, then the chances are you will get stuck at some point and have to abandon it.

  2. Can you access native APIs if necessary? If you can’t access native APIs, then you will likely have to abandon it at some point.

  3. Is it built on a strong, robust, and fast foundation? If it isn’t, then you’ll likely have to abandon it at some point.

If the framework hits all three of these points, then you should be OK. If you need to access a new API on a particular platform, you can just write a native plugin for that. If you run into a bug or a limitation, you can fix it yourself, as long as it is open source. And as long as the core of the framework is strong and fast, you can build your own component libraries as time goes on to extend the platform, without worrying about breaking the platform.

Most HTML5/Javascript frameworks will fail on #3. HTML5 and Javascript just aren’t robust. There are many commercial cross-platform frameworks out there also. I would be careful with those.

In the few months that I have been working with Codename One, I have found the platform to be open and robust. If I find something that it doesn’t do, it is usually quite easy for me to add it myself. The fact that they allow you to write native plugins, wrap native components when necessary (to add to the UI), and develop my own libraries and components that run on top of it, give me confidence that there is no “wall” that I could hit in the future that would be a show-stopper.