Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java to Kotlin converter and nullable method arguments

I had a case in which Java to Kotlin converter failed me miserably by not marking method arguments as nullable.

Example: tracking activity lifecycle using registerActivityLifecycleCallbacks:

registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}

    @Override
    public void onActivityStarted(Activity activity) {}

    @Override
    public void onActivityResumed(Activity activity) {}

    // ... other overriden methods
});

Pasting this code to Kotlin results:

registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle) {}

    override fun onActivityStarted(activity: Activity) {}

    override fun onActivityResumed(activity: Activity) {}

    override fun onActivityPaused(activity: Activity) {}

    // ... other overriden methods (all with non-nullable parameters)
})

The issue is that savedInstanceState argument type is Bundle where it should be Bundle? because it's value can be null.

In this case we will get the following exception when an Activity is created without instance state:

java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter savedInstanceState

Note, the root cause to this can be that onActivityCreated documentation doesn't mention that Bundle can be null whereas onCreate documentation does which can explain why simple onCreate conversion works as expected:

// Java
@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
}
// Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
}

My question is how do we know which arguments should be nullable to prevent those kind of issues? The @Nullable annotation doesn't help here.

like image 692
diman Avatar asked Oct 09 '17 23:10

diman


People also ask

How do you handle nullable in Kotlin?

Use the ?: Elvis operator safe-call operator returns null . It's similar to an if/else expression, but in a more idiomatic way. If the variable isn't null , the expression before the ?: Elvis operator executes. If the variable is null , the expression after the ?: Elvis operator executes.

Can Java and Kotlin work together?

Android Studio provides full support for Kotlin, enabling you to add Kotlin files to your existing project and convert Java language code to Kotlin. You can then use all of Android Studio's existing tools with your Kotlin code, including autocomplete, lint checking, refactoring, debugging, and more.

Is Kotlin fully interoperable with Java?

Kotlin is designed with Java interoperability in mind. Existing Java code can be called from Kotlin in a natural way, and Kotlin code can be used from Java rather smoothly as well.

What happens if you add null null in Kotlin?

Nullable and Non-Nullable Types in Kotlin – If we try to assign null to the variable, it gives compiler error.


1 Answers

If annotations don't help, I don't think there's a way to know if an argument is nullable or not.

Regarding your posted code, I think I know what's going on:

Activity onCreate IS marked as @Nullable:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    final AppCompatDelegate delegate = getDelegate();
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    .
    .

While ActivityLifecycleCallbacks interface methods are not: (See Application.java)

public interface ActivityLifecycleCallbacks {
    void onActivityCreated(Activity activity, Bundle savedInstanceState);
    void onActivityStarted(Activity activity);
    void onActivityResumed(Activity activity);
    void onActivityPaused(Activity activity);
    void onActivityStopped(Activity activity);
    void onActivitySaveInstanceState(Activity activity, Bundle outState);
    void onActivityDestroyed(Activity activity);
}

So apparently Kotlin translator is processing annotations, but using Non-Null as default, which in my humble opinion is not the usual, as it makes more sense for non-annotated parameters to be considered Nullable. However I can understand that decision in order to force developers to pay attention to translated code and decide explicitly if a parameter is Nullable or not.

By the way, mind that there are several @NonNull and @Nullable annotations (javax, android, jetbrains ...) I wouldn't be surprised if Kotlin translator only recognises some of them (but it's just a guess)

Also regarding your code, Java Lint should have given you a warning about your overridden onCreate, saying that you are overriding a method with annotated parameters and you have not annotated them.

like image 102
rupps Avatar answered Sep 23 '22 21:09

rupps