time

Realm database tutorial for Android

android_realm.png

Realm is a lightweight database that can replace both SQLite and ORM libraries in your Android projects.

Compared to SQLite, Realm is faster and has lots of modern features, such as JSON support, a fluent API, data change notifications, and encryption support, relationships, migrations all of which make life easier for Android developers. Realm occupies very less memory space compared to SQLite.

Realm has own C++ core and aims to provide a mobile-first alternative to SQLite. Realm store data in a universal, table-based format by a C++ core.

A detailed explanation on Realm internals, by Christian Melchior is here.

Installing

Installing Realm as a Gradle plugin is a two step process.

Step 1: Add the following class path dependency to the project level build.gradle file.

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

Step 2: Apply the realm-android plugin to the top of application level build.gradle file.

apply plugin: 'realm-android'

A Realm is similar to a SQLite database. It has a file associated with it, which, once created, will persist on Android’s file system. You can find .realm file under the Data/Data/Package Name/files directory and pull it to your computer.

To create a new Realm, you can call the static Realm.getInstance method from inside any Activity.

Realm db = Realm.getInstance(context);

Note that calling Realm.getInstance, without passing a RealmConfiguration to it, results in the creation of a Realm file called default.realm.

If you want to add another Realm to your app, you must use a RealmConfiguration.Builder object and give the Realm file a unique name.

Realm db =
    Realm.getInstance(
        new RealmConfiguration.Builder(context)
            .name("movies.realm")
            .build()
);

CRUD operations

Any Java class that is serializable and has a default constructor and has getter/setter methods for its member variablescan can be stored in a Realm once it extends the RealmObject class. For example, instances of the following class can be easily stored in a Realm:

public class Movie extends RealmObject {
    @PrimaryKey
    private String id;
    private String title;
    private int year;

    public Movie() { }

    public String getId() { return id; }
    public void setId(String id) { this.id = id; }

    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }

    public int getYear() { return year; }
    public void setYear(int year) { this.year = year; }
}

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.

Realm supports following annotations

  • Annotation @Required enforces checks to disallow null values.
  • Annotation @Ignore marks field should not be persisted to disk.
  • Annotation @Index adds a search index to the field.
  • Annotation @PrimaryKey uniquely identifies each record in the database

Realm supports boolean, byte, short, int, long, float, double, string, date, byte[] data types. The boxed types Boolean, Byte, Short, Integer, Long, Float, Double can also be used in model class. Using these types, it is possible to set the value of a field to null.

Realm forces you to execute all write operations inside a transaction. To start a new transaction, use the beginTransaction method. Similarly, to end the transaction, use the commitTransaction method.

You always have an opportunity to cancel your transactions, just call:

realm.cancelTransaction();

Here’s how you would create and save an instance of the Movie class:

db.beginTransaction();
// create an object
Movie movie = db.createObject(Movie.class);

// set its fields
movie.setTitle("Movie 1");
movie.setYear(2016);
db.commitTransaction();

You might have noticed that movie was not created using the constructor of the Movie class. For a Realm to manage an instance of a RealmObject, the instance must be created using the createObject method.

If you must use the constructor, don’t forget to use the copyToRealm method of the relevant Realm object before you commit the transaction. Here’s an example:

// create the object
Movie movie = new Movie();
movie.setTitle("Movie 2");
movie.setYear(2017);

db.beginTransaction();
Movie copyMovie = db.copyToRealm(movie);
db.commitTransaction();

Realm does not currently support auto increment of Primary Key. There are three work-around regarding auto increment of primary key.

First way. Use String as ID and initialize it as

String id = UUID.randomUUID().toString();

Second way. Query for the max primary key value each time you want to insert data and then increment that by 1 and set as the primary key.

long id = db.where(Movie.class).max("id").longValue();
movie.setId(id);

Third way. Run the query to find the ID once, probably when the app launches and then keep a reference to that max ID value and then increment and get that value as needed like so:

AtomicLong movieKey = new AtomicLong(db.where(Movie.class).max("id").longValue() + 1);

long id = movieKey.getAndIncrement();
movie.setId(id);

Realm offers a very intuitive and fluent API for creating queries. To create a query, use the where() method of the relevant Realm object and pass the class of the objects you are interested in. After creating the query, you can fetch all results using the findAll() method, which returns a RealmResults object. In the following example, we fetch and print all objects of type Movie:

RealmResults<Movie> movies = db.where(Movie.class).findAll();

for(Movie movie : movies) {
    Log.d(TAG, movie.getTitle());
}

Fetch one object

Movie movie = db.where(Movie.class).equalTo("id", id).findFirst();

Realm offers several aptly named methods, such as beginsWith(), endsWith(), lesserThan() and greaterThan(), you can use to filter the results. The following code shows you how you can use the greaterThan() method to fetch only those Movie objects whose year is greater than 2016:

RealmResults movies = db.where(Movie.class).greaterThan("year", 2016).findAll();

or query field which contains something

db.where(Movie.class)
    .contains("title", "Movie 1")
    .or()
    .contains("title", "Movie 2")
    .findAll();

If you want the results of the query to be sorted, you can use the findAllSorted() method. As its arguments, it takes a String specifying the name of the field to sort by and a Boolean specifying the sort order.

// sort by title, in descending order
RealmResults<Movie> movies = db.where(Movie.class).findAllSorted("title", false);

Update object

Movie movie = db.where(Movie.class).equalTo("id", 1).findFirst();
db.beginTransaction();
movie.setTitle("Movie 3");
db.commitTransaction();

Delete object

RealmResults<Movie> movies = db.where(Movie.class).equalTo("id", 1).findAll();
db.beginTransaction();
movies.remove(0);
db.commitTransaction();

Clear all

db.beginTransaction();
db.clear(Movie.class);
db.commitTransaction();

Importing objects

We can import list of objects to Realm from List that was created before.

for (Movie m : movies) {
    db.beginTransaction();
    db.copyToRealm(m);
    db.commitTransaction();
}

It’s also possible to load data into the Realm database from JSON format represented by a String, InputStream or JSONObject. A single object can be added by using the createObjectFromJson()

db.beginTransaction();
db.createObjectFromJson(Movie.class, "{ title: \"Movie 4\", year: 1991 }");
db.commitTransaction();

A list of objects is loaded using the createAllFromJson() method

// InputStream is = new FileInputStream(new File("path_to_file"));

Resources resources = getResources();
InputStream is = resources.openRawResource(R.raw.movies);

db.beginTransaction();
db.createAllFromJson(Movie.class, is);
db.commitTransaction();

Relationships

Also Realm supports Many-to-One, Many-to-Many 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&lt;T&gt; field declaration. RealmList behaves very much like regular Java List object. A nice feature is that Realm will never return an instance of RealmList with null value. The returned object is always a list, possibly empty.

Example

Let's modify Application instance and add Realm configurations. Create a class named MyApplication.java under app package.

import android.app.Application;
import io.realm.Realm;
import io.realm.RealmConfiguration;

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
        RealmConfiguration cfg = new RealmConfiguration.Builder()
            .name(Realm.DEFAULT_REALM_NAME)
            .schemaVersion(0)
            .deleteRealmIfMigrationNeeded()
            .build();
        Realm.setDefaultConfiguration(cfg);
    }
}

Setting a default configuration in your custom Application class, will ensure that it is available in the rest of your code.

Open AndroidManifest.xml and add the MyApplication to &lt;application&gt; tag.

<application
    android:name=".MyApplication"
    ...>
</application>

In the model package add a class named Movie.java, see above. Realm data models are created by extending the the RealmObject base class. A Realm data model also supports public, protected and private fields as well as custom methods.

We'll create MainActivity with simple layout and two buttons. First, button will add new Movie record , second button will list all records to console.

public class MainActivity extends AppCompatActivity {
    private Realm db;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        db = Realm.getDefaultInstance();
    }

    public void addMovie(View v) {
        String id = UUID.randomUUID().toString();;

        Movie movie = new Movie();
        movie.setId(id);
        movie.setTitle("Movie " + id);
        movie.setYear(2016);

        db.beginTransaction();
        db.copyToRealm(movie);
        db.commitTransaction();
    }

    public void listMovie(View v) {
        RealmResults<Movie> movies = db.where(Movie.class).findAll();

        for(Movie movie : movies) {
            Log.d(TAG, movie.getTitle());
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // it is good practice to close Realm object
        db.close();
    }

}

Debugging

Under Linux we can use Stetho debug tool together with a Realm plugin.

Useful links

comments powered by Disqus