Почему мой SelectionTracker продолжает удерживать элементы несмотря на то, что они уже удалены из Room?

Если коротко, я пытаюсь реализовать удаление нескольких элементов списка при помощи множественного выделения. Для хранения списка используется Room, для отображения — RecyclerView с кастомным ListAdapter и ViewHolder. Для реализации множественного выделения используется SelectionTracker с кастомными KeyProvider и ItemDetailsLookup. Выделенные элементы, помещаются в специальную коллекцию внутри ViewModel для дальнейшей обработки.

Отрисовка данных происходит корректно, запросы на удаление нескольких элементов успешно обрабатываются (по крайней мере в пределах малого числа элементов). Беспокоит меня в данный момент только одно: по какой-то причине, несмотря на то, что выделенные элементы успешно удаляются и из базы данных, и из RecyclerView соответственно, SelectionTracker не хочет очищать свою коллекцию — счётчик выделенных элементов не изменяется, метод hasSelection() возвращает true.

Я пробовал создать observer для коллекции во ViewModel, отвечающей за хранение выделенных элементов, но условие isEmpty не срабатывало, хотя я вызывал метод очистки той коллекции.

Journal.java

@Entity(tableName = "journal_table")
public class Journal {

    @PrimaryKey(autoGenerate = true)
    private int id;

    @ColumnInfo(name = "title")
    private String title;

    public Journal(String title) {
        this.title = title;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Journal) {
            Journal j2 = ((Journal) obj);

            boolean c1 = (this.id == j2.getId());

            boolean c2 = (this.title.contentEquals(j2.title));

            return c1 && c2;
        } else {
            return obj.equals(this);
        }
    }
}

JournalRepository.java

class JournalRepository {
    private JournalDao journalDao;
    private LiveData<List<Journal>> allJournals;

    JournalRepository(Application application) {
        JournalDatabase database = JournalDatabase.getInstance(application);
        journalDao = database.journalDao();
        allJournals = journalDao.getAllJournals();
    }

    void insert(Journal journal) {
        new InsertJournalAsyncTask(journalDao).execute(journal);
    }

    void delete(Journal journal) {
        new DeleteJournalAsyncTask(journalDao).execute(journal);
    }

    LiveData<List<Journal>> getAllJournals() {
        return allJournals;
    }

    private static class InsertJournalAsyncTask extends AsyncTask<Journal, Void, Void> {

        private JournalDao journalDao;

        private InsertJournalAsyncTask(JournalDao journalDao) {
            this.journalDao = journalDao;
        }

        @Override
        protected Void doInBackground(Journal... journals) {
            journalDao.insert(journals[0]);
            return null;
        }
    }


    private static class DeleteJournalAsyncTask extends AsyncTask<Journal, Void, Void> {

        private JournalDao journalDao;

        private DeleteJournalAsyncTask(JournalDao journalDao) {
            this.journalDao = journalDao;
        }

        @Override
        protected Void doInBackground(Journal... journals) {
            journalDao.delete(journals[0]);
            return null;
        }
    }
}

JournalViewModel.java

public class JournalViewModel extends AndroidViewModel {
    private JournalRepository repository;
    private LiveData<List<Journal>> allJournals;

    private LiveData<List<Journal>> selectedJournals = new MutableLiveData<>(new LinkedList<>());

    public JournalViewModel(@NonNull Application application) {
        super(application);
        repository = new JournalRepository(application);
        allJournals = repository.getAllJournals();
    }

    public void insert(Journal journal) {
        repository.insert(journal);
    }

    public void delete(Journal journal) {
        repository.delete(journal);
    }

    public void deleteSelectedJournals() {
        for (Journal j :
                Objects.requireNonNull(selectedJournals.getValue())) {
            delete(j);
        }

        selectedJournals.getValue().clear();
    }

    public void addToSelection(Journal journal) {
        Objects.requireNonNull(selectedJournals.getValue()).add(journal);
    }

    public void removeFromSelection(Journal journal) {
        Objects.requireNonNull(selectedJournals.getValue()).remove(journal);
    }

    public void clearSelection() {
        Objects.requireNonNull(selectedJournals.getValue()).clear();
    }

    public LiveData<List<Journal>> getAllJournals() {
        return allJournals;
    }

    public List<Journal> getFilteredJournals(String filter) {
        return repository.getFilteredJournals(filter);
    }

