2

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.

steven smith
  • 1,309
  • 12
  • 27
  • Does it matter that the Java code has `ContextMenu.ContextMenuInfo menuInfo`, specifying `ContextMenuInfo` is a static member class of `ContextMenu`, while the Kotlin code just has `menuInfo: ContextMenuInfo`? In `MainActivity#onCreateContextMenu`. – Jonny Henly Sep 01 '20 at 22:27
  • Thanks @JonnyHenly. No. When the Kotlin conversion was done, it generated "import android.view.ContextMenu.ContextMenuInfo" So that is the same. – steven smith Sep 01 '20 at 23:12
  • 3
    I think I found the problem - check https://stackoverflow.com/questions/53794754/parameter-specified-as-non-null-is-null-method-kotlin-jvm-internal-intrinsics-c - so it should be `menuInfo: ContextMenuInfo?` - I guess it’s just a way to signal to the compiler that you know `null` is a valid value for the parameter. Kotlin can probably optimize your code if it knows the value will never be `null`. – Jonny Henly Sep 01 '20 at 23:35
  • Although, I’m not familiar with the Android API/SDK - so if `menuItem` should never be `null`, then this might be pointing to a larger problem - i.e. malformed xml, change of source path, gradle build problems, uncaught exceptions, etc. Which is a nightmare in small projects, 10x worse in much larger projects. – Jonny Henly Sep 01 '20 at 23:46
  • 1
    Thank you @JonnyHenly! I would never have found that. I'll post a note to my question explaining the solution. So -- is this a bug? If so (and it looks like a bug to me) who should I report it to? – steven smith Sep 02 '20 at 02:08
  • 1
    So it does smell like a bug, especially for such an overridden method in the Android SDK, however I think it’s more of a limitation of the Java -> Kotlin converter. Parsing if the method checks for `if (menuItem == null)` early on and subsequently appending a `?` to the parameter type, seems like a no brainer. At the same time, I’ve never attempted creating a converter, so I’m sure there are many caveats and outliers which would make this comment seem asinine. – Jonny Henly Sep 02 '20 at 03:55
  • 1
    I looked into this a bit more. I think the reason I had problems is that instead of creating the menu items I'm using a menu.xml layout and inflating it so I don't look at individual menuItems here. Instead with this style I catch menu selections in onContextItemSelected. So -- in my case the menu item is null because there are no individual menu items and the field should be @Nullable. I looked for the source for Activity.onCreateContextMenu and it doesn't seem to be online. In any case, the fix is working. If anyone finds info re: the menuItem that is contrary, please let me know. – steven smith Sep 03 '20 at 02:09

1 Answers1

2

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.

steven smith
  • 1,309
  • 12
  • 27
  • In my opinion in general it's nicer to ask the commentor to submit and answer, so you can upvote and accept it. also it's cleaner to put the answer in the answer, not on the question. (just said it so you can consider it for next questions if you like). regards. – yaya Sep 03 '20 at 06:14