in my app that I am currently developing I have a fragment in a viewpager that displays a RecyclerView which is filled by some data from a database. In this RecyclerView I have implemented ItemSelection with the recyclerview-selection library together with an actionmode. This alone is working fine. However I also have a FloatingActionButton which opens a dialog where the user can add a new entrance to the database which would also be displayed in the recyclerview. The dialog opens successful but when the user clicks the edittext it crashes with the following error message:
2020-08-16 16:45:36.413 12939-12939/com.nilswinking.kochbuch2 E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.me.myapp, PID: 12939
java.lang.NullPointerException: Attempt to invoke virtual method 'int androidx.recyclerview.widget.RecyclerView$ViewHolder.getAdapterPosition()' on a null object reference
at androidx.recyclerview.selection.StableIdKeyProvider.onDetached(StableIdKeyProvider.java:90)
at androidx.recyclerview.selection.StableIdKeyProvider$1.onChildViewDetachedFromWindow(StableIdKeyProvider.java:69)
at androidx.recyclerview.widget.RecyclerView.dispatchChildDetached(RecyclerView.java:7546)
at androidx.recyclerview.widget.RecyclerView.removeDetachedView(RecyclerView.java:4349)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.removeAndRecycleScrapInt(RecyclerView.java:9243)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep3(RecyclerView.java:4207)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3862)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at androidx.viewpager.widget.ViewPager.onLayout(ViewPager.java:1775)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at androidx.coordinatorlayout.widget.CoordinatorLayout.layoutChild(CoordinatorLayout.java:1213)
at androidx.coordinatorlayout.widget.CoordinatorLayout.onLayoutChild(CoordinatorLayout.java:899)
at androidx.coordinatorlayout.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:919)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at androidx.coordinatorlayout.widget.CoordinatorLayout.layoutChild(CoordinatorLayout.java:1213)
at androidx.coordinatorlayout.widget.CoordinatorLayout.onLayoutChild(CoordinatorLayout.java:899)
at androidx.coordinatorlayout.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:919)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at androidx.coordinatorlayout.widget.CoordinatorLayout.layoutChild(CoordinatorLayout.java:1213)
at androidx.coordinatorlayout.widget.CoordinatorLayout.onLayoutChild(CoordinatorLayout.java:899)
at androidx.coordinatorlayout.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:919)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at androidx.appcompat.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:446)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
2020-08-16 16:45:36.416 12939-12939/com.me.myapp E/AndroidRuntime: at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:784)
at android.view.View.layout(View.java:22844)
at android.view.ViewGroup.layout(ViewGroup.java:6389)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3470)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2938)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1952)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8171)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:972)
at android.view.Choreographer.doCallbacks(Choreographer.java:796)
at android.view.Choreographer.doFrame(Choreographer.java:731)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Dialog:
class AddBookDialog : DialogFragment() {
private val TAG = AddBookDialog::class.java.simpleName
private lateinit var listener: AddBookDialogInterface
private lateinit var editTextName: EditText
private lateinit var title: TextView
private var id: String? = null
interface AddBookDialogInterface {
fun addKochbuch(name: String, id: String? = null): Boolean
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(it, R.style.AppTheme_AlertDialog)
val inflater = requireActivity().layoutInflater;
val view = inflater.inflate(R.layout.add_book_dialog, null)
editTextName = view.findViewById(R.id.editTextName)
// editTextNameLayout = view.findViewById(R.id.editTextNameLayout)
title = view.findViewById(R.id.title)
arguments?.let {
it.getString(nameParam).let {
editTextName.setText(it)
}
id = it.getString(idParam)
}
editTextName.setOnKeyListener(View.OnKeyListener { v, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP) {
add()
return@OnKeyListener true
}
false
})
builder.setView(view)
.setPositiveButton(
"Hinzufügen"
) { dialog, id ->
add()
}
.setNegativeButton(
"Abbrechen"
) { dialog, id ->
// User cancelled the dialog
}
// Create the AlertDialog object and return it
val dialog: AlertDialog = builder.create()
dialog.setOnShowListener {
val button: Button = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
button.setOnClickListener {add()}
}
dialog
} ?: throw IllegalStateException("Activity cannot be null")
}
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
listener = context as AddBookDialogInterface
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException((context.toString() +
" must implement AddBookDialogInterface"))
}
}
private fun add() {
val name = editTextName.text.toString()
listener.addKochbuch(name, id).let {
if (it)
dismiss()
}
}
companion object {
fun newInstance(kochbuch: Kochbuch? = null) =
AddBookDialog().apply {
arguments = Bundle().apply {
putString(nameParam, kochbuch?.name)
putString(idParam, kochbuch?.id)
}
}
}
}
Dialog layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="Erstelle ein neues Kochbuch"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textColor="@color/material_on_background_emphasis_high_type"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/editTextNameLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:hint="Name deines Kochbuches"
android:textColorHint="@color/input_outline_color"
app:boxStrokeColor="@color/input_outline_color"
app:hintTextColor="@color/input_outline_color"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editTextName"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:singleLine="true"
android:textColor="@color/material_on_background_emphasis_high_type" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment:
class HomeBooksFragment : Fragment(), OnActionItemClickListener {
private val TAG = HomeBooksFragment::class.java.simpleName
private lateinit var realm: Realm
private val adapter = ItemAdapterCookingBooks()
private lateinit var result: RealmResults<Kochbuch>
private lateinit var tracker: SelectionTracker<Long>
private var actionmode: SelectionActionModeCallback? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home_books, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
realm = Realm.getDefaultInstance()
result = realm.where<Kochbuch>().sort("name").findAll()
result.addChangeListener { t, _ ->
updateUI(t)
}
adapter.data = ArrayList()
rv.layoutManager = GridLayoutManager(context, 2)
rv.adapter = adapter
tracker = SelectionTracker.Builder<Long>(
"mySelection",
rv,
StableIdKeyProvider(rv),
ItemDetailLookup(rv),
StorageStrategy.createLongStorage()
).withSelectionPredicate(
SelectionPredicates.createSelectAnything()
).build()
tracker.addObserver(
object : SelectionTracker.SelectionObserver<Long>() {
override fun onSelectionChanged() {
super.onSelectionChanged()
tracker.selection.size().let { i ->
when {
i >= 2 -> actionmode?.setEditEnabled(false)
i == 1 -> actionmode?.setEditEnabled(true)
i == 0 -> Log.d(TAG, "onSelectionChanged: zero items selected")
else -> null
}
if (tracker.hasSelection() && actionmode == null) {
actionmode = SelectionActionModeCallback()
view?.let { actionmode?.startActionMode(it, R.menu.selection_action_mode_menu, tracker, "$i Ausgwählt") }
actionmode?.setListener(this@HomeBooksFragment)
} else if (!tracker.hasSelection() && actionmode != null) {
actionmode?.finishActionMode()
actionmode = null
} else {
actionmode?.setTitle("$i Ausgewählt")
}
}
}
}
)
adapter.tracker = tracker
updateUI(result)
}
}
It is no problem with the ItemDetailLookup as I put everything in there in try-catch blocks and the error still showed up.
Further debugging showed, that it only happens when and item is displayed behind the dialog (On my screensize when seven items are diaplayed). However it also happens when the dialog is opened while another fragment is displayed in my viewpager. From this I suspect that the click event is somehow passed through the dialog to the underlaying fragment.
Any help would be appreciated.
Also happens when showing other dialogs
For some reason the selection tracker tried to get the itemdetails of a viewholder that either did not exist or wasn't in the recyclerview and that produced the null pointer exeption. In my ViewHolder I have this method to return all necessary information about the viewholder to make the selection tracker work and here it is with the correct implementation that is null safe:
fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
object : ItemDetailsLookup.ItemDetails<Long>() {
override fun getPosition(): Int {
try {
return adapterPosition
} catch (e: Exception) {
return -1
}
}
override fun getSelectionKey(): Long? = itemId
}
For comparison look at my question where the previous implementation is posted.