3

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!

Community
  • 1
  • 1
Mitchell
  • 61
  • 5

1 Answers1

3

If anyone encounters the same issue, I managed to fix mine by making a small tweak to my onItemDismiss method. The error was created by the notifyItemRangeChanged method when the dismissed item was at position zero. I instead used the notifyDataSetChanged method to fix the problem:

@Override
public void onItemDismiss(int position) {
    Log.d(TAG, "onItemDismiss");
    mExerciseSession.removeSet(position);
    notifyItemRemoved(position);
    if (position != 0) {
        notifyItemRangeChanged(position, mExerciseSession.getNumSets() - position);
    }
    else {
        notifyDataSetChanged();
    }
}
Mitchell
  • 61
  • 5
  • you really saved me a couple of hours with this solution! I'm wondered if Android team even know about this inner bug... – Oleksandr Nos Feb 20 '18 at 15:30