Android

Realm

Introduction#

Realm Mobile Database is an alternative to SQLite. Realm Mobile Database is much faster than an ORM, and often faster than raw SQLite.

Benefits

Offline functionality, Fast queries, Safe threading, Cross-platform apps, Encryption, Reactive architecture.

Remarks#

When you use Realm, you must remember that you mustn’t pass RealmObjects, RealmResults and Realm instances between threads. If you need a query on a given thread, open a Realm instance on that thread. At the termination of the thread, you should close the Realm.

LEGAL NOTE: You understand that the Software may contain cryptographic functions that may be subject to export restrictions, and you represent and warrant that you are not located in a country that is subject to United States export restriction or embargo, including Cuba, Iran, North Korea, Sudan, Syria or the Crimea region, and that you are not on the Department of Commerce list of Denied Persons, Unverified Parties, or affiliated with a Restricted Entity.

Adding Realm to your project

Add the following dependency to your project level build.gradle file.

dependencies {
    classpath "io.realm:realm-gradle-plugin:3.1.2"
}

Add the following right at the top of your app level build.gradle file.

apply plugin: 'realm-android'

Complete a gradle sync and you now have Realm added as a dependency to your project!

Realm requires an initial call since 2.0.0 before using it. You can do this in your Application class or in your first Activity’s onCreate method.

Realm.init(this); // added in Realm 2.0.0
Realm.setDefaultConfiguration(new RealmConfiguration.Builder().build());

Realm Models

Realm models must extend the RealmObject base class, they define the schema of the underlying database.

Supported field types are boolean, byte, short, int, long, float, double, String, Date, byte[], links to other RealmObjects, and RealmList<T extends RealmModel>.

public class Person extends RealmObject {
    @PrimaryKey //primary key is also implicitly an @Index 
                //it is required for `copyToRealmOrUpdate()` to update the object.
    private long id;

    @Index //index makes queries faster on this field
    @Required //prevents `null` value from being inserted
    private String name; 

    private RealmList<Dog> dogs; //->many relationship to Dog

    private Person spouse; //->one relationship to Person

    @Ignore
    private Calendar birthday; //calendars are not supported but can be ignored

    // getters, setters
}

If you add (or remove) a new field to your RealmObject (or you add a new RealmObject class or delete an existing one), a migration will be needed. You can either set deleteIfMigrationNeeded() in your RealmConfiguration.Builder, or define the necessary migration. Migration is also required when adding (or removing) @Required, or @Index, or @PrimaryKey annotation.

Relationships must be set manually, they are NOT automatic based on primary keys.

Since 0.88.0, it is also possible to use public fields instead of private fields/getters/setters in RealmObject classes.

It is also possible to implement RealmModel instead of extending RealmObject, if the class is also annotated with @RealmClass.

@RealmClass
public class Person implements RealmModel {
    // ...
}

In that case, methods like person.deleteFromRealm() or person.addChangeListener() are replaced with RealmObject.deleteFromRealm(person) and RealmObject.addChangeListener(person).

Limitations are that by a RealmObject, only RealmObject can be extended, and there is no support for final, volatile and transient fields.

It is important that a managed RealmObject class can only be modified in a transaction. A managed RealmObject cannot be passed between threads.

List of primitives (RealmList<Integer/String/…>)

Realm currently does not support storing a list of primitives. It is on their todo list (GitHub issue #575), but for the meantime, here is a workaround.

Create a new class for your primitive type, this uses Integer, but change it for whatever you want to store.

public class RealmInteger extends RealmObject {
    private int val;

    public RealmInteger() {
    }

    public RealmInteger(int val) {
        this.val = val;
    }

    // Getters and setters
}

You can now use this in your RealmObject.

public class MainObject extends RealmObject {

    private String name;
    private RealmList<RealmInteger> ints;

    // Getters and setters
}

If you are using GSON to populate your RealmObject, you will need to add a custom type adapter.

Type token = new TypeToken<RealmList<RealmInteger>>(){}.getType();
Gson gson = new GsonBuilder()
        .setExclusionStrategies(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes f) {
                return f.getDeclaringClass().equals(RealmObject.class);
            }

            @Override
            public boolean shouldSkipClass(Class<?> clazz) {
                return false;
            }
        })
        .registerTypeAdapter(token, new TypeAdapter<RealmList<RealmInteger>>() {

            @Override
            public void write(JsonWriter out, RealmList<RealmInteger> value) throws IOException {
                // Empty
            }

            @Override
            public RealmList<RealmInteger> read(JsonReader in) throws IOException {
                RealmList<RealmInteger> list = new RealmList<RealmInteger>();
                in.beginArray();
                while (in.hasNext()) {
                    list.add(new RealmInteger(in.nextInt()));
                }
                in.endArray();
                return list;
            }
        })
        .create();

try-with-resources

try (Realm realm = Realm.getDefaultInstance()) {
    realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                //whatever Transaction that has to be done
            }
    });
    //No need to close realm in try-with-resources
}

The Try with resources can be used only from KITKAT (minSDK 19)

Sorted queries

In order to sort a query, instead of using findAll(), you should use findAllSorted().

RealmResults<SomeObject> results = realm.where(SomeObject.class)
                                            .findAllSorted("sortField", Sort.ASCENDING);

Note:

sort() returns a completely new RealmResults that is sorted, but an update to this RealmResults will reset it. If you use sort(), you should always re-sort it in your RealmChangeListener, remove the RealmChangeListener from the previous RealmResults and add it to the returned new RealmResults. Using sort() on a RealmResults returned by an async query that is not yet loaded will fail.

findAllSorted() will always return the results sorted by the field, even if it gets updated. It is recommended to use findAllSorted().

Async queries

Every synchronous query method (such as findAll() or findAllSorted()) has an asynchronous counterpart (findAllAsync() / findAllSortedAsync()).

Asynchronous queries offload the evaluation of the RealmResults to another thread. In order to receive these results on the current thread, the current thread must be a looper thread (read: async queries typically only work on the UI thread).

RealmChangeListener<RealmResults<SomeObject>> realmChangeListener; // field variable

realmChangeListener = new RealmChangeListener<RealmResults<SomeObject>>() {
    @Override
    public void onChange(RealmResults<SomeObject> element) {
        // asyncResults are now loaded
        adapter.updateData(element);
    }
};

RealmResults<SomeObject> asyncResults = realm.where(SomeObject.class).findAllAsync();
asyncResults.addChangeListener(realmChangeListener);

Using Realm with RxJava

For queries, Realm provides the realmResults.asObservable() method. Observing results is only possible on looper threads (typically the UI thread).

For this to work, your configuration must contain the following

realmConfiguration = new RealmConfiguration.Builder(context)       //
                          .rxFactory(new RealmObservableFactory()) //
                             //...
                          .build();

Afterwards, you can use your results as an observable.

Observable<RealmResults<SomeObject>> observable = results.asObservable();

For asynchronous queries, you should filter the results by isLoaded(), so that you receive an event only when the query has been executed. This filter() is not needed for synchronous queries (isLoaded() always returns true on sync queries).

    Subscription subscription = RxTextView.textChanges(editText).switchMap(charSequence -> 
        realm.where(SomeObject.class)
             .contains("searchField", charSequence.toString(), Case.INSENSITIVE)
             .findAllAsync()
             .asObservable())
    .filter(RealmResults::isLoaded) //
    .subscribe(objects -> adapter.updateData(objects));

For writes, you should either use the executeTransactionAsync() method, or open a Realm instance on the background thread, execute the transaction synchronously, then close the Realm instance.

public Subscription loadObjectsFromNetwork() {
    return objectApi.getObjects()
        .subscribeOn(Schedulers.io())
        .subscribe(response -> {
            try(Realm realmInstance = Realm.getDefaultInstance()) {
                realmInstance.executeTransaction(realm -> realm.insertOrUpdate(response.objects));
            }
        });
}

