John Petitto About Blog

Why I Don't Use Realm Anymore

Since publishing this article, the team at Realm have released a new gradle plugin that alleviates many of the issues around adding functionality to your Realm objects.

If you haven’t heard of Realm before, it’s a mobile database technology for Android (iOS too). Opposed to SQLite, it allows you to work with data objects directly at the persistence layer. On top of that, there is a powerful functional-style query API and effort has been made to make it even faster than traditional SQLite operations. It was for these reasons I decided to give Realm a try.

When I first used Realm around a year ago, my initial impressions were quite good. I needed to persist some user data locally on the phone, but it was a bit too complex for SharedPreferences. Realm allowed me to quickly and cleanly write the necessary code to do this. There was no need to write any of the extra cruft that would have been required with SQLite.

The next project I worked on needed a sophisticated offline mode for when the user lacked a network connection. The data that I grabbed over the network would have to be stored locally on the phone. I decided to go all in with Realm and see how it would scale as my project grew.

What I quickly found out was Realm made working with my data model a burden. It has several limitations that I had to deal with across my codebase. I ended up trying to mitigate this with additional layers of abstraction on top of Realm.

Defining Objects

To demonstrate these limitations, let’s start with a Person object as a simple example:

public class Person extends RealmObject {
  private String name;
  private int age;
  
  public String getName() { return name; }
  public void setName(String name) { this.name = name; }
  
  public int getAge() { return age; }
  public void setAge(int age) { this.age = age; }
}

Notice that we must extend from RealmObject directly. This prevents us from leveraging any sort of inheritance in our data model.

We’re also not allowed to define instance methods beyond our setters and getters. If you were hoping to override equals or toString, you’re out of luck. Another consequence of this is that we’re limited to only using marker interfaces (annotations are fine too).

Not only are we limited to setters and getters, but we’re actually required to provide them. Therefore our data objects cannot be immutable! In addition, the setters and getters are merely proxy methods for Realm to replace with its own implementation. It’s not possible to manipulate the data, throw an exception, or log a message.

We do have the ability to provide non-default constructors, but we must ensure that an empty public constructor exists. This is a bit of an issue if you want a builder or factory method to be the only means of instantiation. Later on we’ll look at how we create objects with Realm.

There are limitations on the type of fields we can persist. All of the primitive types and their boxed counterparts are supported, along with String, Date, and byte[]. For other class types, they must extend from RealmObject in order to be persistable. Lists are supported through RealmList<? extends RealmObject>.

But this is where it stops. If we want to use an enum instead of an int, we can’t (found a reason to use @IntDef!). We’re also prohibited from using collection types such as Set and Map.

Creating and Updating Objects

To create an instance of our Person class, we must do the following:

Realm realm = Realm.getInstance(context);
realm.beginTransaction();
Person person = realm.createObject(Person.class);
person.setName("John");
person.setAge(25);
realm.commitTransaction();

You’ll notice that we have to wrap the creation of our Person object, and any modifications to it, in a transaction. It would be much more flexible if we could do this outside of a transaction and then persist it when we’re ready. Now we’re stuck writing this extra Realm code whenever we want to create or update our objects.

I mentioned before we’re able to define non-default constructors. For instance, we might have a constructor for Person that takes a name and age:

public class Person extends RealmObject {
  private String name;
  private int age;
  
  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
  
  public Person() {
    // required empty public constructor
  }
  
  // setters and getters
  ...
}

We no longer have to call the setters directly:

Person person = new Person("John", 25);
realm.beginTransaction();
Person realmPerson = realm.copyToRealm(person);
realm.commitTransaction();

It saves us from having to write a little extra code, but we’re still stuck inside of a transaction.

Mitigating These Issues

To prevent having to deal with these limitations across my codebase, I defined two sets of classes for my data objects: POJOs and Realm objects. I then created an abstraction that could map between the two.

This works, but I have two main issues with it. The first is that you end up with a lot of code in these mapping classes when you’re persisting many different types of objects. This can be a pain to manage and it’s easy for bugs to sneak in. The idea of object mapping certainly isn’t new nor are its issues.

The second is that I feel this defeats the purpose of using Realm in the first place. Being able to work with objects directly at the persistence layer is its main benefit. If we’re having to create abstractions on top of Realm so we can use POJOs instead, how much more beneficial is it over SQLite, or an ORM like DBFlow?

It’s worth mentioning that the Realm maintainers are aware of these limitations and it’s possible many of them could be resolved at some point (see here and here). Realm does have other strong merits too, such as performance and its ability to share data between iOS and Android.

I’m interested in hearing about your experiences with Realm. Have you found a way to make it work in your codebase or do you rely on other technologies for local persistence?