Skip to main content

Realm 7, the frozen throne





onCreate

As many of us might know, Realm has already introduced freezing objects. Personally, I have been waiting for long time for such feature. So, what actual problem does this solve?

A lot of us might have faced this issue:

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

I believe the error speaks for itself.

Realm < 7.0:

When you call Realm.getDefaultInstance() you are not creating a new object every time you call this method. However, having it on every method is somehow ugly. In cases when you need the object in another thread, it is impossible to use it without a DTO helper data class.

Let's consider the following case:

private lateinit var realm: Realm
...
override var onCreate(){
 ...
 realm = Realm.getDefaultInstance() // Main thread
}
 
suspend fun doSomething() = withContext(Dispatchers.IO){
    val result = realm.where<MyRealmObject>().findFirst() //Crash
    result?.let {
        Timber.d("${it.id}")
  	}
 }

Every time we have to start a realm interaction, we have to call Realm.getDefaultInstance() before, every time you are in a new thread. Otherwise, it would bring the error mentioned above. If you want a sinlge, app-scope Realm instance, you have to make some workaround.

Same would have happened if you were to read an RealmObject or a RealmResults<T>:

private lateinit var realm: Realm
private var myObject: MyRealmObject? = null
...
override var onCreate(){
 ...
 realm = Realm.getDefaultInstance() // Main thread
 myObject = realm.where<MyRealmObject>().findFirst()
}

suspend fun doSomething() = withContext(Dispatchers.IO){
    result?.let {
        Timber.d("${it.id}") // Crash
    }
 }

Realm >= 7.0:

Since this would be a huge obstacle, especially to reactive programming, the Realm team is bringing a new actor in the game: The frozen Realm. Just a new object which can be used across threads, attached to the only instantiated Realm object.

private lateinit var liveRealm: Realm
private lateinit var frozenRealm: Realm
 
override var onCreate(){
...
  realm = Realm.getDefaultInstance() // Main thread
  frozenRealm = realm.freeze() 
}

suspend fun doSomething() = withContext(Dispatchers.IO){
  val result = frozenRealm.where<MyRealmObject>().findFirst()
  result?.let {
    Timber.d("${it.id}") // Works
  }
}

You can do the above solution, or you can directly freeze the RealmResults<T> or directly freeze the object (must be a RealmObject). This would be even easier:

private lateinit var realm: Realm
private var myObject: RealmResults? = null
 
override var onCreate(){
 ...
 realm = Realm.getDefaultInstance() // Main thread
 myObject = realm.where<MyRealmObject>().findAll().freeze()
 
}
 
suspend fun doSomething() = withContext(Dispatchers.IO){
    myObject?.let {
        Timber.d("${it.id}") // Works
    }
}

There are a few more additions to this new feature, but the docs are already perfect for that.

Are coroutines really necessary for this example?

Not at all, but coroutines give a nice presentation of thread switch with withContext block. Realm has its own thread usage:

val result = realm.where<MyRealmObject>().findAllAsync()

The findAllAsync() eliminate the necessity to have other kinds of thread pools. However, combining realm with Flow (particularly callbackFlow) would be one good choice if you want to go more reactive. Let us consider the following case: Select some data from the database and every time data changes, a new toast would appear.

The imperative way:

// In a ViewModel
fun getData(){
  val result = realm.where<MyRealmObject>().findAllAsync()
  result.addChangeListener{ result ->
      _toastLiveData.value = ShowToastFlag
  }
}

As an example is more than enough to eliminate usage of coroutines. But let's consider this following case: Fetch a list of objects locally, and make a POST request on the network after serializing it to JSON.

