Skip to content

Instantly share code, notes, and snippets.

@zarini
Last active April 6, 2024 18:13
Show Gist options
  • Save zarini/c70401f48a953b09e64912e0d6b3dfcb to your computer and use it in GitHub Desktop.
Save zarini/c70401f48a953b09e64912e0d6b3dfcb to your computer and use it in GitHub Desktop.
Now you can call every kotlin suspend function in java code if you want to block until function call finished
@file:kotlin.jvm.JvmName("SuspendRunner")
import kotlinx.coroutines.*
import java.util.concurrent.FutureTask
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import kotlin.coroutines.CoroutineContext
/**
* This class design for call kotlin suspend function in Java code.
*
* This code run on IO Dispatcher, So do not update UI in suspend function body.
*
*
* @author Amir Khodadadzarini
*/
private class SuspendFuture : CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.IO + Job() + CoroutineExceptionHandler { _, throwable ->
result.cancel(throwable)
}
private val result = SuspendFutureResult()
fun run(
timeoutInMillis: Long? = null,
block: suspend CoroutineScope.() -> Any
): Any {
launch { result.emit(block()) }
return result.future(timeoutInMillis)
}
}
private class SuspendFutureResult {
private lateinit var data: Any
private var cancelCause: Throwable? = null
private val future = FutureTask { data }
fun emit(data: Any) {
this.data = data
future.run()
}
fun future(timeoutInMillis: Long?): Any {
return try {
if (timeoutInMillis != null) {
future.get(timeoutInMillis, TimeUnit.MILLISECONDS)
} else {
future.get()
}
} catch (ex: CancellationException) {
throw RuntimeException(cancelCause)
}
}
fun cancel(cause: Throwable) {
this.cancelCause = cause
future.cancel(true)
}
}
/**
* This function run suspend function in Java code,
* Suspend function run on IO Dispatcher so do not update UI in suspend function.
* Suspend functions that call in [runSuspend] function can return unit, so you can ignore return value
* of the function call.
*
* Warning: This function blocks until suspend function result received.
*
* @sample Object result = SuspendRunner.runSuspend(
* (coroutineScope, continuation) -> (Object) RunnerFunctionTestKt.getResult(continuation) // suspend function
* );
*
* @author Amir Khodadadzarini
*/
fun runSuspend(
block: suspend CoroutineScope.() -> Any
): Any {
return SuspendFuture().run(null, block)
}
/**
* This function run suspend function in Java code,
* Suspend function run on IO Dispatcher so do not update UI in suspend function.
* Suspend functions that call in [runSuspend] function can return unit, so you can ignore return value
* of the function call.
*
* Warning: This function blocks until suspend function result received Or reach timeout.
*
* @sample Object result = SuspendRunner.runSuspend(
* 1000, // delay
* (coroutineScope, continuation) -> (Object) RunnerFunctionTestKt.getResult(continuation) // suspend function
* );
*
* @return null if reach timeout before suspend function result received.
* @throws TimeoutException Exception thrown when a blocking operation times out.
* @author Amir Khodadadzarini
*/
@Throws(TimeoutException::class)
fun runSuspend(
timeoutInMillis: Long,
block: suspend CoroutineScope.() -> Any
): Any {
val suspendFuture = SuspendFuture()
return try {
suspendFuture.run(timeoutInMillis, block)
} catch (ex: Exception) {
try {
suspendFuture.coroutineContext.cancelChildren()
} catch (cancelException: Exception) {
cancelException.printStackTrace()
}
throw ex
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment