Introduction to Realm Database with Kotlin

Realm Database has firmly inserted itself into the toolchain and workflow of modern mobile app development in just a few years. This is because Realm database solved a nagging developer pain point so efficiently that the core concepts of Realm Database such as object-based data persistence and or expressive, fluent queries are becoming industry standard – kudos to Room and ObjectBox. This tutorial is a practical introduction to Realm Database with Kotlin. Specifically, I will be focusing on live, auto-updating objects, a unique feature of Realm that is at the core of Realm’s reactive architecture whether you are working with standalone/local Realm database or if your database is backed by Realm Object Server or Realm Cloud.

One way to learn a new technology such as Realm Database is to jump in head first and start using it to create a project and that is what we will do. To demonstrate this concept of live/auto-updating object we will create a simple Android Guest List App with Kotlin called Pronto Guest List. This app will be used to check in users to an event. This app will contain two lists for users that need to be checked in and users that need to check out. Each row in the list contains the name of the user, a profile image and a button to either check-in or check-out. When a user checks it, they are removed from the check-in list on the left-hand side and added to the checked-out list on the right and when they check out they are removed from the checked-out list and added to the checked-in list. The app should also record and display a timestamp of when they checked in. Figure 01 below shows what this app will look like.

You can download the APK of this demo app from here – Pronto Guest List APK (Supports only Tablets in Landscape orientation)

Introduction to Realm Database

Realm Database was built from the ground up to increase developer productivity by being a better replacement for SQLite. With Realm, data is directly exposed as objects and queryable by code, removing the need for ORM’s.  What is a Realm you may ask,  well, a Realm is an instance of a Realm Mobile Database container. Quoting the official Realm documentation, “Realms are our equivalent of a database: they contain different kinds of objects, and map to one file on disk”. In other words, Realms are databases that do not require a separate mapping from Kotlin data class (aka Java objects) to the persisted version on the disk. By eliminating the need to map data to an intermediary format, Realm allows developers to work with data using their programming language such as Java, Kotlin, Swift, C# or React. The actual data persistence functionality is provided by a C++ core upon which the Realm team adds bindings for different platforms which is why Realm manages to be both native and cross-platform, so you can support both an Android and IOS app with the same data set. Figure 02  below illustrates this architecture. Credits to Tim Oliver’s presentation.

Realm was first launched in 2014 and today reports over 2 billion install, this feat is made possible in part by the constant evolution of Realm which leaves even Realm enthusiasts like me struggling to stay up to date. So, if you are slightly confused about Realm’s product offering, you are not alone and following are some pointers for you. Offline first is Realms battle cry because Realm Database can fulfill its core function of data persistence and retrieval offline,  for Android this is made possible by Realm Java library that you add as a gradle plugin. This library can function as either a standalone database or as a client in a client/server architecture.

A Realm that is backed by a server is called a synched Realm. I will write a follow-up tutorial on synched Realm but do know that after enabling synchronization, additional capabilities are unlocked in the Realm library. Even after enabling synchronization you can still use Realm as a standalone database, you just have to obtain a local configuration.  Realm uses a Configuration object to control how a Realm is created. There are RealmConfiguration and SyncConfiguration which applies to local and sync Realms respectively.  We will now continue this tutorial by creating a demo app shown in Figure01 with Kotlin, you can follow along or download the source code from here.

Create Android Studio Project

To follow along, create a standard Android Studio project named Pronto Guest List with Kotlin. I have named the MainActivity GuestListActivity,  and since this app runs in tablet dual screen mode, I used the Master/Detail template and then I had to remove all the boilerplate code. You can do the same or you can just copy from the source code found at the bottom of this post. Using Realm Database on Android is relatively easy, you install, initialize and instantiate and you are good to go. Here are the five steps to start using Realm Database on Android.

  • Install – you add Realm library to Android Studio as a classpath dependency, the documentation contains the latest version.
  • Initialize – once Realm is installed and you have re-built the project, you will need to initialize Realm. The application class or the entry point of your app is an ideal place to do this one-time initialization. with the command Realm.init
  • Obtain an instance – to actually start working with Realm you need to obtain an instance of Realm. This is usually done in the onCreate of your Activity or Fragment where you want to use Realm and the command to get a Realm’s default instance is val realm: Realm = Realm.getDefaultInstance()
  • Work with Realm – now that you have an instance of Realm you can use it to perform your CRUD operations. Realm has fluent and often self-describing APIs that you will learn and appreciate as you use them. Here is a sample covering the CRUD operation against a Guest data class.
    • Create – creates a new Guest object with a primary key of random generated String var guest: Guest = realm.createObject(Guest::class.java, UUID.randomUUID().toString())
    • Read – finds a guest whose name is John Doe val john = realm.where(Guest::class.java).equalTo("name", "John Doe").findFirst()
    • Update – someone has changed Johns last name to Smith so we need to update his record realm.copyToRealmOrUpdate(john) Note that you do not need to do anything if the update is made within a transaction.
    • Delete – John has left the event so we need to delete his record john!!.deleteFromRealm()
  • Close an instance – it is very important to close the Realm instance once you are done using it. The onDestroy callback of the Activity or Fragment is a good place to close Realm, this takes care of native memory deallocation and file descriptors according to Realm documentation.

