RecyclerView Selection与对话框一起使用会导致崩溃.对null对象执行getAdapterPosit



在我目前正在开发的应用程序中,我在一个视图寻呼机中有一个片段,它显示一个由数据库中的一些数据填充的RecyclerView。在这个RecyclerView中,我使用RecyclerView选择库和操作模式实现了ItemSelection。仅此一项就可以正常工作。然而,我也有一个FloatingActionButton,它打开了一个对话框,用户可以在其中向数据库添加一个新的入口,该入口也将显示在回收视图中。对话框打开成功,但当用户单击编辑文本时,它会崩溃,并显示以下错误消息:

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)

对话框:

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)
}
}
}
}

对话框布局:

<?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>

片段:

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)
}
}

ItemDetailLookup没有问题,因为我把所有东西都放在try-catch块中,错误仍然显示出来。

进一步的调试表明,只有当和项显示在对话框后面时才会发生这种情况(在我的屏幕上,当显示了七个项时(。然而,当对话框打开时,另一个片段显示在我的viewpager中时,也会发生这种情况。由此,我怀疑点击事件是通过对话框传递到底层碎片的。

如有任何帮助,我们将不胜感激。

编辑:

显示其他对话框时也会发生这种情况

可能有点晚了,但我最近遇到了同样的问题,并找到了一个适合我的解决方案。创建一个自定义ItemKeyProvider为我解决了NPE问题。我使用了我在网上找到的这篇文章:回收视图选择的指南

class LongItemKeyProvider(private val recyclerView: RecyclerView) :
ItemKeyProvider<Long>(SCOPE_MAPPED) {
override fun getKey(position: Int): Long? {
return recyclerView.adapter?.getItemId(position)
}
override fun getPosition(key: Long): Int {
return recyclerView.findViewHolderForItemId(key)?.layoutPosition ?: 
RecyclerView.NO_POSITION
}
}

由于某种原因,选择跟踪器试图获取不存在或不在回收视图中的视图持有者的项目详细信息,该视图持有者产生了空指针exeption。在我的ViewHolder中,我有这样的方法来返回关于ViewHolder的所有必要信息,以使选择跟踪器工作,这里有一个正确的实现是空安全的:

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
}

为了进行比较,请看我的问题,以前的实现发布在哪里。

相关内容

最新更新