Home > Back-end >  How do I call a Java method that takes a Non-null Void parameter from Kotlin?
How do I call a Java method that takes a Non-null Void parameter from Kotlin?

Time:12-14

Primary Question

Is it possible to call a Java method with a signature like this (from a third-party library, so the method signature cannot be changed) from Kotlin? If so, how?

class Uncallable {
    void myMethod(@NonNull Void a) {
        System.out.println("Ran myMethod");
    }
}

I can call this from another Java class with

Uncallable u = new Uncallable();
u.myMethod(null); // IDE warns about nullability but still runs fine

but from Kotlin, none of these options work

val u = Uncallable()
u.myMethod() // No value passed for parameter 'a'
u.myMethod(null) // Null can not be a value of a non-null type Void
u.myMethod(Unit as Void) // ClassCastException: class kotlin.Unit cannot be cast to class java.lang.Void
u.myMethod(Void)
u.myMethod(Unit)
u.myMethod(Nothing)

Similar questions (like this one) suggest making a Java interface layer to let me get around the nullability rules. That works here, but I'd like to know if there's any way to call this without such an extra layer. I don't actually need to pass a null value, just to call the method.

This question addresses a similar issue, but there they have control of the interface and can just use Void? as the type.

Why do I want to do this?

I don't really... However, I have some unit tests that intercept the addOnSuccessListener callback added to some Firebase methods and call onSuccess on the provided listener. The signature of that callback uses Void for its parameter type, and previously I could call listener.onSuccess(null)

doAnswer { invocation ->
    val args = invocation.arguments
    @Suppress("UNCHECKED_CAST")
    val l = args[0] as OnSuccessListener<Void>
    l.onSuccess(null)
    mMockTask
}.`when`(mMockTask).addOnSuccessListener(ArgumentMatchers.any())

After a recent update of Firebase libraries, it seems that either a @NonNull annotation was added to the parameter for onSuccess, or Kotlin changed to start enforcing it

public interface OnSuccessListener<TResult> {
    void onSuccess(@NonNull TResult var1);
}

As a result, I can no longer call listener.onSuccess(null) - I get a build error about null being passed for a non-null parameter.

I am able to make a VoidCaller Java interface to get around this,

public class VoidCaller {
    static void callSuccessCallback(OnSuccessListener<Void> callback) {
        callback.onSuccess(null);
    }

    static void callFailureCallback(OnFailureListener callback) {
        callback.onFailure(null);
    }
}

which works, but only if I change my callbacks from

fileRef.delete().addOnSuccessListener { onSuccess() }

to

fileRef.delete().addOnSuccessListener { _ : Void? -> onSuccess() }

or I get an error attempting to set it of a non-null type to null.

Parameter specified as non-null is null: method com.app.firebaseHelper.deleteImage$lambda-11$lambda-10, parameter it

Ideally I would like to find a way to call such a method directly from Kotlin.

CodePudding user response:

if you only want to pass the Void object, but its constructor is private, you can use reflect.

Constructor<Void> c = Void.class.getDeclaredConstructor();
c.setAccessible(true);
Void v = c.newInstance();

then you can pass it.

The other way you can override the class or adapter it which methods use that param. But I feel using the Void params is weird anyway.

  • Related