Storage and other Services for each Platform

Ever wanted to access the most appropriate persistent storage for browser, desktop, Android or iOS platform? Ever was in a need to access any other platform dependent service? With new version 0.17 of our Maven archetypes this task has become more than easy.

Setting the Project Up

Let’s get started. Follow the IDE based tutorial or use shell commands to create the project from a command line. In the wizard, choose Knockout for Java Example; from the command line select the knockout4j-archetype.

Make sure you include the example code. Preferably choose as much of supported platforms as possible: browser, Android, NetBeans Platform and iOS. The rest of this tutorial assumes all these platforms are enabled and used.

Structure of the Code

Now take a look at js/src/main/java/**/js/PlatformServices.java file. It contains generic methods for accessing storage:

public class PlatformServices {
    /**
     * Reads a value from a persistent storage.
     * @param key the identification for the value
     * @return the value or <code>null</code> if not found
     */
    public String getPreferences(String key) {
        return getPreferencesImpl(key);
    }

    /**
     * Puts a value into the persitent storage.
     * @param key the identification for the value
     * @param value the value to store
     */
    public void setPreferences(String key, String value) {
        setPreferencesImpl(key, value);
    }
}

and also their default (which in DukeScript means ready for standalone HTML browser) implementation:

@JavaScriptBody(args = { "key" }, body =
    "if (!window.localStorage) return null;\n" +
    "return window.localStorage.getItem(key);\n"
)
private static native String getPreferencesImpl(String key);

@JavaScriptBody(args = { "key", "value" }, body =
    "if (!window.localStorage) return;\n" +
    "window.localStorage.setItem(key, value);\n"
)
private static native void setPreferencesImpl(String key, String value);

The default implementation uses browser’s localStorage object via our interface with JavaScript - the @JavaScriptBody annotation (more about that annotation in HTML/Java documentation),

Using the Services Class

Let’s now explore the way to use this PlatformServices class. Take a look at your client/src/main/java/**/DataModel.java - the model class defining the UI model of your application. It’s header looks like:

@Model(className = "Data", targetId="", instance=true, properties = {
    @Property(name = "message", type = String.class),
    @Property(name = "rotating", type = boolean.class)
})
final class DataModel {
    private PlatformServices services;
    // ...
}

As you can see, the model of your UI has a reference to implementation of PlatformServices which is injected to it when starting the application and constructing the model. Various methods of the DataModel class are using this services field to read and store data into the persistent storage. A form of dependency injection. Now we just need to inject the most appropriate implementation of the services for each supported platform.

Desktop Services

There is a Main.java file next to the DataModel.java and if you inspect it you can see it provides a DesktopServices implementation that is using standard Java way to access (Windows) registry - e.g. preferences API;

public static void onPageLoad() throws Exception {
    DataModel.onPageLoad(new DesktopServices());
}

private static final class DesktopServices extends PlatformServices {
    @Override
    public String getPreferences(String key) {
        return Preferences.userNodeForPackage(Main.class).get(key, null);
    }

    @Override
    public void setPreferences(String key, String value) {
        Preferences.userNodeForPackage(Main.class).put(key, value);
    }
}

NetBeans Platform

The NetBeans Platform boot mechanism - located in client-netbeans/src/main/java/**/NbMain.java file - is similar. Just instead of Java preferences it uses NbPreferences class tailored for NetBeans needs.

iOS

The way to deal with user preferences on iOS system is to use NSUserDefaults as provided by Apple. Of course, their documentation is tailored for Objective-C and we aren’t coding in such language! Luckily there is a 1:1 wrapper API available in org.robovm.apple.foundation.NSUserDefaults class and we can thus easily write:

public static void onPageLoad() throws Exception {
    DataModel.onPageLoad(new iOSServices());
}

private static final class iOSServices extends PlatformServices {
    @Override
    public String getPreferences(String key) {
        return NSUserDefaults.getStandardUserDefaults().getString(key);
    }

    @Override
    public void setPreferences(String key, String value) {
        NSUserDefaults.getStandardUserDefaults().put(key, value);
    }
}

Android

The Android API for storage is associated with application context. Luckily the most recent version of DukeScript Presenters gives us an easy access to the application context. Just declare the Context as a single parameter of the main method:

import android.app.Activity;
import android.content.SharedPreferences;

public class AndroidMain extends Activity {
    public static void main(android.content.Context context) throws Exception {
        SharedPreferences prefs = context.getApplicationContext().getSharedPreferences(AndroidMain.class.getPackage().getName(), 0);
        DataModel.onPageLoad(new AndroidServices(prefs));
    }

    private static final class AndroidServices extends PlatformServices {
        private final SharedPreferences prefs;

        AndroidServices(SharedPreferences prefs) {
            this.prefs = prefs;
        }
        @Override
        public String getPreferences(String key) {
            return prefs.getString(key, null);
        }

        @Override
        public void setPreferences(String key, String value) {
            prefs.edit().putString(key, value).apply();
        }
    }
}

Browser

The simplest work is with the browser environment. As our default implementation of PlatformServices deals with localStorage object available in any browser, we can just use the class without any special tricks. This is exactly what the client-web/src/main/java/**/BrowserMain.java file does.

Trying it Out!

The sample application allows you to edit an input line with a message. With every change it stores the current value of the text into appropriate storage as provided by our registered PlatformServices implementation.

To try it out, run the application once, modify the message, shut the application down and launch it again. You should see the modified text being used and the behavior shall be consistent on desktop, Android, iOS or inside of a NetBeans Platform application. The behavior works in a browser as well - just open new tab with the same URL as the previous one and you can see the text is properly propagated through localStorage object.

DukeScript gives you platform independent UI. PlatformServices concept (as introduced in archetypes version 0.17) gives you simple, standardized and flexible way to access proprietary platform services.

Enjoy DukeScript’s portability and flexibility!