Java to Kotlin converter and nullable method arguments

diman :

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.

rupps :

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.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=468094&siteId=1