In my case, I had an adapter which worked with a recyclerView, the items that were being passed to the adapter were items with their own views.
What was required was just a LinearLayout to act as a container for every item passed, so what I was doing was to grab the item in the specified position inside onBindViewHolder
then add it to the LinearLayout, which was then displayed.
Checking the basics in docs,
When an item scrolls off the screen, RecyclerView doesn't destroy its
view
Therefore, with my items, when I scroll towards a direction, then change towards the opposite direction - fast, the racently displayed items have not been destroyed, meaning, the items are still associated with the LinearLayout container, then on my end, I'm trying to attach to another container, which ends up with a child having a parent already.
My solution was to check if the specified item has a parent, if it has, I assign it to a variable, then call parentVar.removeView(item)
, then assign the new parent.
Here's the sample code(Problematic Adapter):
override fun onBindViewHolder(holder: QuestionWidgetViewHolder, position: Int) {
holder.linearLayoutContainer.removeAllViewsInLayout()
val questionWidget: QuestionWidget =
dataSource[position]
questionWidget.setValueChangedListener(this)
holder.linearLayoutContainer.addView(questionWidget)/*addView throws error once in a while*/
}
inner class QuestionWidgetViewHolder(mView: View) :
RecyclerView.ViewHolder(mView) {
val linearLayoutContainer: LinearLayout =
mView.findViewById(R.id.custom_question_widget_container)
}
Content of the R.id.custom_question_widget_container:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/custom_question_widget_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="vertical"
android:padding="10dp" />
So, the questionWidget
seems to have been retaining the parent for almost 4 steps outside visibility, and when I scroll to the opposite direction fast, I would encounter it still with its parent, then I'm attempting to add it to another container.
Here's the fix - option 1:
override fun onBindViewHolder(holder: QuestionWidgetViewHolder, position: Int) {
holder.linearLayoutContainer.removeAllViewsInLayout()
val questionWidget: QuestionWidget =
dataSource[position]
questionWidget.setValueChangedListener(this)
val initialWidgetParent : ViewParent? = questionWidget.parent
//attempt to detach from previous parent if it actually has one
(initialWidgetParent as? ViewGroup)?.removeView(questionWidget)
holder.linearLayoutContainer.addView(questionWidget)
}
Another better solution - option 2:
override fun onBindViewHolder(holder: QuestionWidgetViewHolder, position: Int) {
holder.linearLayoutContainer.removeAllViewsInLayout()
val questionWidget: QuestionWidget =
dataSource[position]
questionWidget.setValueChangedListener(this)
val initialWidgetParent : ViewParent? = questionWidget.parent
//if it's in a parent container already, just ignore adding it to a view, it's already visible
if(initialWidgetParent == null) {
holder.linearLayoutContainer.addView(questionWidget)
}
}
Actually, it's much of asking the child if it has a parent before adding it to a parent.