How to auto-refresh Realm inside Android WorkManager
Tags: android, realm, workmanager, threading
Published: 2022-03-20 17:57:25

## The problem with Realm and background threads

[Realm](https://realm.io/)’s live objects only work when your Realm instance is operating from a `Looper` thread, namely your `Activity` and `Fragment` classes (the UI thread). However, typical use cases of Realm may require access from background threads too, and those instances won’t be auto refreshed, creating [memory leak and large file size problems](https://docs.mongodb.com/realm-legacy/docs/java/latest/index.html#faq-large-realm-file-size).

## How to use Realm in a WorkManager background thread

I have used the following technique to obtain auto-refreshing Realm instances within WorkManager workers. Using a combination of `Handler::post` + `suspendCoroutine` + `CoroutineWorker`, I am able to achieve synchronous access to a Realm instance and ensure my Realm objects insider a WorkManager worker remain as live objects:

### Full source code

```kotlin
import android.content.Context
import android.os.Handler
import android.os.HandlerThread
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import io.realm.Realm
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

abstract class RealmCoroutineWorker(
    name: String,
    context: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {

  private val realmThread = RealmHandlerThread(name)

  abstract fun doWork(realm: Realm): Result

  final override suspend fun doWork(): Result {
    return withContext(Dispatchers.IO) {
      try {
        realmThread.startAndWaitUntilReady()
        realmThread.executeWithRealm { realm -> doWork(realm) }
      } catch (err: Exception) {
        Result.failure()
      } finally {
        realmThread.quit()
      }
    }
  }
}

class RealmHandlerThread(name: String) : HandlerThread(name) {
  @Volatile private var handler: Handler? = null
  @Volatile private var realm: Realm? = null

  fun startAndWaitUntilReady() {
    start()
    // HandlerThread's getLooper() blocks until it has a value
    handler = Handler(looper)
    handler?.post { realm = Realm.getDefaultInstance() }
  }

  suspend fun <T> executeWithRealm(realmFun: (Realm) -> T): T {
    return suspendCoroutine { continuation ->
      handler!!.post {
        try {
          continuation.resume(realmFun(realm!!))
        } catch (err: Exception) {
          continuation.resumeWithException(err)
        }
      }
    }
  }

  private fun beforeQuit(onFinished: () -> Any): Boolean {
    if (looper == null) {
      return false
    }
    handler?.post {
      realm?.close()
      onFinished()
    }
    return true
  }

  override fun quit(): Boolean {
    return beforeQuit { super.quit() }
  }

  override fun quitSafely(): Boolean {
    return beforeQuit { super.quitSafely() }
  }
}
```

### Code explanation

1. Create a companion `HandlerThread` and attach a `Looper` and a `Handler` to it. Let’s call this class `RealmHandlerThread`. Create a Realm instance on this `Looper` thread.

```kotlin
class RealmHandlerThread(name: String) : HandlerThread(name) {
  @Volatile private var handler: Handler? = null
  @Volatile private var realm: Realm? = null

  fun startAndWaitUntilReady() {
    start()
    // HandlerThread's getLooper() blocks until it has a value
    handler = Handler(looper)
    handler?.post { realm = Realm.getDefaultInstance() }
  }

  private fun beforeQuit(onFinished: () -> Any): Boolean {
    if (looper == null) {
      return false
    }
    handler?.post {
      realm?.close()
      onFinished()
    }
    return true
  }

  override fun quit(): Boolean {
    return beforeQuit { super.quit() }
  }

  override fun quitSafely(): Boolean {
    return beforeQuit { super.quitSafely() }
  }
}
```

2. Use your `RealmHandlerThread`‘s `Handler::post` to perform all your Realm operations exclusively. Since your Realm instance lives on a `Looper` thread, all its objects become live and auto-refreshing objects.

`Handler::post` is asynchronous, so to be able to use your Realm synchronously inside your WorkManager Worker code, bridge the async and sync worlds with a [`suspendCoroutine`](https://kt.academy/article/cc-suspension).

```kotlin
class RealmHandlerThread(name: String) : HandlerThread(name) {
  ...

  suspend fun <T> executeWithRealm(realmFun: (Realm) -> T): T {
    return suspendCoroutine { continuation ->
      handler!!.post {
        try {
          continuation.resume(realmFun(realm!!))
        } catch (err: Exception) {
          continuation.resumeWithException(err)
        }
      }
    }
  }
  ...
}
```

3. Finally, hook your `RealmHandlerThread` to a [`CoroutineWorker`](https://developer.android.com/topic/libraries/architecture/workmanager/advanced/coroutineworker).

```kotlin
abstract class RealmCoroutineWorker(
    name: String,
    context: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {

  private val realmThread = RealmHandlerThread(name)

  abstract fun doWork(realm: Realm): Result

  final override suspend fun doWork(): Result {
    return withContext(Dispatchers.IO) {
      try {
        realmThread.startAndWaitUntilReady()
        realmThread.executeWithRealm { realm -> doWork(realm) }
      } catch (err: Exception) {
        Result.failure()
      } finally {
        realmThread.quit()
      }
    }
  }
}
```

4. Extend `RealmCoroutineWorker` to get a WorkManager Worker that enjoys synchronous access to an auto-refreshing Realm instance.