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.