2. Storage, FileSystem & SQL
✦There are several ways to store data in
Codename One, in this lesson I’ll break them
down
✦Each option has tradeoffs that should be weighed
carefully
3. Storage
✦If you are unsure about what you want to use Storage
is probably the best place to start
✦It’s simplest. Like a flat file system
✦When an app is uninstalled it’s deleted (notice it might
be restored from backup on reinstall)
✦In some OS’s (and simulator) it is implemented on top
of the regular file system but that is an implementation
detail! Don’t rely on that!
✦Storage can be seamlessly encrypted
4. FileSystemStorage
✦A map of the native OS filesystem
✦Hierarchy based, always uses / as separator and
always requires full paths
✦Can contain special roots e.g. SD card, caches
✦Behavior is often OS specific
✦Because of the low level nature features like seamless
encryption aren’t available (although it’s possible to
encrypt anything)
5. Storage FileSystemStorage
Portable Access to native features
Flat Hierarchy, always use full paths
Cleaned on uninstall Behavior varies on uninstall
Private to application Potentially exposed in some locations
Cached access Direct access
Great for small (cachable) files Great for larger files
Which Should I Pick?
6. serverInstance.getItemsAsync(items -> {
shoppingList.removeAll();
shoppingList.add(filler);
ArrayList<Map<String, Object>> data = new ArrayList<>();
for(Item i : items) {
if(!i.isDeleted()) {
Component c = createCheck(i.getName(), i.isMarked());
shoppingList.add(c);
shoppingList.add(createSeparator());
data.add(i.toMap());
}
}
shoppingList.revalidate();
// cache the data
try(OutputStream os = Storage.getInstance().createOutputStream("CachedData")) {
Map<String, Object> m = new HashMap<>();
m.put("root", data);
os.write(Result.fromContent(m).toString().getBytes("UTF-8"));
} catch(IOException err) {
Log.e(err);
}
});
Caching Results from the Server
7. serverInstance.getItemsAsync(items -> {
shoppingList.removeAll();
shoppingList.add(filler);
ArrayList<Map<String, Object>> data = new ArrayList<>();
for(Item i : items) {
if(!i.isDeleted()) {
Component c = createCheck(i.getName(), i.isMarked());
shoppingList.add(c);
shoppingList.add(createSeparator());
data.add(i.toMap());
}
}
shoppingList.revalidate();
// cache the data
try(OutputStream os = Storage.getInstance().createOutputStream("CachedData")) {
Map<String, Object> m = new HashMap<>();
m.put("root", data);
os.write(Result.fromContent(m).toString().getBytes("UTF-8"));
} catch(IOException err) {
Log.e(err);
}
});
Caching Results from the Server
Progress Change
I replaced the removal of the
progress indicator as the list
can now include many
arbitrary entries
8. serverInstance.getItemsAsync(items -> {
shoppingList.removeAll();
shoppingList.add(filler);
ArrayList<Map<String, Object>> data = new ArrayList<>();
for(Item i : items) {
if(!i.isDeleted()) {
Component c = createCheck(i.getName(), i.isMarked());
shoppingList.add(c);
shoppingList.add(createSeparator());
data.add(i.toMap());
}
}
shoppingList.revalidate();
// cache the data
try(OutputStream os = Storage.getInstance().createOutputStream("CachedData")) {
Map<String, Object> m = new HashMap<>();
m.put("root", data);
os.write(Result.fromContent(m).toString().getBytes("UTF-8"));
} catch(IOException err) {
Log.e(err);
}
});
Caching Results from the Server
9. serverInstance.getItemsAsync(items -> {
shoppingList.removeAll();
shoppingList.add(filler);
ArrayList<Map<String, Object>> data = new ArrayList<>();
for(Item i : items) {
if(!i.isDeleted()) {
Component c = createCheck(i.getName(), i.isMarked());
shoppingList.add(c);
shoppingList.add(createSeparator());
data.add(i.toMap());
}
}
shoppingList.revalidate();
// cache the data
try(OutputStream os = Storage.getInstance().createOutputStream("CachedData")) {
Map<String, Object> m = new HashMap<>();
m.put("root", data);
os.write(Result.fromContent(m).toString().getBytes("UTF-8"));
} catch(IOException err) {
Log.e(err);
}
});
Caching Results from the Server
Storage
I’m storing the cached data
as JSON because I already
have the code that does that
transport
10. if(Storage.getInstance().exists("CachedData")) {
JSONParser.setUseLongs(true);
JSONParser p = new JSONParser();
try(Reader r = new InputStreamReader(Storage.getInstance().createInputStream("CachedData"), "UTF-8")) {
Map<String, Object> result = p.parseJSON(r);
List<Map<String, Object>> lst = (List<Map<String, Object>>)result.get("root");
for(Map<String, Object> itm : lst) {
Item i = new Item(itm);
if(!i.isDeleted()) {
Component c = createCheck(i.getName(), i.isMarked());
shoppingList.add(c);
shoppingList.add(createSeparator());
}
}
shoppingList.revalidate();
} catch(IOException err) {
Log.e(err);
}
} else {
Component ip = FlowLayout.encloseCenterMiddle(new InfiniteProgress());
shoppingList.add(ip);
}
Loading Cached Server Results
12. SQLite
✦SQLite is a native C embeddable database engine
that’s available almost everywhere
✦It’s very customizable and as a result implementations
on various OS’s aren’t completely consistent
✦It’s available in Android, iOS, UWP & JavaScript
✦SQLite is useful if you need a “real” database on the
device where you can perform fast queries
13. SQLite Device Fragmentation
✦The Android version is threadsafe where the iOS
version is not e.g. even the GC can crash SQLite on
iOS if it closes a cursor from the GC thread
✦The Android & iOS versions handle transaction
isolation differently
✦In JavaScript you can’t ship an app with your own
database
14. Shipping an App With a Database
✦One of the common use cases for SQLite is to ship
an app with a ready made database (e.g. nutritional
database) within the JAR:
String path = Display.getInstance().getDatabasePath("MyDB.db");
if(path != null && !FileSystemStorage.getInstance().exists(path)) {
try(InputStream i = Display.getInstance().getResourceAsStream(getClass(), "/MyDB.db");
OutputStream o = FileSystemStorage.getInstance().openOutputStream(path)) {
Util.copy(i, o);
} catch(IOException err) {
Log.e(err);
}
}
15. Database db = null;
Cursor cur = null;
try {
db = Display.getInstance().openOrCreate("MyDB.db");
cur = db.executeQuery(sqlStatement);
while(cur.next()) {
Row r = cur.getRow();
String s = r.getString(cur.getColumnIndex("columnName"));
// ....
}
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error: " + err);
} finally {
Util.cleanup(db);
Util.cleanup(cur);
}
Issue a Query
16. Database db = null;
Cursor cur = null;
try {
db = Display.getInstance().openOrCreate("MyDB.db");
cur = db.executeQuery(sqlStatement);
while(cur.next()) {
Row r = cur.getRow();
String s = r.getString(cur.getColumnIndex("columnName"));
// ....
}
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error: " + err);
} finally {
Util.cleanup(db);
Util.cleanup(cur);
}
Issue a Query
Cleanup
It’s crucial to cleanup
properly. Otherwise on iOS
the app might crash as the
GC might cleanup before your
code causing a thread conflict
17. What did we learn?
✦When to choose file system storage and when to
choose storage
✦How to implement data caching/offline mode
using storage
✦The problems and value proposition of SQLite