suspend fun sendListToNetwork() = withContext(Dispatchers.IO){
   val result = frozenRealm.where<MyRealmObject>().findAll()
   // We are in another thread because of the Network Request and not because of Realm
   result.addChangeListener{ mResult ->
   val jsonStringResultObject = serialize(result.toList())
   val response = request().postRequest(jsonStringResult).execute()
   when(response){
      is Success -> doSomething()
      else -> doSomethingElse()
   }
}

If the case would have been longer, we would have had a more difficult code to read.

Let's try to make this more Reactive

Let's make a generic method for all Realm objects that are going to be selected as a list:

inline fun  observeData(): Flow> = callbackFlow{
   val result = frozenRealm.where<T>().findAllAsync()
   result.addChangeListener{ mResult ->
    offer(result.toList())
   }
   awaitClose {result.removeAllChangeListeners()}
}

It's time to observe the magic of this new feature with combination to coroutines:

// inside the ViewModel
suspend fun sendDataToNetwork(){
  observeData().flowOn(Dispatchers.IO)
  .map {
    return@map serialize(it)
  }.flowOn(Dispatchers.Default) // same Realm object, different threads
  .map {
      return@map postRequest(it).execute()
  }.flowOn(Dispatchers.IO)
  .collect { networkResult ->
      handleResult(networkResult)
  }
}

And that's it.

onDestroy

Without the Realm new feature, this would have been impossible. Instead, we would have wasted DTOs in order to hold reference to each selection that we could get. Allowing to share object across thread is a very convenient and necessary feature to have. Saving time and code is not the only factor that is being touched. This new feature opened a new way to reactive programming also.

Stavro Xhardha

Popular posts from this blog

What I learned from Kotlin Flow API

I used to check the docs and just read a lot about flows but didn't implement anything until yesterday. However, the API tasted really cool (even though some operations are still in Experimental state).Prerequisites: If you don't know RxJava it's fine. But a RxJava recognizer would read this faster.Cold vs Hot streamsWell, I really struggled with this concept because it is a little bit tricky. The main difference between cold and hot happened to be pretty simple: Hot streams produce when you don't care while in cold streams, if you don't collect() (or RxJava-s equivalent subscribe()) the stream won't be activated at all. So, Flows are what we call cold streams. Removing the subscriber will not produce data at all, making the Flows one of the most sophisticated asynchronous stream API ever (in the JVM world). I tried to make a illustration of hot and cold streams: Since I mentioned the word asynchronous this implies that they do support coroutines also. Flows vs…

Modularizing your Android app, breaking the monolith (Part 1)

Inspired by a Martin Fowlers post about Micro Frontends, I decided to break my monolithic app into a modular app. I tried to read a little more about breaking monolithic apps in Android, and as far as I got, I felt confident to share my experience with you. This will be some series of blog posts where we actually try to break a simple app into a modularized Android app.

Note: You should know that I am no expert in this, so if there are false statements or mistakes please feel free to criticize, for the sake of a better development. 

What do you benefit from this approach:
Well, people are moving pretty fast nowadays and delivery is required faster and faster. So, in order to achieve this, modularising Android apps is really necessary.You can share features across different apps. Independent teams and less problems per each.Conditional features update.Quicker debugging and fixing.A feature delay doesn't delay the whole app. As per writing tests, there is not too much difference about…

From Gson to Moshi, what I learned

There is no doubt that people are getting away from GSON and I agree with those reasons too. The only advantage GSON has over other parsing libraries is that it takes a really short amount of time to set up. Furthermore, the most important thing is that Moshi is embracing Kotlin support.

First let's implement the dependency:
implementation("com.squareup.moshi:moshi:1.8.0") It's not a struggle to migrate to Moshi. It's really Gson look-a-like. The only thing to do is annotate the object with @field:Json instead of @SerializedName (which is Gsons way for JS representation):

data class User( //GSON way @SerializedName("name") val name: String, @SerializedName("user_name") val userName: String, @SerializedName("last_name") val lastName: String, @SerializedName("email") val email: String ) data class User( //Moshi way @field:Json(name = "name") val name: String, @field:Json(name = "user_name…