Basic Usage

Setting up an instance

To use Realm you first need to obtain an instance of it. Each Realm instance maps to a file on disk. The most basic way to get an instance is as follows:

// Create configuration
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(context).build();

// Obtain realm instance
Realm realm = Realm.getInstance(realmConfiguration);
// or
Realm.setDefaultConfiguration(realmConfiguration);
Realm realm = Realm.getDefaultInstance();

The method Realm.getInstance() creates the database file if it has not been created, otherwise opens the file. The RealmConfiguration object controls all aspects of how a Realm is created - whether it’s an inMemory() database, name of the Realm file, if the Realm should be cleared if a migration is needed, initial data, etc.

Please note that calls to Realm.getInstance() are reference counted (each call increments a counter), and the counter is decremented when realm.close() is called.

Closing an instance

On background threads, it’s very important to close the Realm instance(s) once it’s no longer used (for example, transaction is complete and the thread execution ends). Failure to close all Realm instances on background thread results in version pinning, and can cause a large growth in file size.

Runnable runnable = new Runnable() {
    Realm realm = null;
    try {
        realm = Realm.getDefaultInstance();
        // ...
    } finally {
        if(realm != null) {
            realm.close();
        }
    }
};

new Thread(runnable).start(); // background thread, like `doInBackground()` of AsyncTask

It’s worth noting that above API Level 19, you can replace this code with just this:

try(Realm realm = Realm.getDefaultInstance()) {
    // ...
}

Models

Next step would be creating your models. Here a question might be asked, “what is a model?“. A model is a structure which defines properties of an object being stored in the database. For example, in the following we model a book.

public class Book extends RealmObject {
 
    // Primary key of this entity
    @PrimaryKey
    private long id;
 
    private String title;

    @Index // faster queries
    private String author;
 
    // Standard getters & setter
    public long getId() {
        return id;
    }
 
    public void setId(long id) {
        this.id = id;
    }
 
    public String getTitle() {
        return title;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
 
    public String getAuthor() {
        return author;
    }
 
    public void setAuthor(String author) {
        this.author = author;
    }
}

Note that your models should extend RealmObject class. Primary key is also specified by @PrimaryKey annotation. Primary keys can be null, but only one element can have null as a primary key. Also you can use the @Ignore annotation for the fields that should not be persisted to the disk:

@Ignore
private String isbn;

Inserting or updating data

In order to store a book object to your Realm database instance, you can first create an instance of your model and then store it to the database via copyToRealm method. For creating or updating you can use copyToRealmOrUpdate. (A faster alternative is the newly added insertOrUpdate()).

// Creating an instance of the model
Book book = new Book();
book.setId(1);
book.setTitle("Walking on air");
book.setAuthor("Taylor Swift")

// Store to the database
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        realm.insertOrUpdate(book);
    }
});

Note that all changes to data must happen in a transaction. Another way to create an object is using the following pattern:

Book book = realm.createObject(Book.class, primaryKey);
...

Querying the database

  • All books:

    RealmResults<Book> results = realm.where(Book.class).findAll();
  • All books having id greater than 10:

    RealmResults<Book> results = realm.where(Book.class)
                                      .greaterThan("id", 10)
                                      .findAll();
  • Books by 'Taylor Swift' or '%Peter%':

    RealmResults<Book> results = realm.where(Book.class)
                                      .beginGroup()
                                          .equalTo("author", "Taylor Swift")
                                          .or()
                                          .contains("author", "Peter")
                                      .endGroup().findAll();

Deleting an object

For example, we want to delete all books by Taylor Swift:

// Start of transaction
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        // First Step: Query all Taylor Swift books
        RealmResults<Book> results = ...
        
        // Second Step: Delete elements in Realm
        results.deleteAllFromRealm();
    }
});

This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow