Realm DB | Android Pitfalls Demystified

Muhammad Waris
Embrace-It
Published in
7 min readApr 5, 2021

--

I have been using Realm DB in one of my production apps for over 2 years almost. And there are a lot of things that I have learned during this time that I would like to share. This article is related to the common mistakes that we make during the implementation of Realm DB for Android. I will try to explain the common mistakes and ways how we can avoid these mistakes.

Just to grasp the context and some brief background on Realm DB let me revise some of the basics related to Realm DB.

What is Realm DB?

Realm Database is an offline-first mobile object database in which you can directly access and store live objects without an ORM.

you can say it's an alternate DB option to use in Mobile applications besides SQLite, Room, and CoreData (IOS).

What makes Realm DB different from Other DB’s?

Here are some of the features that make Realm DB slightly different from other options we have:

  1. Purposefully built for offline first support.
  2. Support lightweight Native Query Language for Interaction with data.
  3. Supports Encryption build-In.
  4. Supports Live Objects (Objects always reflect the latest state of data in the database automatically).
  5. Easy to Scale.
  6. Supports both Android and IOS.

Internals of Realm DB

Realm DB is super easy to use and it powers you as a developer if you truly understand how to perfectly use it. Most of the issues that take hours and hours to debug and fix are almost always arise when we have comparatively less knowledge of how actually Realm DB is working under the hood.

Some people think that Realm DB is SQLite. That's a big misunderstanding. It's a completely custom engine developed in C++. Also, it's a No-SQL Database completely different from an SQL database.

One of the true power of Realm DB lies in its memory-map techniques that realm DB uses under the hood to provide us zero-copy experience.
Zero-Copy means when you have an object in your code, you are not working with the copy of that object, actually, you are working with the pointer that is pointing to that specific entry in the Database. So there is no actual memory consumption and overhead of copying objects.

Another cool thing about Realm DB is its live Updates. This makes it really easy to write less code to query objects again and again. For example, if you query an Object and you get Results object and while you hold that Object and in the meantime if the fields of that Object updates from somewhere then the objects you are holding will have the latest changes that were made to that specific object. You don’t need to query again to get the latest changes.
Isn't that cool? For sure it is.

Common Pitfalls | Solutions

  1. Not Respecting Thread-Confined Nature of Realm DB

One of the most common issue we face while using Realm DB in Android is the following one:

java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created. 

let’s examine that and try to find out why this happens.
Cause:

Realm DB when talking in terms of the mobile app is strictly thread-confined. This means you can’t move Realm objects between different threads. Like if you have Called Realm.getInstance(defaultConfig) from a UI thread and after getting the Results you can not update the Results in another Background thread and vice versa. You can only operate in a thread where the Objects were fetched first.

The same goes with the background thread. Updating Realm Objects in a UI thread after getting them in the background thread will result in the same exception mentioned above.

Solution:

The ideal solution to get rid of this issue from happening is that you can manage different Realm Instances to process per thread type. like for example, there should be one universal Realm Instance that is dedicated to access from UI thread, and in Background threads, you should always get the latest Realm Instance by calling Realm.getInstance(defaultConfig) again and do your thing within the context of that specific instance. After use doesn't forget to close that instance afterward using realm.close().

here is a link you can check in terms of implementing Global Realm Instance in Android thanks to EpicPandaForce.

2. Not Closing Realms Instances After Use

Another common mistake we make while using Realm DB is that we don't properly close realm instances after using them. This causes some performance problems as described in the official documentation.

Open Realm instance holds significant resources, some of which are not controlled by the Java memory manager. Java cannot automatically administer these resources. It is essential that code that opens a Realm instance closes it when it is no longer needed.

Another important thing to note here is that Realm uses an internal counted cache which means after the first call to Realm.getInstance(defaultConfig) each subsequent call to this method will increase the instance count. And the resources will be released only after all the instances of Realm are closed on a specific thread.

Solution:

Always try to use the following language constructs to free up the resources after you are done using them.

For Kotlin:

