Skip to content

Instantly share code, notes, and snippets.

@michaelbukachi
Last active May 24, 2022 04:31
Show Gist options
  • Save michaelbukachi/0c7f18bdeaf4bdb12b5527304d6cdbc0 to your computer and use it in GitHub Desktop.
Save michaelbukachi/0c7f18bdeaf4bdb12b5527304d6cdbc0 to your computer and use it in GitHub Desktop.
Realm Coroutines
import io.realm.*
import io.realm.kotlin.where
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
private suspend fun <T: RealmObject, S: RealmQuery<T>> findAllAwait(query: S): RealmResults<T> = suspendCancellableCoroutine { continuation ->
val listener = RealmChangeListener<RealmResults<T>> { t -> continuation.resume(t) }
query.findAllAsync().addChangeListener(listener)
}
private suspend fun <T: RealmObject, S: RealmQuery<T>> findFirstAwait(query: S): T? = suspendCancellableCoroutine { continuation ->
val listener = RealmChangeListener { t: T? -> continuation.resume(t) }
query.findFirstAsync().addChangeListener(listener)
}
private suspend fun executeAsync(realm: Realm, block: (Realm) -> Unit): Unit = suspendCancellableCoroutine { continuation ->
realm.executeTransactionAsync({ block(it) }, { continuation.resume(Unit) }, { continuation.resumeWithException(it) })
}
suspend fun <S: RealmObject> RealmQuery<S>.await() = findAllAwait(this)
suspend fun <S: RealmObject> RealmQuery<S>.awaitFirst() = findFirstAwait(this)
suspend fun Realm.transactAwait(block: (Realm) -> Unit) = executeAsync(this, block)
class TestObject(val name: String? = "") : RealmObject()
fun test() {
GlobalScope.launch {
val realm = Realm.getDefaultInstance()
val result = realm.where<TestObject>().awaitFirst()
val results = realm.where<TestObject>().await()
realm.transactAwait(Realm.Transaction {
val testObject = TestObject(name = "Some Test")
it.copyToRealm(testObject)
})
}
}
@michaelbukachi
Copy link
Author

@kibotu just use the usual try-catch statement to catch exceptions
e.g

try {
    val results = realm.where<TestObject>().await()
} catch(e: Exception) {
    // handle exception
}

Since the scope the of the listener is local, It will be destroyed and garbage collected as soon as the results are disposed.

@rayel-datu
Copy link

When I run this code, I encounter this

java.lang.IllegalStateException: `copyOrUpdate` can only be called inside a write transaction.

@michaelbukachi
Copy link
Author

@rayel0915 I've corrected the snippet.

@kibotu
Copy link

kibotu commented Feb 14, 2020

even though it works perfectly fine, i found that copyToRealm is super slow, it takes up to 1500ms to copy 400 realm objects each time :/

also i feel like later on in a medium sized project realm is really slow mainly because of the need to copy entire realm objects

realm 7+ supports freeze() which deals a bit better with switching threads, do you think this solution can be applied?

i've played a round with it a bit but freezing still requires lazy loading of properties later on which is really slow if you have large realm objects, too :/

@michaelbukachi
Copy link
Author

@kibotu First time hearing about freeze(). I haven't visited their site in a while. Let me experiment with it and see what can be done with it.

@kibotu
Copy link

kibotu commented Mar 3, 2020

wouldn't it be better to also remove the listener when there is an exception? i'm not super strong with all coroutine conventions / features yet,
is there reason why it is not necessary?

another thing is that i believe it would be better to use inline functionality here as well

suspend inline fun <reified T : RealmObject, reified S : RealmQuery<T>> findAllAwait(query: S): RealmResults<T> = suspendCancellableCoroutine { continuation ->
    val listener = RealmChangeListener<RealmResults<T>> { t -> continuation.resume(t) }
    val results = query.findAllAsync()
    results.addChangeListener(listener)
    continuation.invokeOnCancellation {
        results.removeChangeListener(listener)
    }
}

suspend inline fun <reified T : RealmObject, reified S : RealmQuery<T>> findFirstAwait(query: S): T? = suspendCancellableCoroutine { continuation ->
    val listener = RealmChangeListener { t: T? -> continuation.resume(t) }
    val results = query.findFirstAsync()
    results.addChangeListener(listener)
    continuation.invokeOnCancellation {
        results.removeChangeListener(listener)
    }
}

suspend fun Realm.transactAwait(block: (Realm) -> Unit): Unit = suspendCancellableCoroutine { continuation ->
    executeTransactionAsync({ block(it) }, { continuation.resume(Unit) }, { continuation.resumeWithException(it) })
}

suspend inline fun <reified S : RealmObject> RealmQuery<S>.await() = findAllAwait(this)

suspend inline fun <reified S : RealmObject> RealmQuery<S>.awaitFirst() = findFirstAwait(this)

@michaelbukachi
Copy link
Author

Hi @kibotu. Thanks for pointing that out. It's important do cleanup after cancellation. Also, I created a library for this at https://github.com/michaelbukachi/realm-koroutines. Would nice if you could raise an issue over there.

@kibotu
Copy link

kibotu commented Mar 3, 2020

cool thanks for sharing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment