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.