Kripton Persistence Library — An introduction

An open source library to simplify persistence on Android platform on SQLite, Shared-Preferences, file system (and REST client)

Kripton logo
Article updated to version 5.0.0

It was 2015, and I was working on an Android project. The tasks that application needs to accomplish were always the same: get some data from REST Web services, store data locally, manage user’s preferences, display data and so on. I began to separate the persistence code in a little library and then… Kripton Persistence Library was born!

I write Kripton Library to simplify tasks about persist data on Android platform. First of all… what I mean with “persist”? The answer is:

  • on the file system as a file
  • in an SQLite database
  • in a SharedPreferences
  • remotely with REST services

There are many other open source libraries to accomplish these tasks, but my goal was to simplify my developer life, creating a library that allows for accomplishing this task in a simple and uniform way… and with performance in mind.

I didn’t reinvent the wheel, Kripton is based on many great libraries like Jackson for data format conversion, Java poet, XML pull parsing, Antlr and many others.

To work with REST service I simply wrote an integration library between Kripton with Retrofit.

As you know, reflection is a bad thing if we talk about performance on Android Platform. This is the reason for which Kripton is heavily based on annotation processors that work at compile time an write for us the boilerplate code need to persist data.

Ok, now it’s time to show how Kripton works.

Setup

To configure Kripton on a Gradle project, you have to include these lines in your project’s dependencies:

dependencies {
...
// annotation processors
annotationProcessor"com.abubusoft:kripton-processor:5.0.0"
...
// dependencies
implementation "com.abubusoft:kripton-android-library:5.0.0"
...
}

The Kripton-processor is the artefact that contains annotation processors. Kripton-android-library contains core classes and references to all-you-need to execute the generated code. Kripton-retrofit-converter is the Retrofit integration (if you don’t need REST service, you don’t have to include it).

Persistence on the file system

Just imagine that in your application you have to manage a Person object. You simply need to persist on a file, just to reload the data in a second time. We define the Person class in this way:

@BindType
public class Person{
public String name;
public String surname;
public String email;
public Date birthDate;
}

When you compile your project, Kripton annotation processor will find Person class marked with @BindType annotation and will generate all the needed code for us. So, in your code to persist a Person’s instance you can simply write:

// define the Person object
Person bean = new Person();
bean.name = "name";
bean.surname = "sunrame";
bean.birthday = new Date();
// get the persistence context
BinderContext context = KriptonBinder.jsonBind();
// persist on a file
context.serialize(person, new File(..));

To read from a file data and convert it into a Person instance

// get the persistence context
BinderContext context = KriptonBinder.jsonBind();
// read from a file
Person person=context.parse(new File(..), Person.class);

The conversion is based on code that Kripton write for us during compile time. The data format used in this case is JSON, but you can also use other data format like CBOR, YAML, XML and (Java) Properties. In our example, we used the String and Date type. Kripton supports many attribute type like primitive type, collections, arrays, maps and so on. Moreover, it’s possible to extend a set of the supported type with TypeAdapters.

For more information about these features, you can visit Kripton wiki.

Persistence on SQLite database

This is the persistence mechanism on which I worked a lot. You probably know that manage SQLite database on the Android application is a very very boring task. If you don’t know this, please read the official documentation. I used the DAO pattern to approach at the database management. In the DAO pattern we can found:

  • A data model composed of simple POJO objects in Java world, and tables in the SQLite world.
  • Data Access Object interfaces that define how to access database
  • Data Access Object implementation that implements the DAO interfaces
  • A database that is composed of DAOs and data model.

Kripton needs the developer defines the data model with @BindSqlType annotated java classes, the DAO’s interfaces with @BindDao annotated Java interface and a data source (the database) by an @BindDataSource annotated Java interface. At compile time, Kripton will generate all needed code to implements DAO interfaces and manage data source.

We can take the previous Person example to see how to define an SQLite database with a person table, and a DAO interface with some methods to do CRUD operations (Create Read Update Delete). The data model is represented by Person class:

@BindSqlType(name="persons")
public class Person{
public long id;
public String name;
public String surname;
public String email;
public Date birthDate;
}

Just two things:

  • every SQLite table needs an id column of type Long or long. It’s a constraint that Kripton required for every table and it is a best practice for SQLite databases.
  • @BindSqlType is the annotation used to mark a data model that will be used in an SQLite database.

The DAO interface definition is:

@BindContentProviderPath(path = "persons")
@BindDao(Person.class)
public interface PersonDao {
@BindContentProviderEntry
@BindSqlSelect(orderBy="name")
List<Person> selectAll();
@BindSqlSelect(jql="select * from person order by name")
List<Person> selectTwo();
@BindSqlSelect()
List<Person> selectThree(@BindSqlDynamicOrderBy String orderBy);
@BindSqlSelect(where = "id=${work.id}")
List<E> selectById(@BindSqlParam("work") E bean);
@BindContentProviderEntry
@BindSqlInsert
void insert(Person bean);
@BindContentProviderEntry  
@BindSqlUpdate(where = "id=${work.id}")
boolean update(@BindSqlParam("work") Person bean);

@BindContentProviderEntry
@BindSqlDelete(where = "id=${work.id}")
boolean delete(@BindSqlParam("work") Person bean);
}

And the data source definition is:

@BindContentProvider(authority="com.abubusoft.kripton")
@BindDataSource(daoSet= { PersonDao.class }, fileName = "person.db", log=true)
public interface PersonDataSource {
}

When the project is compiled, Kripton annotation processor will generate for us the code that implements the data source defined by the data model, the DAO and data source interfaces.

The need annotations to define a data source with Kripton is:

  • @BindDataSource: defines a data source
  • @BindDao: define the DAO interface
  • @BindSqlType: associate a class to a table
  • @BindInsert|Update|Select|Delete: defines SQL to manage tables

As you notice in the source code there are other used annotations, needed if you want to generate a Content Provider too:

  • @BindContentProvider: allows generating a content provider
  • @BindContentProviderPath: include DAO in the content provider definition
  • @BindContentProviderEntry: include DAO’s method in the content provider definition

Yes, given a data source definition, Kripton can generate a content provider just with a couple of extra annotations. In your application, to use generated implementation of data source you can use code like this:

// typically Kripton library is done in Application#onCreate
KriptonLibrary.init(context);
// usage example 1: open data source and insert somedata
try (BindPersonDataSource dataSource = BindPersonDataSource.open())
{
dataSource.getPersonDAO().insert(person);
}
// usage example 2: using transaction
BindPersonDataSource.instance().execute(daoFactory -> {
PersonDao dao=daoFactory.getPersonDao();
dao.insert(person);
...
return TransactionResult.COMMIT;
});
// usage example 3: using shared connection
BindPersonDataSource.instance().executeBatch(daoFactory -> {
PersonDao dao=daoFactory.getPersonDao();
dao.selectAll();
...
});

For a PersonDataSource interface, Kripton generates a BindPersonDataSource class that implements the data source which allows to work in a thread-safe way and exposing all DAO defined in PersonDataSource interface. The generated data source exposes some methods to work in a transaction mode and in shared connection mode. You can see an example of generated DAO implementation and of generated Data Source implementation. As you can observe from source code, all generated classes are well documented!

There are other features that I want to mention: the capability to generate a log of SQL operation, the capability to support RX library and the capability to test database version migration with helper classes.

Persistence with SharePreferences

Shared Preferences is another standard way to persists data on Android Platform. In almost all the case, they are used to manage user’s application settings. Kripton can generate a Shared Preferences wrapper that allows accessing to settings properties in a strong type way. Just an example: suppose to represent the application’s setting with the AppPreferences class:

@BindSharedPreferences
public class AppPreferences {
public float valueFloat=5.0f;
@BindPreference(“value”)
public boolean valueBoolean;
}

With BindSharedPreferences annotation, Kripton will generate a class named BindAppPreferences:

Generated SharedPreference wrapper class for AppPreferences class

So if you want to read or write properties from SharedPreference in the application, you can write:

// get Shared Preference instance
BindAppPreferences prefs = BindAppPreferences.instance();
// read the value of valueBoolean property
...=prefs.valueBoolean();
// write the value of valueBoolean property
prefs.edit().putValueBoolean(true).commit();

You can use String, primitive types, List, Map, Sets or another object as SharedPreference’s attributes. The complex type will be converted into its JSON representation.

Persistence with REST Web service

Last but not least persistence type covered by Kripton is the one which uses REST service. There are many libraries that permit to generate REST service client easily. The one that I prefer is the Retrofit.

So, I decide to integrate Kripton with Retrofit, just to use Kripton persistence mechanism in Retrofit library. I want to show you here how simple is to work with them.

For example, we want to consume the REST service at

https://jsonplaceholder.typicode.com/posts/

So we define a Post class with Kripton BindType annotation

@BindType
public class Post {
public long userId;
public long id;
public String title;
public String body;
}

We can define the REST client interface:

public interface JsonPlaceHolderService {
@POST(“/posts/”)
Call<List<Post>> getAllPost();
}

The code to consume the REST service is:

// create retrofit using Kripton converter factory
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(“https://jsonplaceholder.typicode.com/")
.addConverterFactory(KriptonBinderConverterFactory.create())
.build();
JsonPlaceHolderService service = Retrofit.create(JsonPlaceHolderService.class);
// consume service
Response<List<Post>> response = service.getAllPost().execute();

The integration between Kripton and Retrofit is done by KriptonBinderConverterFactory converter.

Conclusion

This post was only an introduction on Kripton Persistence Library. It’s a powerful library that can help development on Android Platform.

If you like Kripton, give it a star on GitHub, thank you.

You can found Kripton Persistence Library on GitHub. Moreover, you can consult my blog Abubusoft.com.

Happy Coding

Francesco Benincasa