I have a suspend
function from which I want to return the result of a Java 5 Future
. The future object comes from another library Firebase Cloud Firestore- Admin SDK for Java and provides a blocking call get()
to retrieve the result of the said future.
My function looks like this-
suspend fun getPrefix(messageCreateEvent: MessageCreateEvent): String {
val snapshot = db.collection("prefixes")
.document(messageCreateEvent.guildId.get().asString())
.get() //This returns a future
.get() //Retrieves the future's result (Blocks thread; IDE gives warning)
//Return the prefix
return if (snapshot.exists())
snapshot.getString("prefix") ?: DEFAULT_PREFIX
else DEFAULT_PREFIX
}
The first thing that I considered was to look in kotlinx.coroutine
for extensions to bridge the futures. While the extensions exist, they do only for CompletionStatge
. So I decided to wrap the future into one ()-
val snapshot = CompleteableFuture.supplyAsync {
db.collection("prefixes")
.document(messageCreateEvent.guildId.get().asString())
.get() // This returns a future
.get() // Get the result
}.await()
I am quite inexperienced and not sure if this is was proper solution. I queried my question on a programming community, where a person recommended me to use a Deferred
-
val deferred = CompletableDeferred<DocumentSnapshot>()
val future = db.collection("prefixes")
.document(messageCreateEvent.guildId.get().asString())
.get()
future.addListener(
Runnable { deferred.complete(future.get()) },
ForkJoinPool.commonPool()
)
val snapshot = deferred.await()
I've give it quite a time to search for a way to bridge futures to co-routines, there isn't even a similar question on SO. Through, I wouldn't be surprised if this question gets a duplicate mark.
The key to this problem is the suspendCoroutine
function. The other non-obvious bit is that to add a callback to the ApiFuture
you use a static method on ApiFutures
.
Here's an extension function that implements await()
on an ApiFuture
.
/**
* Function to convert an ApiFuture into a coroutine return.
*/
suspend fun <F : Any?, R : Any?> ApiFuture<F>.await(
successHandler: (F) -> R,
): R {
return suspendCoroutine { cont ->
ApiFutures.addCallback(this, object : ApiFutureCallback<F> {
override fun onFailure(t: Throwable?) {
cont.resumeWithException(t ?: IOException("Unknown error"))
}
override fun onSuccess(result: F) {
cont.resume(successHandler(result))
}
}, Dispatchers.IO.asExecutor())
}
}
/**
* inline function to retrieve a document as a POJO from a DocumentReference
*/
suspend inline fun <reified T: Any>DocumentReference.toObject(): T? {
return get().await<DocumentSnapshot, T?> {
it.toObject(T::class.java)
}
}
Now you can write things like:
suspend fun getUser(id: String): User? {
return db.collection("users").document(id).toObject()
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With