Search code examples
androidandroid-recyclerviewandroid-snackbarsnackbaritemtouchhelper

RecyclerView ItemTouchHelper getAdapterPosition() returns wrong value on fast swipe


I have a problem regarding RecyclerView + Swipe to delete + undo with the help of a snackbar. Whenever I try to fast swipe 3+ items to delete them, viewHolder.getAdapterPosition(); returns a wrong value. Seems like it can't react that fast.

RecyclerView list Example:

  1. Test1
  2. Test2
  3. Test3
  4. Test4

So, if I quickly swipe away Test3, Test2 and then Test1 viewHolder.getAdapterPosition(); returns the positions 2, 2 and then 0. The correct return has to be 2, 1 and then 0.

For two items everything behaves normal, returns 2 and then 1.

Note: Last position is always returned 2 seconds later, because snackbar times out and then the item gets deleted. The snackbars which show before get dismissed as soon as the next item gets swiped away.

Any ideas how to solve this? I tried to figure it out for several hours now.

ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
        @Override
        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
            return false;
        }

        @Override
        public void onSwiped(final RecyclerView.ViewHolder viewHolder, int swipeDir) {

            final int position = viewHolder.getAdapterPosition();

            FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
            rusure = Snackbar.make(fab, getResources().getString(R.string.rule_deleted), Snackbar.LENGTH_SHORT).addCallback(new Snackbar.Callback() {
                @Override
                public void onDismissed(Snackbar snackbar, int event) {

                    switch(event) {
                        case Snackbar.Callback.DISMISS_EVENT_ACTION:
                            rulesAdapter.notifyDataSetChanged();
                            break;
                        case Snackbar.Callback.DISMISS_EVENT_TIMEOUT:
                        case Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE:
                        case Snackbar.Callback.DISMISS_EVENT_MANUAL:

                            Log.d("test", Integer.toString(position));
                            if(position!=-1 && position<arrayList.size()){

                                // Define 'where' part of query.
                                String selection = DatabaseTablesColums.FeedEntry._ID + " LIKE ?";
                                // Specify arguments in placeholder order.
                                String[] selectionArgs = {Integer.toString(arrayList.get(position).getId())};
                                // Issue SQL statement.


                                arrayList.remove(position);
                                rulesAdapter.notifyItemRemoved(position);
                                rulesAdapter.notifyItemRangeChanged(position, rulesAdapter.getItemCount());


                                db.delete(DatabaseTablesColums.FeedEntry.TABLE_NAME, selection, selectionArgs);
                                // Remove item from backing list here
                            }

                            break;
                    }
                }

                @Override
                public void onShown(Snackbar snackbar) {
                }
            }).setAction(getResources().getString(R.string.undo), new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                }
            });
            rusure.show();


        }
    });

    itemTouchHelper.attachToRecyclerView(recyclerView);

Solution

  • Looks like you are removing the item from the backing data store, in your case, it's the arrayList and notifies the adapter only after the SnackBar dismisses.

    So if you swipe the next item before the first Snackbar dismisses, RecyclerView adapter has no idea that the first item has been removed from the adapter. So it's giving you a wrong position.

    You should remove the item from the backing data store (arrayList) and call notifyItemRemoved() as soon as you swipe the item rather than updating after the Snackbar dismisses.

    You can update the database only after the SnackBar dismisses, though.