Realm.getInstance(defaultConfig).use { realm ->
realm.executeTransaction { db ->
// ...
// YOUR CODE TO INTERACT WITH REALM DB
// ...
}
}

For Java:

try(Realm realm = Realm.getDefaultInstance()) {
// ...
// YOUR CODE TO INTERACT WITH REALM DB
// ...
}

Almost always try to use them when you are in the Background thread and interacting with your Realm DB. And for your UI thread interactions, you should use one of the Universal Instances as discussed above.

For debugging purposes and making sure you are always closing the instances you are done using, you can write the following lines of code to get the exact count of local and global instances of Realm DB that are opened at any particular time.

val globalInstancesCount = Realm.getGlobalInstanceCount(getRealmConfig())
val localInstancesCount = Realm.getLocalInstanceCount(getRealmConfig())

Global Instance Count: current number of open Realm instances across all threads in the current process

Local Instance Count: current number of open Realm instances on the thread calling this method

3. Updating Realm Objects outside of executeTransaction

Another silly mistake we make is that we try to update Objects outside the executeTransaction block and face the following exception.

Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first

Cause:

Realm under the hood enforces the transactional model. Once you add an object to the realm then to update you always have to update that object inside a write transaction. This ensures that there are no possible conflicts. And Realm DB makes sure that there is only one write transaction at a time. Any other transactions more than one will be paused and will wait for the first one to finish. In this way, Realm DB ensures that there will always be atomic writes and changes will be saved to disk properly without conflicts.

One thing to take note of is that all write transactions are blocking. So to avoid any ANR’s in an application always do write transacrions in Background thread.

Solution:

Realm.getInstance(defaultConfig).use { realm ->
realm.executeTransaction { db ->
// ...
// YOUR CODE TO UPDATE REALM OBJECTS
// ...
}
}

4. Operating on Invalid Object

Another issue we face is when we try to access an Object and we face the following exception:

java.lang.IllegalStateException: Illegal State: 
Object is no longer valid to operate on. Was it deleted by another thread?

This exception occurs when the Object you are trying to operate on is either deleted from the Realm DB or the Realm instance by which you fetched this Object is Closed along with all other instances.

One of the way to reproduce this issue:

Realm realm = Realm.getInstance(defaultConfig);
Job job = realm.where(Job.class).equalTo("jobId",jobId).findFirst();
realm.close(); <---- Close of Realm Instance
String jobName = job.getJobName()
<---- Accessing Object After Close

One way to check if the object is valid by calling the job.isValid()

isValid() is the best way to check if the realm object you are operating on is a valid one, which means neither deleted from DB and it's safe to operate on.

5. Not using realm.executeTransaction(Realm.Transaction)

Most of the times when we try to update data in our realm we use the following bad pattern to commit changes:

Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
realm.copyToRealm(job)
realm.commitTransaction();

This seems quite perfect in a normal use case but think for a second what will happen to this code if for some reason this transaction fails?…will this transaction get canceled automatically if it fails? No.

It's a good practice to always cancel transactions that somehow fail to commit the changes. And for that to happen the above code is not helpful in any way.

A good approach to get rid of that issue is to use the following pattern

For Kotlin:

Realm.getInstance(defaultConfig).use { realm ->
realm.executeTransaction { db ->
// ...
// YOUR CODE TO UPDATE REALM OBJECTS
// ...
}
}

For Java:

try(Realm realm = Realm.getDefaultInstance()) {
// ...
// YOUR CODE TO INTERACT WITH REALM DB
// ...
}

The best thing executeTransaction does is that it takes care of beginTransaction() and commitTransaction() and in case if an exception happens while committing the changes it auto cancels the transaction.
Isn't that cool.

Conclusion:

Almost all of the issues I have tried to compile here are the ones that are most commonly occurring in any android app where Realm DB is implemented. We can easily avoid these common mistakes and get going. I have tried to shed some light on the internal as well. without knowing Realm DB under the hood will result in spending more time on debugging and tracing the point of exception occurrence. I hope this helped you to know some more about Realm DB as a whole. Thanks :)

--

--