Saving data in Android using Realm

How to save data for your mobile application? There’re three possible ways: Shared preferences/User defaults, files and databases. When choosing which one to use you should take into account the volume of data your app will work with, mobile data type and what exactly you’ll be doing with the data. Traditionally mobile software developers were using SQLite but there’s another type of the database out there called Realm and here we’d like to talk about its advantages and show how we use it in our projects.

At first almost all mobile applications were relying on SQLite to save data for mobile phones and tablets, either directly or through one of the numerous libraries providing a convenient wrapper for it.

Data base technologies 2000-2014

From the image above you can see that the number of server database software (blue) was steadily growing over the years comparing to mobile database market (red) which weren’t seeing newcomers until 2014. In 2000 SQLite was a revolutionary solution but of course in the next 16 years mobile development has seen a lot of changes.

Many libraries providing wrappers for SQLite were created in order to simplify the usage of CRUD operations (create, read, update and delete). For example, SugarORM, GreenDAO, Core Data, ORMLite. In 2014 a new alternative to ORM databases was created: Realm.

Realm Advantages

One of the most important advantages of Realm is its speed. According to the data from the official website, it is the fastest solution when you’re getting count of records on a database of 200K records:

Realm speed test (Counts)

When iterating over all records matching the query:

Realm speed test (Queries)

And when inserting 200K in a single transaction:

Realm speed test (Inserts)

Quite a result, I think.

Another interesting feature we get with Realm is denormalization, saving a model that has relationships in place.

Let’s imagine that we have two models: user and email address. User can have several email addresses. Each time we need a user model, all of the emails belonging to this user are available to us as well. We don’t need to run additional queries to get the list of email addresses belonging to this user. We can save email list as a model list in user model.

And last but not least, Realm is easy to use. And here we will show you some examples.

Connecting Realm

Prerequisites:

  • No Java support outside of Android
  • Android Studio >= 1.5.1
  • JDK version => 7
  • All mobile devices running Android working with API Level 9 (Android 2.3 Gingerbread and above)

Realm is installed as a Gradle plugin:

buildscript {
    repositories {
        jcenter()}
    dependencies {
        classpath "io.realm:realm-gradle-plugin:0.88.1"}}
 
apply plugin:'realm-android'

There’s no support for Maven and Ant build systems. And starting from version 0.88 Eclipse support is considered to be outdated. If you want to continue using Eclipse, you’ve got to use Realm v0.87.5. But note that this version doesn’t receive any additional updates.

When compiling, Realm generates proxy class for every RealmObject. To guarantee that these classes will remain findable after obfuscation and statistical analysis you need to add configuration below into ProGuard configuration file:

-keep class io.realm.annotations.RealmModule-keep @io.realm.annotations.RealmModuleclass*-keep class io.realm.internal.Keep-keep @io.realm.internal.Keepclass*{*;}-dontwarn javax.**-dontwarn io.realm.**

After doing it we can start using Realm.

Using Realm

To avoid repeating what is already well explained in the Realm documentation, let’s talk about what is not so obvious. We use MVVM architecture in our projects so expect examples relevant to this pattern here.

First of all, we need to set preferences for Realm. If only one configuration is used, all we need to do is to describe this configuration as a default one in our Application class:

@Override
publicvoid onCreate(){super.onCreate();
...
   RealmConfiguration config =new RealmConfiguration.Builder(this).build();
   Realm.setDefaultConfiguration(config);
	...
}

After doing it we can receive an instance of Realm:

Realm realm = Realm.getDefaultInstance();

There’s something to remember here. Each time we call getDefaultInstance, a new instance of Realm object is created. That’s why after using it you should close instance with the following command:

realm.close();

Here’s how we approach it in our projects: Realm instance is created and closed in base ActivityViewModel.

publicabstractclass ActivityViewModel<A extends BaseActivity, B extends ViewDataBinding>extends BaseObservable {
...
   private Realm realm;
...
   public ActivityViewModel(A activity, B binding){
...
       realm= Realm.getDefaultInstance();
...
   }
 
...
   publicvoid onDestroy(){
       realm.close();}
 
   public Realm getRealm(){return realm;}
...
}

It allows us to get Realm instance in any place of our ViewModel and don’t worry about memory leaks.

Here’s a reliable pattern for asynchronous tasks and separate flows:

    Realm realm =null;try{
        realm = Realm.getDefaultInstance();
 
        // ... Use the Realm instance}finally{if(realm !=null){
            realm.close();}}

To create a model we need a simple POJO inherited after RealmObject. But there’re some limitations here. For example, you must declare getters and setters in your models. But if your setter or getter is performing any additional actions, this actions will be ignored. To bypass it use fields that are ignored (@Ignore annotation) as well as getters and setters of this field. Here’s how it looks like:

publicclass SempleObject extends RealmObject {
 
    privateString name;
 
    @Ignore
    privateString kingName;
 
    // custom setterpublicvoid setKingName(String kingName){ setName("King "+ kingName);}
 
    // custom getterpublicString getKingName(){return getName();}
 
    // setter and getter for 'name'}

Many-to-one connection is implemented by simply announcing type field for one of RealmObject subclasses:

publicclass User extends RealmObject {
 
   @PrimaryKey
   privateString id;
 
   @SerializedName("name")privateString name;
 
   @SerializedName("username")privateString username;
 
   @SerializedName("image")privateImage image;
 
   //getters and setters}

Every user (User instance) has either 0 or 1 image (Image instance). In Realm nothing will prevent you from using the same image object for several users and the model above might have many-to-one connection but it is more often used for modelling one-to-one connections.

Many-to-many connection is implemented with RealmList field declaration:

publicclass User extends RealmObject {
 
   ...
 
   private RealmList<Image> images;
 
   //getters and setters}

Realm will automatically update to the latest version with each change in the database. It is a handy feature which allows us to easily save UI with the latest version of data.

If automatic update is not enough, we can receive a callback when any changes are made to the sample. For example:

RealmResults<Message> results = getRealm()
.where(Message.class)
.equalTo("conversation.id", conversationId);
 
results.addChangeListener(()->{//handle callback});

Disadvantage here is that we don’t know which element was changed. In this case we use EventBus. It is important to remember that a Realm object created in one stream can’t be used in another. That’s why in events we use not the Realm object itself but only its id.

Event declaration for Conversation update:

publicclass ConversationEvent {publicString conversationId;
 
   public ConversationEvent(String conversationId){this.conversationId= conversationId;}}

Let’s imagine a method our Conversation is updated in:

publicvoid updateConversation(Conversation conversation){
	...
   EventBus.getDefault().post(new ConversationEvent(conversation.getId()));}

Here’s how our ActivityViewModel will look like:

publicclass ConversationActivityVM
       extends ActivityViewModel<ConversationActivity, ActivityConversationBinding>{
...
 
//Subscribing to ConversationEvent events
@Subscribe(threadMode = ThreadMode.MAIN)publicvoid onConversationEvent(ConversationEvent event){if(adapter !=null){
Conversation conversation = realm.where(Conversation.class).equalTo("id", id).findFirst();
       adapter.addOrUpdate(conversation);}}
 
   @Override
   publicvoid onStart(){
       EventBus.getDefault().register(this);}
 
   @Override
   publicvoid onStop(){
       EventBus.getDefault().unregister(this);}
 
   ...
}

If Gson is used in the project, the program will fail with error java.lang.StackOverflowError. To avoid it use this code when creating your Gson instance:

Gson gson =new GsonBuilder()
        .setExclusionStrategies(new ExclusionStrategy(){
            @Override
            publicboolean shouldSkipField(FieldAttributes f){return f.getDeclaringClass().equals(RealmObject.class);}
 
            @Override
            publicboolean shouldSkipClass(Class<?> clazz){returnfalse;}})
        .create();

To make things easier we have created a builder and use it for getting Gson class instance:

publicclass GsonRealmBuilder {
 
   private GsonRealmBuilder(){}
 
   publicstatic GsonBuilder getBuilder(){returnnew GsonBuilder()
               .setExclusionStrategies(new ExclusionStrategy(){
                   @Override
                   publicboolean shouldSkipField(FieldAttributes f){return f.getDeclaringClass().equals(RealmObject.class);}
 
                   @Override
                   publicboolean shouldSkipClass(Class<?> clazz){returnfalse;}});}
 
   publicstatic Gson get(){return getBuilder().create();}}

Conclusion

All in all, we are mostly satisfied with how the things are going after we started using Realm. Now database implementation takes much less time than it used to with SQLite. Of course, some aspects could be improved but it’s a different story.

Need MVP development, iOS and Android apps or prototyping? Check out our portfolio and make an order today!