Working with Persistent Storage Using the Multi-OS Engine Technology Preview - Part 2¶

This is the second part of a two-part tutorial that shows how to do persistent storage in Apple iOS* with Java* using Intel’s Multi-OS Engine Technology Preview. This part adds SQLite* functionality to our app created using the first tutorial part.

We will show how to use SQLite library using the Multi-OS Engine for both Android* and iOS by generating bindings for the sqlite.h file and how to access them. The first part of this tutorial can be found here. Building on the app from part 1, download sqlite3.h from the SQLite website and copy it to our common library directory.

The Multi-OS Engine creates a new directory to contain all the NatJ bindings related to the SQLite. We will use these bindings to create database instance and run our database CRUD operations common to both platforms.

Here we will add an annotation @Library(“sqlite3”) to our generated class file. The @Library annotation specifies the name of the native library that needs to be loaded for the marked class to work. More specifically, when NatJ.register() is invoked, NatJ will search for this annotation in the invoker class, and try to load the specified library with NatJ.lookUpLibrary(...).

Also, we need the Multi-OS Engine’s NatJ bindings to access native C pointers. To do this, add natj-api.jar library as dependency to the common library folder. This jar can be found in the GitHub* project and you can directly import it into your project.

Next, we create a Note class that will be our data model for the database.

Now let’s create common code to create and manage the database and to store the note contents.

SQLiteDatabaseHelper.java is used to open and close database instance. In its constructor directory path of the database file is passed which will be platform specific. Its onCreate method creates a new notes table if it already doesn’t exists.

The Database.java class handles the insertion, deletion, update and selection queries. The methods of this class abstracts the native SQLite operations. For example, here is the insertion query.

The SQLiteStatement.java handles the native routines of the SQLite libraries like prepare(), exec(), query(), strep(), close(). This class uses the native C API calls that were generated using the binding generator. The following snippets show how prepare() and exec() methods are implemented.

Based on this, we can implement different methods of the sqlite statement class.

The SQLiteCursor.java class handles the data extraction APIs from the statement class. Following are methods to get integer and string values from the query executed.

@OverridepublicStringgetString(inti){if(stmt==null){thrownewRuntimeException("statement is closed");}returnGlobals.sqlite3_column_text(stmt.getStmtHandle(),i);}@OverridepublicintgetInt(inti){if(stmt==null){thrownewRuntimeException("statement is closed");}returnGlobals.sqlite3_column_int(stmt.getStmtHandle(),i);}

Android has its own version of SQLite that has a slightly different implementation than the native one. This limits our ability to reuse the code between the platforms. For this reason, we will build our own custom version of SQLite for Android using NatJ bindings. A dynamic shared object library of the native SQLite is created which is linked at runtime so Android could handle the native C calls of SQLite. Along with the sqlite.so library, two more libraries are linked with the project. They are libnatj.so and libc++_shared.so. The libnatj.so is a NatJ bridge to work with the NatJ calls of Multi-OS Engine. The libc++_shared.so is shipped to support Android 4.x. It is not needed for Android 5.x.

In the build.gradle file, we have to make changes so that we can use of the .so native libs.

First we have to specify the src directory for jnilibs in the sourceSets

tasknativeLibsToJar(type:Zip,description:'create a jar archive of the native libs'){destinationDirfile("$buildDir/native-libs")baseName'native-libs'extension'jar'fromfileTree(dir:'src/main/native-libs',include:'**/*.so')into'lib/'}

A SQLiteDatabaseHelper class is created which extends the SQLiteDatabaseHelper common class. Only the getDocumentsPath() method of this class is overridden to get the iOS specific documents directory path.

In the viewDidLoad() method of MasterViewController, an instance of databasehelper is created in whose constructor filepath of the database is passed. An instance of Database is created and its reference is retrieved by getWritableDatabase() method.

Run the app so you can see the database files created in your respective platforms.

This method allows the business logic of the code can be reused on both the platforms using the same implementation of SQLite. This definitely saves a lot of coding effort and helps minimize code maintenance.