Home > Enterprise >  How to Override a suspend function in java class
How to Override a suspend function in java class

Time:05-03

Consider the following interface in kotlin:

LoginRepository.kt

interface LoginRepository {
    suspend fun login(): LoginResponse
}

LoginRepo.java

class LoginRepo implements LoginRepository {
    public Object login(@NonNull Continuation<? super LoginResponse> $completion) {
        api.login((result) -> {
            ContinuationUtilsKt.resumeContinuationWithSuccess($completion, result);
        });

        return null;
    }
}

ContinuationUtils.kt

fun <T> resumeContinuationWithSuccess(cont: Continuation<T>, value: T) {
    cont.resumeWith(Result.success(value))
}

I've attempted to drill down the code to its essential parts, i.e. a suspend function that is overridden in the java class makes an API call and returns a success or failure continuation using the continuation object and returns null.

However, the method LoginRepository.login when called returns null.

The overridden method signature in LoginRepo is generated by the IDE.

Am I supposed to return some object instead of null? Or something else that I'm missing.

CodePudding user response:

I really don't think you're supposed to do this. The functions and classes used to implement it in Kotlin are internal/private and hidden from the Java side.

Basically, you need to intercept the original Continuation and resume the new returned Continuation with your return value. Then return Intrinsics.COROUTINE_SUSPENDED to indicate that you are not synchronously returning a value. If the return value is anything besides Intrinsics.COROUTINE_SUSPENDED, then I think it assumes you are directly returning the declared return value of the suspend function.

While this code may work, it probably doesn't handle all the edge cases, and it probably won't provide helpful stack traces in the event of a crash. The standard library implementation is far more complicated.

class LoginRepo implements LoginRepository {
    public Object login(@NonNull Continuation<? super LoginResponse> $completion) {
        Continuation<? super LoginResponse> cont = IntrinsicsKt.intercepted($completion);

        api.login((result) -> {
            ContinuationUtilsKt.resumeContinuationWithSuccess(cont, result);
        });

        return IntrinsicsKt.getCOROUTINE_SUSPENDED();
    }
}

CodePudding user response:

Kotlin interoperability with Java doesn't really include suspend functions. Suspend functions are very specific to Kotlin, they are hard to both invoke and to implement from Java.

In most cases I suggest to not even try to handle continuations and suspendable code from Java and instead create small "adapters" in Kotlin. These adapters would translate suspendable code to something more usable from Java. For example, in Kotlin it is pretty easy to convert between suspend functions and CompletableFuture - in both directions.

Your case is more tricky, because you need to implement an interface. Still, there are ways to handle this from Kotlin. We can for example create abstract implementation of LoginRepository in Kotlin. It would provide login(), but you would implement all remaining methods in Java. We can do a similar thing using composition instead of inheritance by creating a non-abstract implementation of LoginRepository in Kotlin (throwing errors from all unrelated functions) and delegating to it from the Java class. Or we can create a static function that performs the conversion from callback-based API to suspend API. This solution is the most flexible, but we need to mess with some coroutines internals from Java:

@file:JvmName("SuspendUtils")

// utility function in Kotlin, it converts callback API to a suspend function
suspend fun login(api: Api): LoginResponse = suspendCoroutine { cont ->
    api.login { cont.resume(it) }
}
public static class LoginRepo implements LoginRepository {
    private Api api = new Api();

    @Nullable
    @Override
    public Object login(@NotNull Continuation<? super String> $completion) {
        return SuspendUtils.login(api, $completion);
    }
}
  • Related