    public LiveData<List<Journal>> getSelectedJournals() {
        return selectedJournals;
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    Resources resources;
    JournalViewModel viewModel;

    JournalAdapter adapter;
    SelectionTracker<Long> tracker;

    ActionMode actionMode;
    RecyclerView recyclerView;
    FloatingActionButton buttonCreate;

    private ActionMode.Callback callback = new ActionMode.Callback() {
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            mode.getMenuInflater().inflate(R.menu.menu_main_selection, menu);
            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            mode.setTitle(resources.getString(R.string.selected_count_title, tracker.getSelection().size()));
            buttonCreate.setVisibility(View.GONE);
            return true;
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            switch (item.getItemId()) {
                case R.id.select_all:
                    tracker.setItemsSelected(adapter.getAllIds(), true);
                    return true;
                case R.id.delete_selected:
                    viewModel.deleteSelectedJournals();
                    return true;
                default:
                    return false;
            }
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            tracker.clearSelection();
            viewModel.clearSelection();
            actionMode = null;
            buttonCreate.setVisibility(View.VISIBLE);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        resources = getResources();
        adapter = new JournalAdapter();
        viewModel = new ViewModelProvider(this).get(JournalViewModel.class);

        recyclerView = findViewById(R.id.recycler_view);
        buttonCreate = findViewById(R.id.button_create_journal);

        LinearLayoutManager llm = new LinearLayoutManager(this);

        recyclerView.setLayoutManager(llm);
        recyclerView.setAdapter(adapter);

        recyclerView.setHasFixedSize(true);

        buttonCreate.setOnClickListener(view -> openCreateDialog());

        adapter.setOnSelectionChangedListener((journal, isChecked) -> {
            if (isChecked) {
                viewModel.addToSelection(journal);
            } else {
                viewModel.removeFromSelection(journal);
            }
        });

        viewModel.getAllJournals().observe(this, adapter::submitList);

        viewModel.getAllJournals().observe(this, journals -> {
            if (journals.size() == 0) {
                if (actionMode != null) {
                    actionMode.finish();
                }
            }
        });

        tracker = new SelectionTracker.Builder<>(
                "selectedJournalsTrackerId",
                recyclerView,
                new JournalKeyProvider(adapter, recyclerView),
                new JournalDetailsLookup(recyclerView),
                StorageStrategy.createLongStorage()
        )
                .withSelectionPredicate(SelectionPredicates.createSelectAnything())
                .build();

        tracker.addObserver(new SelectionObserver<Long>() {
            @Override
            public void onSelectionChanged() {
                if (tracker.hasSelection()) {
                    if (actionMode == null) {
                        actionMode = startSupportActionMode(callback);
                    } else {
                        actionMode.setTitle(resources.getString(R.string.selected_count_title, tracker.getSelection().size()));
                    }
                } else if (!tracker.hasSelection()) {
                    if (actionMode != null) {
                        actionMode.finish();
                    }
                }
            }
        });

        adapter.setTracker(tracker);

        viewModel.getSelectedJournals().observe(this, list -> {
            if (list.isEmpty()) {
                tracker.clearSelection();
            }
        });
    }
}

JournalAdapter.java

public class JournalAdapter extends ListAdapter<Journal, JournalAdapter.JournalViewHolder> {
    private static final DiffUtil.ItemCallback<Journal> DIFF_CALLBACK = new DiffUtil.ItemCallback<Journal>() {
        @Override
        public boolean areItemsTheSame(@NonNull Journal oldItem, @NonNull Journal newItem) {
            return oldItem.getId() == newItem.getId();
        }

        @Override
        public boolean areContentsTheSame(@NonNull Journal oldItem, @NonNull Journal newItem) {
            return oldItem.equals(newItem);
        }
    };

    private OnSelectionChangedListener selectionChangedListener;

    private SelectionTracker<Long> tracker;

    public JournalAdapter() {
        super(DIFF_CALLBACK);
        setHasStableIds(true);
    }

    @NonNull
    public JournalViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.card_journal, parent, false);

        return new JournalViewHolder(itemView, itemClickListener, selectionChangedListener);
    }

    public void setOnSelectionChangedListener(OnSelectionChangedListener listener) {
        selectionChangedListener = listener;
    }

    public void setTracker(SelectionTracker<Long> tracker) {
        this.tracker = tracker;
    }

    @Override
    public void onBindViewHolder(@NonNull JournalViewHolder holder, int position) {
        Journal current = getItem(position);

        holder.textViewTitle.setText(current.getTitle());

        if (tracker != null) {
            if (tracker.hasSelection()) {
                holder.checkBox.setChecked(tracker.isSelected(getItemId(holder.getAdapterPosition())));
            }
        }
    }

    @Override
    public long getItemId(int position) {
        return getItem(position).getId();
    }

    public List<Long> getAllIds() {
        List<Long> list = new LinkedList<>();

        for (int i = 0; i < getItemCount(); i++) {
            list.add(getItemId(i));
        }

        return list;
    }

    public interface OnSelectionChangedListener {
        void onSelectionChanged(Journal journal, boolean isChecked);
    }

    class JournalViewHolder extends RecyclerView.ViewHolder {

        private TextView textViewTitle;
        private CheckBox checkBox;

        JournalViewHolder(@NonNull View itemView, final OnSelectionChangedListener scListener) {
            super(itemView);

            textViewTitle = itemView.findViewById(R.id.title);
            checkBox = itemView.findViewById(R.id.checkBox);

            checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
                int position = getAdapterPosition();
                if (buttonView != null && position != RecyclerView.NO_POSITION) {
                    scListener.onSelectionChanged(getItem(position), isChecked);
                }
            });

            tracker.addObserver(new SelectionTracker.SelectionObserver() {
                @Override
                public void onSelectionChanged() {
                    if (tracker.hasSelection()) {
                        checkBox.setVisibility(View.VISIBLE);
                    } else if (!tracker.hasSelection()) {
                        checkBox.setChecked(false);
                        checkBox.setVisibility(View.GONE);
                    }
                }
            });

        }
    }
}

JournalDetailsLookup.java

public final class JournalDetailsLookup extends ItemDetailsLookup<Long> {

    private final RecyclerView mRecyclerView;

    public JournalDetailsLookup(RecyclerView mRecyclerView) {
        this.mRecyclerView = mRecyclerView;
    }

    @Nullable
    @Override
    public ItemDetails<Long> getItemDetails(@NonNull MotionEvent e) {
        View view = mRecyclerView.findChildViewUnder(e.getX(), e.getY());

        if (view != null) {
            RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(view);
            if (holder instanceof JournalAdapter.JournalViewHolder) {
                final JournalAdapter.JournalViewHolder journalHolder = (JournalAdapter.JournalViewHolder) holder;

                return new ItemDetails<Long>() {
                    @Override
                    public int getPosition() {
                        return journalHolder.getAdapterPosition();
                    }

                    @NonNull
                    @Override
                    public Long getSelectionKey() {
                        return journalHolder.getItemId();
                    }
                };
            }
        }
        return null;
    }

}

JournalKeyProvider.java

public class JournalKeyProvider extends ItemKeyProvider<Long> {

    private JournalAdapter adapter;
    private RecyclerView recyclerView;

    public JournalKeyProvider(JournalAdapter adapter, RecyclerView recyclerView) {
        super(SCOPE_MAPPED);
        this.adapter = adapter;
        this.recyclerView = recyclerView;
    }

    @Nullable
    @Override
    public Long getKey(int position) {
        return adapter.getItemId(position);
    }

    @Override
    public int getPosition(@NonNull Long key) {
        RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForItemId(key);
        return viewHolder == null ? RecyclerView.NO_POSITION : viewHolder.getLayoutPosition();
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_coordinator_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:scrollbars="vertical"
        tools:listitem="@layout/card_journal" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

card_journal.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:layout_marginStart="4dp"
    android:layout_marginEnd="4dp"
    android:clickable="true"
    android:focusable="true"
    android:foreground="?android:attr/selectableItemBackground"
    app:cardCornerRadius="6dp"
    app:cardElevation="0dp"
    app:contentPadding="16dp">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="start|center_vertical"
        android:textAppearance="@style/TextAppearance.AppCompat.Large" />

    <CheckBox
        android:id="@+id/checkBox"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="end|center_vertical"
        android:visibility="gone"
        android:clickable="false" />


</androidx.cardview.widget.CardView>

Пример поведения программы, от которого я хочу избавиться (YouTube)


Ответы (0 шт):