Add Model Class

Now that you have created an Android Studio project and added Realm. We need to create a model class to use in this project except in Kotlin we use data class instead of model classes. Since this is a very simple project we will be using only one data class named Guest. And here is the data class definition for Guest.kt

open class Guest : RealmObject() {

    @PrimaryKey
    var id: String? = null
    var name: String? = null
    var email: String? = null
    var gender: String? = null
    var checkedIn: Boolean = false
    var checkInTime: Long = 0

    init {
        this.checkedIn = false
    }
}

Notice that the data class has to be decorated with the open keyword so that other classes can inherit from it and in the initializer block we are setting the default value of checked in to false. In Kotlin, there are few places you can put initialization code that will be executed for each instantiation of that class and these include using initializer block, using property initializers or using constructor. Using a property initializer for the checkedIn property of the Guest class would look like this instead.

open class Guest(var checkedIn: Boolean = false) : RealmObject() {

    @PrimaryKey
    var id: String? = null
    var name: String? = null
    var email: String? = null
    var gender: String? = null
    var checkInTime: Long = 0

}

Add Default Data

To add default data to this app, we are going to use a free mock data generator like Mockaro.com to generate a thousand mock users. The source code for this demo app contains a mock_users.json file which contains one thousand mock users. The profile pictures are some sample pictures that I downloaded from Random User Generator and then randomly shared it among the mock users. Add the JSON file to the assets folder and then create an IntentService called DefaultDataIntentService and in this IntentService, we want to add a method that creates Realm objects from the JSON file content. Here is the code for DefaultDataIntentService.

class DefaultDataIntentService : IntentService("DefaultDataIntentService") {
    private lateinit var realm: Realm

