I’ve been toying with the ItemTouchHelper class to enable “swipe to delete” for rows in a RecyclerView. It works beautifully most of the time, but when I dismiss the very first row my app always crashes with the “java.lang.IllegalArgumentException: parameter must be a decendant of this view” error.
Matters are complicated by the fact that each row contains focusable components (EditTexts and ImageButtons). I saw this question which addressed the same error, so I figured that I was encountering a similar focusing problem. In an attempt to fix the bug I overrode the onViewDetachedFromWindow in the RecyclerViewAdapter to clear the focus from each view group as it is detached:
@Override
public void onViewDetachedFromWindow(ViewHolder holder) {
int position = holder.getAdapterPosition();
Log.d(TAG, "onViewDetachedFromWindow (" + position + ")");
holder.mViewGroup.clearFocus();
super.onViewDetachedFromWindow(holder);
}
It didn't solve the problem. Now I’m at a loss for how to continue. Has anyone else encountered a similar issue, and does anyone have any ideas as to why this error would only happen when dismissing the first row?
Stack Trace with error and debug logging messages (first row swiped when two rows present):
D/ItemTouchHelperCallback: onSwiped
D/ExerciseSessionAdapter: onItemDismiss (0)
D/ExerciseSession: removeSet
D/ExerciseSession: refreshCurrentSetIndex
D/ActiveExerciseSession: onExerciseSessionUpdate
D/ActiveExerciseSession: refreshFab
D/ExerciseSession: isCompleted
D/ExerciseSession: getNumSets
D/ExerciseSessionAdapter: getItemCount
D/ExerciseSession: getNumSets
D/TestItemTouchHelper: getItemOffsets
D/TestItemTouchHelper: getItemOffsets
D/ExerciseSessionAdapter: getItemCount
D/ExerciseSession: getNumSets
D/ExerciseSessionAdapter: getItemCount
D/ExerciseSession: getNumSets
D/ExerciseSessionAdapter: getItemViewType
D/ExerciseSessionAdapter: onBindViewHolder
D/ExerciseSession: getSet
D/ExerciseSession: getCurrentSetIndex
D/ExerciseSession: isCompleted
D/ExerciseSession: hasCategory
D/SetMeasurementChangedListener: setMeasurementChangedListener
D/ButtonEditText: setOnNumberChangedListener
D/ButtonEditText: setNumber
D/ButtonEditText: getNumberString
D/ButtonEditText: onNumberChanged
D/SetMeasurementChangedListener: numberChanged
D/ExerciseSession: getSet
D/ButtonEditText: getNumber
D/ExerciseSession: hasCategory
D/SetMeasurementChangedListener: setMeasurementChangedListener
D/ButtonEditText: setOnNumberChangedListener
D/ButtonEditText: setNumber
D/ButtonEditText: getNumberString
D/ButtonEditText: onNumberChanged
D/SetMeasurementChangedListener: numberChanged
D/ExerciseSession: getSet
D/ButtonEditText: getNumber
D/ExerciseSession: hasCategory
D/ExerciseSession: hasCategory
D/ExerciseSessionAdapter: onBindViewHolder (0)
D/ExerciseSessionAdapter: onViewAttachedToWindow (0)
D/TestItemTouchHelper: onChildViewAttachedToWindow
D/TestItemTouchHelper: getItemOffsets
D/ExerciseSessionAdapter: getItemCount
D/ExerciseSession: getNumSets
D/ExerciseSessionAdapter: getItemCount
D/ExerciseSession: getNumSets
D/ExerciseSessionAdapter: getItemViewType
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
Process: edu.umn.paull011.evolveworkoutlogger, PID: 14405
java.lang.IllegalArgumentException: parameter must be a descendant of this view
at android.view.ViewGroup.offsetRectBetweenParentAndChild(ViewGroup.java:5334)
at android.view.ViewGroup.offsetDescendantRectToMyCoords(ViewGroup.java:5263)
at android.widget.ScrollView.isWithinDeltaOfScreen(ScrollView.java:1161)
at android.widget.ScrollView.onSizeChanged(ScrollView.java:1566)
at android.view.View.sizeChange(View.java:16748)
at android.view.View.setFrame(View.java:16710)
at android.view.View.layout(View.java:16627)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1079)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.support.design.widget.CoordinatorLayout.layoutChild(CoordinatorLayout.java:1092)
at android.support.design.widget.CoordinatorLayout.onLayoutChild(CoordinatorLayout.java:802)
at android.support.design.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:816)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at com.android.internal.policy.PhoneWindow$DecorView.onLayout(PhoneWindow.java:2678)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2171)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1931)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1107)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6013)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
at android.view.Choreographer.doCallbacks(Choreographer.java:670)
at android.view.Choreographer.doFrame(Choreographer.java:606)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Initializing the RecyclerView:
public class ExerciseSessionSetsFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
mRecyclerView.setHasFixedSize(false);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
// specify an adapter
mAdapter = new ExerciseSessionAdapter(this.getContext(), mExerciseSession);
mRecyclerView.setAdapter(mAdapter);
// add ItemTouchHelperCallBack to RecyclerView
ItemTouchHelper.Callback callback = new ItemTouchHelperCallback(mAdapter);
ItemTouchHelper touchHelper = new TestItemTouchHelper(callback); //Prints log messages
touchHelper.attachToRecyclerView(mRecyclerView);
...
}
}
OnItemDismiss Callback:
public class ExerciseSessionAdapter extends RecyclerView.Adapter<ExerciseSessionAdapter.ViewHolder>
implements ItemTouchHelperAdapter{
...
@Override
public void onItemDismiss(int position) {
Log.d(TAG,"onItemDismiss");
mExerciseSession.removeSet(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, mExerciseSession.getNumSets() - position);
}
...
}
Any advice would be greatly appreciated!