1

I am trying to use ItemTouchHelper for swipe and drag'n'drop actions in a recyclerview. The data is coming from a android room database. Its methods should be async.

I cannot use ListAdapter in the recyclerview, because it calls other methods then notifyItemMoved. That would let drag'n'drop actions glitch in ItemTouchHelper, see ItemTouchHelper - The drop is forced after the first jumped line. At the end I have to implement my own adapter and care about the notify functions myself.

I followed this example to get ItemTouchHelper work with Android Room, which looks like this:

private CategoryAdapter categoryAdapter;
private DaoCategory daoCategory;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.category_activity);
    setTitle(R.string.edit_categories);

    RecyclerView recyclerView = findViewById(R.id.recyclerView);
    daoCategory = DataBase.Companion.getInstance(this.getApplicationContext()).getDaoCategory();

    categoryAdapter = new CategoryAdapter(new ArrayList<>(daoCategory.getCategoriesAscending()));
    recyclerView.setAdapter(categoryAdapter);
    updateList(() -> {
    });

    ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
        @Override
        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
            moveCategory(categoryAdapter.getCatList().get(viewHolder.getAdapterPosition()), viewHolder.getAdapterPosition(), target.getAdapterPosition());
            return true;
        }

        @Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
            CategoryViewHolder categoryViewHolder = (CategoryViewHolder) viewHolder;
            deleteCategory(categoryAdapter.getCatList().get(categoryViewHolder.getAdapterPosition()), categoryViewHolder.getAdapterPosition());
        }
    });
    itemTouchHelper.attachToRecyclerView(recyclerView);

    FloatingActionButton floatingActionButton = findViewById(R.id.fab);
    floatingActionButton.setOnClickListener(v -> {
        saveCategory(new Category(String.valueOf(System.currentTimeMillis())));
    });
}

private void updateList(Callback callback) {
    new AsyncTask<Void, Void, List<Category>>() {
        @Override
        protected List<Category> doInBackground(Void... params) {
            return daoCategory.getCategoriesAscending();
        }

        @Override
        protected void onPostExecute(List<Category> categories) {
            categoryAdapter.setCatList(new ArrayList<>(categories));
            callback.callback();
        }
    }.execute();
}

interface Callback {
    void callback();
}

private void deleteCategory(final Category category, final int oldpos) {
    new AsyncTask<Category, Void, Void>() {
        @Override
        protected Void doInBackground(Category... params) {
            daoCategory.deleteCategory(category.getName());
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            updateList(() -> categoryAdapter.notifyItemRemoved(oldpos));
        }
    }.execute();
}

private void moveCategory(final Category category, final int oldPos, final int newPos) {
    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground(Void... params) {
            daoCategory.moveCategory(category.getName(), newPos);
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            updateList(() -> categoryAdapter.notifyItemMoved(oldPos, newPos));
        }
    }.execute();
}

private void saveCategory(final Category category) {
    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground(Void... params) {
            daoCategory.saveCategory(category);
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            updateList(() -> categoryAdapter.notifyItemInserted(categoryAdapter.getItemCount() - 1));
        }
    }.execute();
}

Especially on drag'n'drop, when moveCategory is called, the method is called that fast, that everything crashes with a "IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter". Race conditions.

Any idea how I can fix that easily? The solution I see here is to get all daoCategory.[...]Category methods running mutex and in the right order. This could be achieved with a queue of threads (maybe limited to 3-5 items). The categoryAdapter.[...] things would runOnUiThread. I hope "synchronized" covers this too.

On the other side I can get it to work easily when I allowMainThreadQueries(), which is not intended to use. This could mean, that the ui freezes on queries (some daoCategory methods consist of many queries, like moveCategory).

Which approach would you choose? Do I miss any obvious solutions?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Kolonka
  • 56
  • 5

0 Answers0