Convert java to kotlin breaks contextmenu

Viewed 180

I've been converting a project to Kotlin and discovered a problem. The context menu from the java code is broken in the generated kotlin.
This is a simplified test of the source from the project. It consists of only a main activity with a single layout and a context menu. The java version works but the kotlin version crashes. The only thing I can think of that is unusual is that the view I'm registering is an imageView in a RelativeLayout.

 java.lang.NullPointerException:
        Parameter specified as non-null is null: 
        method kotlin.jvm.internal.Intrinsics.checkNotNullParameter
        , parameter menuInfo
    at com...MainActivity.onCreateContextMenu(MainActivity.kt)
    at android.view.View.createContextMenu(View.java:8392)
    at com.android.internal.view.menu.ContextMenuBuilder
        .show(ContextMenuBuilder.java:81)
    at com.android.internal.policy.impl
        .PhoneWindow$DecorView
        .showContextMenuForChild(PhoneWindow.java:2517)
    at android.view.ViewGroup.showContextMenuForChild(ViewGroup.java:658)

MainActivity.java is:

public class MainActivity extends AppCompatActivity {
    private static int animationSpeed = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        registerForContextMenu(findViewById(R.id.imageView));
    }
    @Override
    public void onCreateContextMenu(ContextMenu menu, View v,
                ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.speed_select, menu);
        menu.getItem(animationSpeed).setChecked(true);
    }
    @Override
    public boolean onContextItemSelected(MenuItem item) {
        int itemId = item.getItemId();
        boolean rv = true;
        switch(itemId) {
            case R.id.animate_slow: animationSpeed = 0; break;
            case R.id.animate_normal: animationSpeed = 1; break;
            case R.id.animate_fast: animationSpeed = 2; break;
            default: Log.d("onContextItemSelected", String.format(
                    "menu item unhandled:0x%08x", itemId)
            );
                rv = false;
        }
        return rv;
    }
}

MainActivity.kt is:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        registerForContextMenu(findViewById(R.id.imageView))
    }
    override fun onCreateContextMenu(menu: ContextMenu, v: View,
                                     menuInfo: ContextMenuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo)
        val inflater = menuInflater
        inflater.inflate(R.menu.speed_select, menu)
        menu.getItem(animationSpeed).isChecked = true
    }
    override fun onContextItemSelected(item: MenuItem): Boolean {
        val itemId = item.itemId
        var rv = true
        when (itemId) {
            R.id.animate_slow -> animationSpeed = 0
            R.id.animate_normal -> animationSpeed = 1
            R.id.animate_fast -> animationSpeed = 2
            else -> {
                Log.d("onContextItemSelected", String.format(
                        "menu item unhandled:0x%08x", itemId))
                rv = false
            }
        }
        return rv
    }
    companion object {
        private var animationSpeed = 0
    }
}

My menu file is:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <group 
        android:checkableBehavior="single"
        android:id="@+id/animate_speed" >
       <item android:id="@+id/animate_slow"
             android:title="@string/slow" />
       <item android:id="@+id/animate_normal"
             android:title="@string/normal" />
       <item android:id="@+id/animate_fast"
             android:title="@string/fast" />
       </group>
</menu>

The activity layout is:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/relative_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="250dp"
        android:layout_height="250dp"
        android:layout_centerInParent="true"
        android:background="@drawable/andy"
        />
</RelativeLayout>

I've tried breaking in the onCreateContextMenu but never get there.
I'm using Kotlin 1.40, AndroidStudio 4.01, SDK 30, and gradle 4.01. I've been looking at the docs and the code for a couple of days now and to me, the generated kotlin looks right.
Thanks!

Thanks to John Healy below this was solved. John said he thought it might be in Kotlin's null-safety handling. I doubted so I added a log statement to the working Java code and menuInfo was coming in as a null. I added a @Nullable annotation to the Java declaration which gave me:

public void onCreateContextMenu(
    ContextMenu menu, View v,
    @Nullable ContextMenu.ContextMenuInfo menuInfo)

Testing of the Java code showed the compiler and lint were happy and the code still ran. I again ran the jave through the conversion process and the resulting kotlin signature for the function is:

override fun onCreateContextMenu(
    menu: ContextMenu, v: View,
    menuInfo: ContextMenuInfo?)

I tested the Kotlin and it now works too!

NOTE: for your edification and amusement I posted the source on git hub.

1 Answers

I'm only puting up this reply to my question so people will notice a solution was found thanks to a commentor. This fix only applies if you are using a menu list rather than creating individual menu items and the fix is only necessary for Kotlin because of the way in which Kotlin handles null-safety. Please look at the end of the question and at my comment to that question,
Steve S.

Related