Persisting State with DukeScript
I’ve been asked a couple of times, if it’s possible to store data on the device via DukeScript - for example in order to store UI state. We’re currently exploring how to automate this tracking, so you would only need to register your ViewModel once for automated persistence. A PersistenceManager would then take care of persisting the model on every change of a Model Property. For Performance reasons there would be a custom debounce threshold you can set to make sure changes are only persisted when input events have comleted.
Until we offer this as an API (Update: as of February 2017, there is an easy way to get started via Maven archetype 0.17 or newer version), you might want a specific solution for your Application right now.
So here’s a starting point:
public class StorageManager {
private static Storage LOCAL = new Storage() {
@Override
public void put(String key, String val) {
StorageManager.put_impl(key, val);
}
@Override
public String get(String key) {
return StorageManager.get_impl(key);
}
};
private static Storage FALLBACK = new Storage() {
@Override
public void put(String key, String val) {
Preferences pref = Preferences.userNodeForPackage(StorageManager.class);
pref.put(key, val);
}
@Override
public String get(String key) {
Preferences pref = Preferences.userNodeForPackage(StorageManager.class);
return pref.get(key, "");
}
};
private static Storage EMPTY = new Storage() {
@Override
public void put(String key, String val) {
}
@Override
public String get(String key) {
return "";
}
};
public interface Storage {
public void put(String key, String val);
public String get(String key);
}
public static Storage getStorage() {
if (supportsLocalStorage()) {
return LOCAL;
} else if (supportsPreferences()) {
return FALLBACK;
}
return EMPTY;
}
@JavaScriptBody(args = {}, body = "try {\n"
+ " return 'localStorage' in window && window['localStorage'] !== null;\n"
+ " } catch (e) {\n"
+ " return false;\n"
+ " }")
private static native boolean supportsLocalStorage();
private static boolean supportsPreferences() {
try {
Preferences userNodeForPackage = Preferences.userNodeForPackage(StorageManager.class);
userNodeForPackage.put("dummy_probably_very_unlikely_s0mbody_U5e5_th1s_key", "-1");
return true;
} catch (Exception e) {
return false;
}
}
@JavaScriptBody(args = {"key"}, body = "if(localStorage){"
+ " var obj = localStorage[key]; "
+ " return obj;"
+ "}"
+ "return '';")
public static native String get_impl(String key);
@JavaScriptBody(args = {"key", "val"}, body = "var store = window['localStorage'];if (store) localStorage[key]=val;")
public static native void put_impl(String key, String val);
}
With this code you can store Strings, which is good enough, because ViewModels can be serialized to JSON. So you can easily read and write your data:
// store the data as JSON
StorageManager.getStorage().put("test", model.toString());
// parse JSON
String test = StorageManager.getStorage().get("test");
ViewModel vm = new ViewModel();
InputStream inputStream = new ByteArrayInputStream(test.getBytes(StandardCharsets.UTF_8));
try {
vm = Models.parse(BrwsrCtx.findDefault(TaskList.class), ViewModel.class, inputStream);
} catch (IOException ex) {
taskList = new TaskList();
}
}
Models.applyBindings(vm, "body");
This simple solution requires you to track changes in the model yourself and serialize the data.
By the way: A happy new Year to all friends of DukeScript!