    override fun onHandleIntent(intent: Intent?) {
        try {
            loadJsonFromStream()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    @Throws(IOException::class)
    private fun loadJsonFromStream() {
        val stream = assets.open("mock_users.json")

        // Open a transaction to store items into the realm
        realm = Realm.getDefaultInstance()
        realm!!.beginTransaction()
        try {
            realm!!.createAllFromJson(Guest::class.java, stream)
            realm!!.commitTransaction()
        } catch (e: IOException) {
            // Remember to cancel the transaction if anything goes wrong.
            realm!!.cancelTransaction()
        } finally {
            stream?.close()
        }
        realm!!.close()
    }

}

And in the GuestListActivity, we add a boolean flag to the SharedPreference and check this flag to see if the user is running the app for the first time and if yes, the DefaultDataIntentService is launched. Here is the updated onCreate() method of the MainActivity.

  
   sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
        val firstRun = sharedPreferences!!.getBoolean("first_run", true)
        if (firstRun) {
            val defaultDataIntent = Intent(this, DefaultDataIntentService::class.java)
            startService(defaultDataIntent)
            val editor = sharedPreferences!!.edit()
            editor.putBoolean("first_run", false).commit()
        }

Realm Database Auto Updating Feature

This project uses two Fragments named CheckInFragment and CheckoutFragment participating in a dual screen layout. Each Fragment is using RecyclerView to show a list of users coming from the database. So far, that is standard Android components, nothing unique. Where Realm comes in is how the changes in one list are communicated to the other list.  Back in the days of raw SQLite and Content Provider, we may need to use Loader Framework and/or events pub/sub to notify data-driven UI that the underlying data has changed. Modern database technologies such as Firebase uses ValueEvent listener to surface data changes to the UI. With Realm Database it gets even better, there is no need for the round trip from the UI to the database to update the UI because the data that is shown in the UI is a Live Object.

According to Realm documentation once an object has been added to a Realm, modifying that object in your code modifies it in the Realm, too. You don’t need to call an update method or re-add it to the Realm to persist your changes. They’ll be updated correctly. The code snippet below shows how we populate the checked-in list in Figure 01.

   private fun setupRecyclerView(checkInRecyclerView: RecyclerView) {
        val checkedOutGuestList = realm!!.where(Guest::class.java).equalTo("checkedIn", false).findAllAsync().sort("name")
        checkedOutGuestList.addChangeListener(changeListener)
        adapter = GuestListAdapter(checkedOutGuestList, context!!, object : GuestListAdapter.OnCheckInButtonClickedListener {
            override fun checkInButtonClicked(guest: Guest) {
                realm!!.executeTransaction { realm ->
                    val clickedGuest = realm.where(Guest::class.java).equalTo("id", guest.id).findFirst()
                    if (clickedGuest != null) {
                        clickedGuest.checkedIn = true
                        clickedGuest.checkInTime = System.currentTimeMillis()
                    }
                }
            }

            override fun checkoutButtonClicked(guest: Guest) {

            }
        })
        checkInRecyclerView.setHasFixedSize(true)
        val layoutManager = LinearLayoutManager(context)
        checkInRecyclerView.layoutManager = layoutManager
        checkInRecyclerView.adapter = adapter

    }

In the above code snippet, we obtained a list of Guest objects wrapped in a RealmResult object. RealmResult is a Realm implementation of the Collection interface that holds live Realm objects, it is typically used to hold query results from Realm Database. These Guest objects are not a representation of what is persisted on the disk, there is no format mapping, it is the same Kotlin object that was saved that is being returned. The rest of the code snippet in the above code snippet is self-explanatory except the line where we are attaching a change listener to the list. Why do we need to attach a change listener if our list holds live objects? The reason is that the RealmResult holding the objects is a Realm component but the adapter displaying the list is not.

In a looper thread, RealmResult will automatically update its query result if a Realm transaction is committed that affect the result. To reflect this change in the UI, Realm provides three types of Notifications that you can listen for and update your UI as needed without re-querying for the data again.

Realm Notification

This notification is a listener that you can attach to the Realm, a simple analogy would be to imagine this as a listener you attach to a table. In our example, the Guest Realm can be likened to a Guest table in a relational database. With that example, we can add a Realm notification to the Guest table which will notify us anytime there is a change to the table (or more correctly Realm). Here is an example of Realm notification.

 val realmListener = RealmChangeListener() {
            //do something here
        }

We can add this listener to the Realm or we can add it to a RealmResult object. In GuestListActivity, I used Realm notification to listen for changes in the Guest realm and update the labels for check-in and checkout totals like this.

 checkedInGuests.addChangeListener(RealmChangeListener<RealmResults>() {
            checkedInCount = it.size
            text_view_check_out_label.text = getString(R.string.check_in) + " - $checkedInCount"
        })

        checkedOutGuests.addChangeListener(RealmChangeListener<RealmResults>() {
            var checkedOutCount = it.size
            text_view_check_in_label.text = getString(R.string.check_out) + " - $checkedOutCount"
        })

Collections Notification

The other type of notification is Object notification which is used to surface changes to a specific object to any entity that has expressed interest in being notified of changes to that object. For example, we can listen for changes to Jane Doe and be notified if her last name changes to Jane Smith. The Realm notification that is applicable to our list of Guest objects, however, is the Collections notification which provides fine-grained notification about changes to a collection. These changes can be accessed via the OrderedCollectionChangeSet parameter that is passed to the change listener.  Here is an implementation of the Collections notification for the Guest’s list using OrderedRealmCollectionChangeListener

  private val changeListener = OrderedRealmCollectionChangeListener<RealmResults> { guests, changeSet ->
        // `null`  means the async query returns the first time.
        if (changeSet == null) {
            adapter!!.notifyDataSetChanged()
            return@OrderedRealmCollectionChangeListener
        }
        // For deletions, the adapter has to be notified in reverse order.
        val deletions = changeSet.deletionRanges
        for (i in deletions.indices.reversed()) {
            val range = deletions[i]
            adapter!!.notifyItemRangeRemoved(range.startIndex, range.length)
        }

        val insertions = changeSet.insertionRanges
        for (range in insertions) {
            adapter!!.notifyItemRangeInserted(range.startIndex, range.length)
        }

        val modifications = changeSet.changeRanges
        for (range in modifications) {
            adapter!!.notifyItemRangeChanged(range.startIndex, range.length)
        }
    }

And we can simply attach this listener to our guest list RealmResult like this checkedOutGuestList.addChangeListener(changeListener)  Once the Guest object is modified as shown in the code snippet above, that transaction once it is committed will cause the following two query results to change;

 val checkedOutGuestList = realm!!.where(Guest::class.java).equalTo("checkedIn", false).findAllAsync().sort("name")
 val checkedInGuestList = realm!!.where(Guest::class.java).equalTo("checkedIn", true).findAllAsync().sort("name")

Both query results are wrapped in a RealmResult which holds a live view of the objects matching that query. These objects are not copied from the database to RealmResult, so there is no need to re-fetch the objects. The RealmResult named checkedOutGuestList and checkInGuestList is essentially a live link to the same set of data. Once any change is committed, the corresponding notification objects attached to the results will notify the adapter to redraw itself accordingly.

Subscribe to my Android Development Newsletter and download source code

Summary

And that my friend, is a crash course on Realm database focusing on the concept of live, auto-updating object. Understanding this feature and properly taking advantage of it can simplify how you write code. When you hear that Realm has a reactive architecture, this feature is the underpinning of that claim. And remember at the top of the post, I mentioned that this feature also applies to Realms in the cloud and/or server, that capability is enabling development paradigms such as Object as API, why make an API call to update an object when if you update it locally that change propagates to the copy of that object on the server.

If you are struggling to implement Realm into your project, or you have unique use cases, I will be glad to provide you with an affordable quote. Use the contact form to request a quote.

Advertisements

About the Author valokafor

I am a family first Software Engineer, Entrepreneur, and Author. I am the founder and Principal Engineer of Okason Software, LLC a San Diego, CA-based mobile app development company.

follow me on:

Leave a Comment: