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?