108

Assume we have the following suspend function:

suspend fun doSomething(): List<MyClass> { ... } 

If I want to call this function in one of my existing Java classes (which I'm not able to convert to Kotlin for now) and get its return value I have to provide a Continuation<? super List<MyClass>> as its parameter (Obviously).

My question is, How can I implement one. Specially its getContext getter.

2
  • 1
    I would do anything possible to avoid having to do this; I wouldn't expect it to work very effectively. For example, you could add another Kotlin function to launch the coroutine however you think is appropriate, that isn't a suspend fun, and call that from Java. Commented Oct 18, 2018 at 16:48
  • 1
    I used to have Java code that managed to create an implementation of Continuation and call a suspend fun, but in Kotlin 1.3 Continuation declares resumeWith(Result), where Result is a discriminated union of the result and an internal class Failure and there's just no way to supply that from Java, save for using reflection to access private members in Kotlin implementation. Commented Oct 19, 2018 at 13:34

4 Answers 4

107

First, add org.jetbrains.kotlinx:kotlinx-coroutines-jdk8 module to your dependencies. In your Kotlin file define the following async function that corresponds to Java style of writing async APIs:

fun doSomethingAsync(): CompletableFuture<List<MyClass>> = GlobalScope.future { doSomething() } 

Now use doSomethingAsync from Java in the same way as you are using other asynchronous APIs in the Java world.

Sign up to request clarification or add additional context in comments.

17 Comments

Not work on Android 6.0. Error: java.lang.NoClassDefFoundError: Failed resolution of: Ljava/util/concurrent/CompletableFuture;
You represent it as any other future type and you are using in your Java code. We have built-in support for conversion into CompletableFuture, ListenableFuture, Rx Single, Reactor Mono. You can write your own, too.
Note: this requires API level 24 or above.
"Now use doSomethingAsync from Java in the same way as you are using other asynchronous APIs in the Java world." How???
using Dispatchers.Main is typically what would be wanted when calling from java: fun doSomethingAsync(): CompletableFuture<List<MyClass>> = GlobalScope.future(Dispatchers.Main) { doSomething() }
|
18

If you dont want to use org.jetbrains.kotlinx:kotlinx-coroutines-jdk8, I have a new idea.

Write below code in your kotlin project.

@JvmOverloads fun <R> getContinuation(onFinished: BiConsumer<R?, Throwable?>, dispatcher: CoroutineDispatcher = Dispatchers.Default): Continuation<R> { return object : Continuation<R> { override val context: CoroutineContext get() = dispatcher override fun resumeWith(result: Result<R>) { onFinished.accept(result.getOrNull(), result.exceptionOrNull()) } } } 

I wrote it in my Coroutines class

Then you can call your suspend function like:

Coroutines coroutines = new Coroutines(); // My coroutines utils class UserUtils.INSTANCE.login("user", "pass", coroutines.getContinuation( (tokenResult, throwable) -> { System.out.println("Coroutines finished"); System.out.println("Result: " + tokenResult); System.out.println("Exception: " + throwable); } )); 

login() function is a suspend function.
suspend fun login(username: String, password: String): TokenResult

For your code, you can:

doSomething(getContinuation((result, throwable) -> { //TODO })); 

Besides, you may want to run your callback code in a different thread (e.g. Main thread), just use launch(Dispathers.Main) to wrap resumeWith()

... override fun resumeWith(result: Result<R>) { launch(Dispathers.Main) { onFinished.accept(result.getOrNull(), result.exceptionOrNull()) } } ... 

Update: My friend has developed a plugin kotlin-jvm-blocking-bridge that can automatically generate blocking bridges for calling suspend functions from Java with minimal effort, also give it a try.

3 Comments

awesome!!! this is a workaround to call coroutine inside java for interopability
Just like Roman's answer, this also requires API 24 or higher
@Samuel Maybe you can replace BiConsumer and Lambda by your own interface and anonymous class to make it compatible with old Android SDK (not tested)
15

For coroutines 1.3.0 use this:

BuildersKt.launch(GlobalScope.INSTANCE, Dispatchers.getMain(),//context to be ran on CoroutineStart.DEFAULT, (coroutineScope, continuation) -> suspendFunction(arguments) ); 

For java < 8:

BuildersKt.launch( GlobalScope.INSTANCE, Dispatchers.getMain(),//context to be ran on CoroutineStart.DEFAULT, new Function2<CoroutineScope, Continuation<? super Unit>, Unit/*or your return type here*/>() { @Override public Unit/*or your return type here*/ invoke(CoroutineScope coroutineScope, Continuation<? super Unit> continuation) { //do what you want return Unit.INSTANCE; //or something with the defined type } } ); 

My gradle file:

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0" 

Kotlin uses static classes for extension functions, launch is an extension function, so it is defined in BuildersKt. The first parameter is the target of the extension function, the rest are the parameters from the extension functions.

7 Comments

This approach is useless because you can't resume the continuation, it takes an inline class that you can't access from Java.
I don't understand what you mean. If you want to use Deferred you can use BuildersKt.async instead of launch, this is how coroutines work.
// do what you want -- you can't do what you want here because you must supply a continuation that expects to be resumed with the return type of the called function, but you don't have such a continuation and your answer doesn't show how to construct it from Java. The continuation you get from launch is just the completion continuation, the one you invoke with Unit when the whole coroutine is done.
@MarkoTopolnik you can try to pass the continuation on the lambda there as the last parameter of your suspendFunction(). i.e., (coroutineScope, continuation) -> suspendFunction(arguments, continuation)
@Dragos Rachieru I like this solution however I'm not sure how to use this approach in methods with a return type. I.e. public MyObject getMyObject() - MyObject Id like to return depends on the data that comes from suspend function. Unfortunately I can't return from inside the BuildersKt.launch{}. Can you help me solve it?
|
4

I created interface class based on @Kenvix answer to make it compatible with old Android SDK (lower than API 24)

interface CoroutineCallback<RESULT> { companion object { @JvmOverloads fun <R> call( callback: CoroutineCallback<R>, dispatcher: CoroutineDispatcher = Dispatchers.Default ): Continuation<R> { return object : Continuation<R> { override val context: CoroutineContext get() = dispatcher override fun resumeWith(result: Result<R>) { callback.onComplete(result.getOrNull(), result.exceptionOrNull()) } } } } fun onComplete(result: RESULT?, error: Throwable?) } 

usage

class kotlinClass { suspend doSomething(foo, bar) : FooBar {} } class javaClass { void doSomething(){ kotlinClassObject.doSomething("foo", "bar", CoroutineCallback.Companion.call((fooBar, error) -> { //do something with result or error })); } } 

now call suspend function from any java class by passing CoroutineCallback

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.