Persist data effectively with Android Realm database

When it comes to storing application data in a database on Android, there’s not much of a choice the developer has. First of all, there is SQLite as the only officially recommended solution in Android framework. On top of SQLite, there is a number of ORM (Object-relational mapping) frameworks available which aim at allowing developer to work directly with objects when storing and reading data from a database. SQLite is however sometimes not so easy to use and there are some drawbacks due to not being designed specificaly for mobile devices. Luckily, there is another kind of database gaining on popularity lately — the Android Realm database.

Android Realm DB

Realm database has been launched in 2014 as the first mobile-first database with support for both iOS and Android. Android Realm database is simple to use, yet powerful as the core has been written in C++ and is compiled in native code. The data is directly exposed as objects and can be queried in code eliminating need for any kind of ORM.

Features like object relationships (one-to-many, many-to-many), full ACID transactions and thread safety are expected in a modern mobile database and Android Realm offers them as a standard. Apart from that, Android Realm is very fast, sometimes more than twice as fast as SQLite, according to the project website.

Now, let’s have a look at how to set up Realm for your Android projects.

Prerequisites & Set up

Android Realm requires a recent version of Android SDK and Android Studio. Minimal supported Android SDK is version 9 (Android 2.3 Gingerbread).

There are only two steps required in order to install Realm database. First, add the following lines to your project level build.gradle file:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "io.realm:realm-gradle-plugin:2.2.1"
    }
}

After that, put this line to the top of your application level build.gradle:
apply plugin: 'realm-android'

Synchronise Gradle dependencies and you’re ready to go.

Realms

Realms are basically database instances which you can use in your application. Before you can use a Realm, you have to initialise it first. The easiest way to do that is to extend the Android Application class and initialise the database there.

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
    }
}

You can change a number of database properties by instantiating a configuration and then setting it as a default one. The following piece of code sets a custom name, encryption key, and schema version.
RealmConfiguration config = new RealmConfiguration.Builder()
    .name("myrealm.realm")
    .encryptionKey(getEncryptionKey())
    .schemaVersion(2)
    .build();

Realm.setDefaultConfiguration(config);

After the Android Realm database is set up, you can get its instance as many times as you want anywhere in your application by calling:
Realm realm = Realm.getDefaultInstance();

However, for every instance you acquire, you should also remember to close it appropriately. The instances are reference counted. For the UI thread, the easiest way to close the database is to execute realm.close() in onDestroy().

Database design

Modelling your database is really easy with Realm. All you have to do is create a model class extending a RealmObject.

public class Owner extends RealmObject {
    
    @PrimaryKey
    private String id;
    private String name;
    private Address address;    /* one-to-one relationship */
    private List<Pet> pets;     /* one-to-many relationship */

    public Owner(String name, Address address, List<Pet> pets) {
        this.id = UUID.randomUUID().toString();
        this.name = name;
        this.address = address;
        this.pets = pets;
    }

    /* getters and setters */
}

The model class can also contain custom methods.

Field types

Realm supports all standard data types: boolean, byte, short, int, long, float, double, String, Date and byte[]. Boxed variants of the primitive data types are also available to use, allowing to set the value to null. In case a value should be supplied with these types, a @Required annotations must be used.

Primary keys can be of String or integer type and should be annotated by @PrimaryKey annotation. An index on a field is created with an @Index annotation with the primary key indexed implicitly. You can chose not to store a field in a database by annotating it with the @Ignore annotation.

Relationships

Realm supports creating relationships using data models in a very intuitive way. To link another object in a database, simply declare a field of the appropriate type (one-to-one). A single object can be linked from several other objects too (one-to-many).

Establishing relationship to any number of objects is possible via a RealmList<T> field declaration. RealmList behaves very much like regular Java List object. A nice feature is that Android Realm will never return an instance of RealmList with null value. The returned object is always a list, possibly empty.

Writing and querying

Database operations are typically executed using an instance of Android Realm database. Let’s see how to write data and create queries in the following paragraphs.

Writing to the database

All write operations (including adding, modifying and removing objects) must be wrapped in a write transaction which can be then committed to the database or cancelled. This ensures that the data is always consistent in the database. It also ensures thread safety.

Realm realm = Realm.getDefaultInstance();

realm.beginTransaction();

/* add or update objects here ... */

realm.commitTransaction();

There are two ways to create a new object in the Android Realm database. First is instantiating a new object directly using a Realm instance:
realm.beginTransaction();
Owner owner = realm.createObject(Owner.class);
owner.setName("George");
realm.commitTransaction();

The second way is by creating a new instance using a constructor and then copying it to the database:
Owner owner = new Owner("George");

realm.beginTransaction();
Owner realmOwner = realm.copyToRealm(owner);
realm.commitTransaction();

When using the latter way, an important thing to remember is that only the returned object is managed by Realm. Any changes made to the original unmanaged object will not be recorded into the database.

Deleting an object from the database can be done by simply calling a deleteFromRealm() method on the particular object.

Querying database

To query a database, Android Realm uses a Fluent interface allowing to specify the query in an easy-to-understand way.

RealmResults<Owner> owners = realm.where(Owner.class)
        .equalTo("name", "George")
        .or()
        .greaterThan("age", 18)
        .findAll();

The result is a list of references to the real objects in database and modifying has to be done inside of a write transaction again.

Many query methods are supported. Logical AND is implicit, OR has to be explicitly specified by using an or() method. Parentheses are substituted by the beginGroup() and endGroup() methods which allow you to build more complicated queries.

You can sort the results using the sort() method and also several standard aggregation functions to calculate sum, maximum, minimum, or average.

JSON

It’s also possible to load data into the Android Realm database from JSON format represented by a String, InputStream or JSONObject. A single object can be added by using the Realm.createObjectFromJson() and a list of objects is loaded using the Realm.createAllFromJson() method.

realm.beginTransaction();

/* single object */
realm.createObjectFromJson(Owner.class, "{ owner: \"George\", age: 32 }");

/* list of objects */
InputStream is = new FileInputStream(new File("path_to_file"));
realm.createAllFromJson(Owner.class, is);

realm.commitTransaction();

Asynchronous operations, change listeners

Android Realm makes it really easy to use the database data in multiple threads without having to worry about consistency of the data or performance. The reason for this is that the database objects are always auto-updating. Take this example: a list of users is downloaded and saved to the database in the background thread and immediately displayed on the UI thread automatically thanks to the auto-updating feature of database objects.

Asynchronous writes

There can be only a single transaction being processed at a time in Realm. Therefore, it might be useful to do all writing on a background thread and not block the UI thread. You can use asynchronous transactions to write data on the background thread and report back when the transaction is done.

RealmAsyncTask transaction = realm.executeTransactionAsync(new Realm.Transaction() {
        @Override
        public void execute(Realm bgRealm) {
            Owner owner = bgRealm.createObject(Owner.class);
            owner.setName("George");
        }
    }, new Realm.Transaction.OnSuccess() {
        @Override
        public void onSuccess() {
            /* success actions */
        }
    }, new Realm.Transaction.OnError() {
        @Override
        public void onError(Throwable error) {
            /* failure actions */
        }
    });

The OnSuccess and OnError callbacks are both optional. The asynchronous transactions are represented by RealmAsyncTask object which should be used to cancel the pending transaction if the calling Activity or Fragment is being stopped:
public void onStop () {
    if (transaction != null && !transaction.isCancelled()) {
        transaction.cancel();
    }
}

Watching for changes

In case you need to know that the database data has been changed (possibly in a background thread), Android Realm offers notification mechanism just for that purpose. All you have to do is to attach a change listener to a database object or a list of objects:

Owner owner = realm.where(Owner.class).findFirst();
owner.addChangeListener(new RealmChangeListener<Owner>() {
    @Override
    public void onChange(Owner owner) {
        /* take action */
    }
});

Realm on Android

Realm database works with Android framework seamlessly out of the box. To make integration of Realm on Android even easier, there are customised Android classes available (such as Realm adapters). The only thing to keep in mind when using Realm database on Android is that you cannot just pass Realm objects between threads, activities, fragments, etc.

Realm adapters

Realm provides adapter classes to help bind database data to UI widgets. There is RealmBaseAdapter to be used with ListView and RealmRecyclerViewAdapter which can be used with RecyclerView. To use them, just add a new dependency to your build.gradle file:

dependencies {
    compile 'io.realm:android-adapters:1.4.0'
}

Intents and threading

Because you cannot pass Realm objects directly between threads, activities, etc, it’s recommended to pass only identifiers of the objects you’re working with (for example a primary key).

An example of passing an ID through intents follows:

Intent intent = new Intent(getActivity(), AnotherActivity.class);
intent.putExtra("owner_id", owner.getId());
getActivity().startActivity(intent);

In the other activity, retrieve the reference and load the object from database using a newly acquired reference:
String ownerId = intent.getStringExtra("owner_id");
Realm realm = Realm.getDefaultInstance();
try {
    Owner owner = realm.where(Owner.class).equalTo("id", ownerId).findFirst();
    /* do something here */
} finally {
    realm.close();
}

Android Realm Gotchas

Even though Realm seems like a great option for storing data on Android, nothing is perfect and before adopting Android Realm in your projects you should consider the following gotchas I stumbled upon when using Realm in my own projects.

Primary keys

Android Realm doesn’t support auto-incrementing of primary keys. In order to use this feature, you have to implement it yourself. There are basically two options how to do that.

If you require the primary key IDs to be ordered, you should probably implement a PrimaryKeyFactory similar to this one (example taken from Github issue page):

public class PrimaryKeyFactory {

    private static Map<Class, AtomicLong> keys = new HashMap<Class, AtomicLong>();

    public static void initialize(Realm realm) {
        // 1. Loop through all classes using RealmSchema
        // 2. Determine the maximum value for each primary key field and save it in the 'keys' map.
    }

    // Automitically create next key
    public synchronized long nextKey(Class clazz) {
        return keys.get(clazz).incrementAndGet();
    }
}

On the other hand, if you don’t require the IDs to be ordered, an easier approach is to go with UUIDs as primary keys. A primary key can be generated using this simple command:
String id = UUID.randomUUID().toString();

Library size

Because the core of the Realm database is written in C++, the library’s native code has to be distributed for all the architectures. This adds to the APK size quite significantly. In order to eliminate this, you could apply a tip from my previous post to reduce APK size with libraries containing native code by building a separate build for each architecture.

Debugging

If you’re lucky to develop on Mac operating system, there is an official database browser available for Realm. This way you can view and also edit the content of your database. You still have to manually copy the realm file from your Android device though and only after that you can use the database browser.

Another option is to use Stetho debug tool together with a Realm plugin.

Conclusion

Android Realm brings new air into the Android storage implementations. It is easy to work with thanks to its simple interface and it outperforms SQLite in read and write performance as well as in speed of development. Thanks to automatical updating of database objects, you always work with the newest changes and they are synchronised across multiple threads too.

Android Realm comes with some handy features such as easy migration of database to a new scheme version, an easy-to-use writing of data in a background thread, or watching for a change of data using database listeners.

Overall, Android Realm is my number one option when it comes to databases on Android and I include it in every new project I work on. However, you should know about some missing features you might be used to from SQLite as the missing auto-incrementing of primary keys, slightly bigger library size, or a more complicated debugging if you’re not using Mac OS. Let’s hope the project developers solve these minor issues soon. Other than that, the library is very stable and full of useful features a modern mobile database should have.

If you’re not ready for a switch to a new database approach that Realm offers though, you can still use some of the countless ORM frameworks for SQLite on Android. One of my favourite ones is GreenDAO which I introduced in one of my previous posts.

Sources

Share this: