DEV Community

Kai's brain dump
Kai's brain dump

Posted on • Edited on • Originally published at braindumpk.substack.com

2 1

How to auto-refresh Realm inside Android WorkManager

The problem with Realm and background threads

Realm’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.

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

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() }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.
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() }
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. 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.

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)
        }
      }
    }
  }
  ...
}
Enter fullscreen mode Exit fullscreen mode
  1. Finally, hook your RealmHandlerThread to a CoroutineWorker.
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()
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Extend RealmCoroutineWorker to get a WorkManager Worker that enjoys synchronous access to an auto-refreshing Realm instance.

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay