Realm Auto-Updated Objects: What you need to know

Frank
5 min readMar 2, 2017

--

Realm is blazingly fast and uncomplicated to use. Unless you start using its auto-updated objects; then there is a lot to keep in mind.

Auto-updated objects or managed objects are model objects that extend RealmObject and auto-update on database changes. You can attach listeners to them to be notified when the they change. More info here.

It sounds great. Using it however, you will encounter some drawbacks. Most problems are listed here, with some tips and alternatives to overcome them.

Database code all over your project

Having your database code centralised, in Data Access Object (DAO) classes or the like, is hardly possible when using Realm’s auto-updated objects. Two main reasons:

Transactions everywhere

Every setter on every model object needs to be inside a Realm transaction block. Using lambda expressions, the calls are relatively clean:

realm.executeTransaction(r -> user.setFirstName(firstName));

These blocks will be all over your project within no time. Forgetting to wrap a setter method (or a constructor call) in a transaction will crash your app. This will happen a lot in the first weeks of adopting auto-updated objects.

Managing instances

Auto-updated objects cannot be shared across threads. The same is true for Realm instances.

All threads, and often activities and fragments, will have to open and close Realm instances. You’ll be thinking constantly “On which thread am I?”. If you cross thread boundaries with model objects or Realm instances, you will crash your app.

Realm open and close methods will end up throughout your multithreaded project. They will appear inside your activity’s and fragment’s lifecycle callbacks and inside many methods on your worker threads. Don’t forget closing them after use; code review it all, carefully. Try-with-resources eases the pain on worker threads:

try (Realm realm = Realm.getDefaultInstance()) {
AnalyticsEventDao.createOrUpdate(realm, completedEvents);
... // a lot of stuff
}

In this setup, your realm instance will be auto-closed at the end of your try block, also if a future developer adds a “return X” somewhere inside this method or when an exception is thrown.

Retrofit and Gson

Versions: Realm 3.5.0, Retrofit 2.3.0.

If you use Retrofit 2 then you probably use its default parser: Gson. Combining this library will Realm usage causes some problems, hereby the solutions.

Gson sees Realm objects as empty.

Because Gson relies heavily on reflection and Realm populates data only when a getter is called, you will have to copy your auto-updated objects before you put them on Retrofit’s requests. If you don’t, Gson will parse all your object’s fields to nulls and zero’s in its json. Fortunately, disconnecting your object before posting is easy:

User unmanagedUser = realm.copyFromRealm(user);

Debugger objects empty

The Android Studio debugger has the same problem. It cannot show you the values of fields inside your model objects. Using the debugger’s ‘Evaluate Expression’ does not solve this for me. Realm is apparently considering writing a plugin for the debugger to solve this problem. I don’t know any temporary solutions. More info here.

Transient keyword crashes Realm

Realm will throw an exception if you try to use the transient keyword. Use @Ignore instead, although this is not supported by Gson. If you want to save fields in Realm but not update these with changes from the server, you have two main options:

  1. Marking all fields that you do want to update with @Expose. Or, if you think that is too cumbersome:
  2. Create a custom @Exclude annotation, as explained here.

Arrays of primitives from server

You will probably receive json arrays of primitives from your server. These are not supported in Realm (yet). Realm recommends saving these arrays in normal Strings. You can either do your own split and join on the fly, or use one of the solutions mentioned in ‘RealmList<RealmString> is an anti-pattern!’.

EDIT: I created this solution for it, a GSON deserializer. I think that is the best solution, over any of the above.

Async Realm methods

TL;DR: Probably the best solution is not using async Realm calls. Go on a background thread manually when you want to query or write asynchronously, for example using RxJava.

If you do want to use Realm’s async methods…

If one query is async, all writes must be async

If you call a setter method on a model object, you either have to do it async, or not use any async Realm calls in your project at all. This goes over classes; if you do something async on one model class, you must use async writes for all model classes:

“Warning: REALM_JAVA: Mixing asynchronous queries with local writes should be avoided. Realm will convert any async queries to synchronous in order to remain consistent. Use asynchronous writes instead. You can read more here: https://realm.io/docs/java/latest/#asynchronous-transactions

Async calls on a background thread are illegal

Async calls cannot be executed on a background thread, you will get an IllegalStateException. Duplicate some of your DAO methods and add annotations to warn developers who pick the wrong method for their thread:

@UiThread
public static void createOrUpdateAsync(final List<Message> messages) { ... }
@WorkerThread
public static void createOrUpdate(final List<Message> messages)
{ ... }

Long running async transactions need separate Realm instances

The Realm instance tied to the activity or fragment can get closed when you are still running your async write, which will cause an IllegalStateException. Open a dedicated one for your long running transactions and close it on its callbacks:

Realm realm = Realm.getDefaultInstance();
realm.executeTransactionAsync(localRealm -> localRealm
.where(Message.class)
.equalTo(Message.FIELD_LANGUAGE, language)
.findAll().deleteAllFromRealm(),
() -> realm.close(), error -> realm.close());

Note the use of ‘localRealm’ here. If you would use realm.where() instead, your application would crash because ‘realm’ is initiated on a different thread.

Fetch in async transaction

Make sure to fetch objects that you are changing, inside the actual async transaction itself:

List<Pages> pages = PageDao.getPages(localRealm); // UI thread
...
realm.executeTransactionAsync(localRealm -> {
List<Pages> localPages = PageDao.getPages(localRealm);
.... // update localPages, not pages
});

Also: transactions inside a transaction are not allowed.

Save Integration

An alternative for auto-updated model objects is Safe-Integration. This means you copy your Realm models into disconnected model POJOs. By doing so, the objects will not auto-update anymore and you can attach no listeners to them, but it does take away most of the problems described in this document.

Further reading

This article describes other common mistakes on Realm usage: How to use Realm for Android like a champ, and how to tell if you’re doing it wrong

That’s it

There are many things to keep in mind while coding with auto-updated Realm objects, mainly on threading. The only way to find your mistakes is by test-running the app and hopefully spotting them. Together with having database code all over your project and your debugger not being able to show model contents, these are at least things to consider before you start using auto-updated objects.

If the Realm development team would add lint warnings to some of the items mentioned in this document, that would greatly improve development experience.

Apart from these drawbacks, I would not discourage usage of auto-updated Realm objects to teams that include at least one senior developer. That is, excluding usage of async Realm calls. Having your views auto-update after model changes is great. Be prepared to do a lot of thinking on your database handling though. And code-review each other carefully, all the time.

--

--