1

I have a private ItemTouchHelper that delete a row from a room database however currently it's cause an ArrayIndexOutOfBounds exception when I swiping to delete:

FATAL EXCEPTION: main
Process: xxx.xxx.xxx.xxx, PID: 6327
java.lang.ArrayIndexOutOfBoundsException: length=1; index=-1

I've posted a similar question however now I've distilled the source of the problem being the itemTouchHelper.

The itemtouch helper is attached to a RecycleViewAdapter that gets it's dataset from a room database.

The dataset is displayed as a list and i'm using the itemTouchHelper's viewHolder.getAdapterPosition to delete the item at the position that has been swiped in the RecycleAdapter. Is this causing me issues?

I've attached the relevant code:

database handling code in mainActivity.java

 @Override
    public boolean searchTerm(String title) {
        try {
            this.dbThread.submit(() -> {
                this.searchResults = this.userData.listModel().searchByTitle(title);
            });
            this.mRecycleAdapter.reloadAdapterData(this.searchResults);
            this.mRecycleAdapter.notifyDataSetChanged();
            Log.d(TAG, "Search Successful!");
            return true;
        } catch (Exception e) {
            Log.d(TAG, "Search Failed \n -See Console");
            return false;
        }
    }

    @Override
    public void addTask(GlobalLists item) {
        this.dbThread.submit(() -> {
            this.userData.listModel().insertItem(item);
        });
    }

    @Override
    public List<GlobalLists> getAllToDo() {
        this.dbThread.submit(() -> {
            this.searchResults = this.userData.listModel().loadAllLists();
        });
        return this.searchResults;
    }

    @Override
    public List<GlobalLists> getResults() {
        return this.searchResults;
    }

    @Override
    public RecycleViewAdapter getAdapter() {
        return this.mRecycleAdapter;
    }

    @Override
    public void refresh() {
        try {

            this.dbThread.submit(() -> {
                this.mRecycleAdapter.reloadAdapterData(this.userData.listModel().loadAllLists());
            });
            this.mRecycleAdapter.notifyDataSetChanged();
        } catch (Exception e) {
            Toast.makeText(this, "Refresh Failed", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
    }

    @Override
    public void completeTask(GlobalLists item) {
        try {
            this.dbThread.submit(() -> {
                this.userData.listModel().completeTask(new CompletedTasks(item.getTitle(), item.getContents()));
                this.userData.listModel().deleteItem(item);
            });
            this.refresh();
            Log.d(item.getTitle(), "Task Completed!");
        } catch (Exception e) {
            Toast.makeText(this, "Complete Task Failed", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
    }

    @Override
    public void deleteTask(GlobalLists item) {
        try {
            this.dbThread.submit(() -> {
                this.userData.listModel().deleteItem(item);
            });
            this.refresh();
            Log.d(item.getTitle(), "Task Deleted");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

RecycleViewAdapter

public class RecycleViewAdapter extends RecyclerView.Adapter<RecycleViewAdapter.ViewHolder>{

    private LayoutInflater inflater = null;
    private List<GlobalLists> results = null;

    public RecycleViewAdapter(Context context, List<GlobalLists> results){
        this.inflater = LayoutInflater.from(context);
        this.results = results;
    }



    @NonNull
    @Override
    public RecycleViewAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = inflater.inflate(R.layout.custom_row, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        GlobalLists item = this.results.get(position);
        holder.rowTitle.setText(item.getTitle());

    }

    public void reloadAdapterData(List<GlobalLists> refresh){
        this.results = refresh;
    }

    @Override
    public int getItemCount() {
        return this.results == null ? 0 : this.results.size();
    }




    public class ViewHolder extends RecyclerView.ViewHolder {

        private TextView rowTitle = null;
        private ImageView rowImage = null;
        private CheckBox completedCheck = null;

        public ViewHolder(View itemView) {
            super(itemView);
            this.rowTitle = itemView.findViewById(R.id.rowTitle);
            this.rowImage = itemView.findViewById(R.id.rowImage);
            this.completedCheck = itemView.findViewById(R.id.completedCheck);

        }
    }
}

fragment with itemTouchHelper

public class showAllLists_fragment extends android.support.v4.app.Fragment {

    private static final String TAG = "showAllLists";


    private RecyclerView mAllItemsView = null;
    private DataCommunication mData = null; //interface for communicating between Activity and fragment.
    private ItemTouchHelper itemTouchHelper = null;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View view = inflater.inflate(R.layout.show_all_lists_frag, container, false);


        this.mAllItemsView = (RecyclerView) view.findViewById(R.id.allItems);
        this.itemTouchHelper = new ItemTouchHelper(this.createHelperCallBack());
        this.itemTouchHelper.attachToRecyclerView(this.mAllItemsView);
        this.mAllItemsView.setAdapter(this.mData.getAdapter());
        this.mAllItemsView.setLayoutManager(new LinearLayoutManager(getActivity()));


        return view;
    }


    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try {
            this.mData = (DataCommunication) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(context.toString()
                    + " must implement DataCommunication");
        }
    }

    private ItemTouchHelper.Callback createHelperCallBack() {

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

            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                if (direction == ItemTouchHelper.LEFT) {
                    mData.deleteTask(mData.getAllToDo().get(viewHolder.getAdapterPosition()));
                    mData.getAdapter().notifyItemRemoved(viewHolder.getAdapterPosition());


                } else if (direction == ItemTouchHelper.RIGHT) {
                    mData.completeTask(mData.getAllToDo().get(viewHolder.getAdapterPosition()));
                    mData.getAdapter().notifyItemRemoved(viewHolder.getAdapterPosition());

                }

            }
        };

        return simpleCallback;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

        if (isVisibleToUser) {
            this.mData.refresh();
        }
    }

}

I've noted from debugging that it takes 2 swipes for an item to fully dissapear from the RecycleAdapter's view and sometimes it does not delete what I'm swiping

Adrian Coutsoftides
  • 1,203
  • 1
  • 16
  • 38

1 Answers1

0

I found that the issue was indeed on the item touch helper; specifically, they way I was retrieving data.

In my onSwipe I would call get the item I was swiping by the following method:

mData.deleteTask(mData.getAllToDo().get(viewHolder.getAdapterPosition()));

The issue with this is that the getAllToDo() method would return the current list of database items (even if the list has been unchanged) as opposed to the list held within the adapter.

So to fix this, I have re-implemented the line as following:

`mData.deleteTask(mData.getAdapter().getResults().get(viewHolder.getAdapterPosition()));

Where getAdaper() retrieves the adapter I'm using, and getResults() retrieves the list the adapter is currently using. The object is then properly identified and removed from the database and all works fine.

Note that if you then receive an error such as the following: java.lang.IndexOutOfBoundsException: Inconsistency detected, then it is because you are changing the adapter data in a different thread to your main thread.

The solution to which can be found here:

RecyclerView and java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder in Samsung devices

Ram Koti
  • 2,203
  • 7
  • 26
  • 36
Adrian Coutsoftides
  • 1,203
  • 1
  • 16
  • 38