Migrating Android Apps from Realm Java SDK to Kotlin SDK
Rate this article
So, it's here! The engineering team has released a major milestone of the Kotlin
SDK. The preview is available to
you to try it and make comments and suggestions.
Until now, if you were using Realm in Android, you were using the Java version of the SDK. The purpose of the Realm
Kotlin SDK is to be the evolution of the Java one and eventually replace it. So, you might be wondering if and when you
should migrate to it. But even more important for your team and your app is what it provides that the Java SDK
doesn't. The Kotlin SDK has been written from scratch to combine what the engineering team has learned through years of
SDK development, with the expressivity and fluency of the Kotlin language. They have been successful at that and the
resulting SDK provides a first-class experience that I would summarize in the following points:
- The Kotlin SDK allows you to use expressions that are Kotlin idiomatic—i.e., more natural to the language.
- It uses Kotlin coroutines and flows to make concurrency easier and more efficient.
- It has been designed and developed with Kotlin Multiplatform in mind.
- It has removed the thread-confinement restriction on Java and it directly integrates with the Android lifecycle hooks so the developer doesn't have to spin up and tear down a realm instance on every activity lifecycle.
- It's the way forward. MongoDB is not discontinuing the Java SDK anytime soon, but Kotlin provides the engineering team more resources to implement cooler things going forward. A few of them have been implemented already. Why wouldn't you want to benefit from them?
Are you on board? I hope you are, because through the rest of this article, I'm going to tell you how to upgrade your
projects to use the Realm Kotlin SDK and take advantage of those benefits that I have just mentioned and some more. You
can also find a complete code example in this repo.
Build better mobile apps with Atlas Device Sync: Atlas Device Sync is a fully-managed mobile backend-as-a-service. Leverage out-of-the-box infrastructure, data synchronization capabilities, built-in network handling, and much more to quickly launch enterprise-grade mobile apps. Get started now by build: Deploy Sample for Free!
First things first. You need to make some changes to your
build.gradle
files to get access to the Realm Kotlin SDK
within your project, instead of the Realm Java SDK that you were using. The Realm Kotlin SDK uses a gradle plugin that
has been published in the Gradle Plugin Portal, so the prefered way
to add it to your project is using the plugins section of the build configuration of the module —i.e.,
app/build.gradle
— instead of the legacy method of declaring the dependency in the buildscript
block of the
project build.gradle
.After replacing the plugin in the module configuration with the Kotlin SDK one, you need to add an implementation
dependency to your module. If you want to use Sync with your MongoDB
cluster, then you should use
'io.realm.kotlin:library-sync'
, but if you just want to have local persistence, then
'io.realm.kotlin:library-base'
should be enough. Also, it's no longer needed to have a realm
dsl section in the
android
block to enable sync.If you have more than one module in your project and want to pin the version number of the plugin for all of them, you
can define the plugin in the project
build.gradle
with the desired version and the attribute apply false
. Then, the
build.gradle
files of the modules should use the same plugin id, but without the version attribute.Kotlin scope functions (i.e.,
apply
, run
, with
, let
, and also
) make object creation and manipulation easier.
That was already available when using the Java SDK from kotlin, because they are provided by the Kotlin language itself.Defining a model class is even easier with the Realm Kotlin SDK. You are not required to make the model class
open
anymore. The Java SDK was using the Kotlin Annotation Processing Tool to derive proxy classes that took care of
interacting with the persistence. Instead, the Kotlin SDK uses the RealmObject
interface as a marker for the plugin.
In the construction process, the plugin identifies the objects that are implementing the marker interface and injects the
required functionality to interact with the persistence. So, that's another change that you have to put in place:
instead of making your model classes extend —i.e., inherit— from RealmObject
, you just have to implement the interface
with the same name. In practical terms, this means using RealmObject
in the class declaration without parentheses.There are also changes in terms of the type system.
RealmList
that was used in the Java SDK to model one-to-many
relationships is extended in the Kotlin SDK to benefit from the typesystem and allow expressing nullables in those
relationships. So, now you can go beyond RealmList<String>
and use RealmList<String?>
. You will get all the
benefits of the syntax sugar to mean that the strings the object is related to, might be null. You can check this and
the rest of the supported types in the documentation of the Realm Kotlin SDK.Using Realm is even easier now. The explicit initialization of the library that was required by the Realm Java SDK is
not needed for the Realm Kotlin SDK. You did that invoking
Realm.init()
explicitly. And in order to ensure that you
did that once and at the beginning of the execution of your app, you normally put that line of code in the onCreate()
method of the Application
subclass. You can forget about that chore for good.The configuration of the Realm in the Kotlin SDK requires passing the list of object model classes that conform the
schema, so the
builder()
static method has that as the argument. The Realm Kotlin SDK also allows setting the logging
level per configuration, should you use more than one. The rest of the configuration options remain the same.It's also different the way you get an instance of a Realm when you have defined the configuration that you want to
use. With the Java SDK, you had to get access to a thread singleton using one of the static methods
Realm.getInstance()
or Realm.getDefaultInstance()
(the latter when a default configuration was being set and used).
In most cases, that instance was used and released, by invoking its close()
method, at the end of the
Activity/Fragment lifecycle. The Kotlin SDK allows you to use the static method open()
to get a single instance of a
Realm per configuration. Then you can inject it and use it everywhere you need it. This change takes the burden of
Realm lifecycle management off from the shoulders of the developer. That is huge! Lifecycle management is often
painful and sometimes difficult to get right.Objects in the Realm Kotlin SDK are now frozen to directly integrate seamlessly into Kotlin coroutine and flows. That
means that they are not live as they used to be in the Realm Java SDK and don't update themselves when they get changed
in some other part of the application or even in the cloud. Instead, you have to modify them within a write
transaction, i.e., within a
write
or writeBlocking
block. When the scope of the block ends, the objects are frozen
again.Even better, the realms aren't confined to a thread. No more thread singletons. Instead, realms are thread-safe, so
they can safely be shared between threads. That means that you don't need to be opening and closing realms for the
purpose of using them within a thread. Get your Realm and use it everywhere in your app. Say goodbye to all those
lifecycle management operations for the realms!
Finally, if you are injecting dependencies of your application, with the Realm Kotlin SDK, you can have a singleton for
the Realm and let the dependency injection framework do its magic and inject it in every view-model. That's much easier
and more efficient than having to create one each time —using a factory, for example— and ensuring that the
close method was called wherever it was injected.
It took a while, but Kotlin brought coroutines to Android and we have learned to use them and enjoy how much easier
they make doing asynchronous things. Now, it seems that coroutines are the way to do those things and we would like
to use them to deal with operations that might affect the performance of our apps, such as dealing with the persistence
of our data.
Support for coroutines and flows is built-in in the Realm Kotlin SDK as a first-class citizen of the API. You no longer
need to insert write operations in suspending functions to benefit from coroutines. The
write {}
method of a realm is
a suspending method itself and can only be invoked from within a coroutine context. No worries here, since the compiler
will complain if you try to do it outside of a context. But with no extra effort on your side, you will be performing
all those expensive IO operations asynchronously. Ain't that nice?You can still use the
writeBlocking {}
of a realm, if you need to perform a synchronous operation. But, beware that,
as the name states, the operation will block the current thread. Android might not be very forgiving if you block the
main thread for a few seconds, and it'll present the user with the undesirable "Application Not Responding" dialog. Please,
be mindful and use this only when you know it is safe.Another additional advantage of the Realm Kotlin SDK is that, thanks to having the objects frozen in the realm, we can
make asynchronous transactions easier. In the Java SDK, we had to find again the object we wanted to modify inside of
the transaction block, so it was obtained from the realm that we were using on that thread. The Kotlin SDK makes that
much simpler by using
findLatest()
with that object to get its instance in the mutable realm and then apply the
changes to it.One thing where Realm shines is when you have to retrieve information from it. Data is obtained concatenating three
operations:
- Creating a RealmQuery for the object class that you are interested in.
- Optionally adding constraints to that query, like expected values or acceptable ranges for some attributes.
- Executing the query to get the results from the realm. Those results can be actual objects from the realm, or aggregations of them, like the number of matches in the realm that you get when you use
count()
.
The Realm Kotlin SDK offers you a new query system where each of those steps has been simplified.
The queries in the Realm Java SDK used filters on the collections returned by the
where
method. The Kotlin SDK offers
the query
method instead. This method takes a type parameter using generics, instead of the explicit type parameter
taken as an argument of where
method. That is easier to read and to write.The constraints that allow you to narrow down the query to the results you care about are implemented using a predicate
as the optional argument of the
query()
method. That predicate can have multiple constraints concatenated with
logical operators like AND
or OR
and even subqueries that are a mayor superpower that will boost your ability to
query the data.Finally, you will execute the query to get the data. In most cases, you will want that to happen in the background so
you are not blocking the main thread. If you also want to be aware of changes on the results of the query, not just the
initial results, it's better to get a flow. That required two steps in the Java SDK. First, you had to use
findAllAsync()
on the query, to get it to work in the background, and then convert the results into a flow with the
toFlow()
method. The new system simplifies things greatly, providing you with the asFlow()
method that is a
suspending function of the query. There is no other step. Coroutines and flows are built-in from the beginning in the
new query system.As it was the case when writing to the Realm, you can also use blocking operations when you need them, invoking
find()
on the query. And also in this case, use it only when you know it is safe.You're probably not reading this, because if I were you, I would be creating a branch in my project and trying the
Realm Kotlin SDK already and benefiting from all these wonderful changes. But just in case you are, let me summarize the
most relevant changes that the Realm Kotlin SDK provides you with:
- The configuration of your project to use the Realm Kotlin SDK is easier, uses more up-to-date mechanisms, and is more explicit.
- Model classes are simpler to define and more idiomatic.
- Working with the realm is much simpler because it requires less ceremonial steps that you have to worry about and plays better with coroutines.
- Working with the objects is easier even when doing things asynchronously, because they're frozen, and that helps you to do things safely.
- Querying is enhanced with simpler syntax, predicates, and suspending functions and even flows.
Time to code!