Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Renew StableIdKeyProvider cache and RecyclerView/SelectionTracker crash on new selection after item removed

Preparation:

RecyclerView with RecyclerView.Adapter binded to SQLite Cursor (via ContentProvider && Loader). RecyclerView and RecyclerView.Adapter linked with SelectionTracker as design suggests. SelectionTracker builded with StableIdKeyProvider.

On first step - delete an item:

  1. Select RecyclerViews's an item with a long press (cheers to SelectionTracker's SelectionObserver), draw Action Bar Context Menu, fire the delete action, do the SQL deletion task.
  2. After SQL deletion ends, do the Cursor Loader renewal with
    restartLoader call.
  3. onLoadFinished fired, new Cursor obtained, on
    RecyclerView.Adapter method notifyDataSetChanged called.
  4. RecyclerView.Adapter redraw RecyclerView content, and all is looks good.

On second step - do the selection of some other item. Crash:

java.lang.IllegalArgumentException
    at androidx.core.util.Preconditions.checkArgument(Preconditions.java:38)
    at androidx.recyclerview.selection.DefaultSelectionTracker.anchorRange(DefaultSelectionTracker.java:269)
    at androidx.recyclerview.selection.MotionInputHandler.selectItem(MotionInputHandler.java:60)
    at androidx.recyclerview.selection.TouchInputHandler.onLongPress(TouchInputHandler.java:132)
    at androidx.recyclerview.selection.GestureRouter.onLongPress(GestureRouter.java:96)
    at android.view.GestureDetector.dispatchLongPress(GestureDetector.java:779)
    at android.view.GestureDetector.access$200(GestureDetector.java:40)
    at android.view.GestureDetector$GestureHandler.handleMessage(GestureDetector.java:293)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:193)
    at android.app.ActivityThread.main(ActivityThread.java:6669)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

What I see on first step while deletion item in progress. While StableIdKeyProvider do internal job with onDetached ViewHolder item, it don't see previously assigned ViewHolder's position within an Adapter:

   void onDetached(@NonNull View view) {
        RecyclerView.ViewHolder holder = mRecyclerView.findContainingViewHolder(view);
        int position = holder.getAdapterPosition();
        long id = holder.getItemId();
        if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) {

int position here is RecyclerView.NO_POSITION

Thats why the RecyclerView crashes after - StableIdKeyProvider's cache contains old snapshot of ID's without deletion affected.

The question is - WHY? and HOW to renew the cache of StableIdKeyProvider?

Another note: While I read the RecyclerView code, I see this comment:

     * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the
     * next layout pass, the return value of this method will be {#NO_POSITION}.

I am not understood what exactly mean this words. Perhaps I faced with described situation - notifyDataSetChanged called in not appropriate time? Or I need to call it twice?

PS. Sorry for about literary description, there is a lot of complexity code

like image 721
A. Petrov Avatar asked Nov 28 '18 15:11

A. Petrov


2 Answers

I got it resolved by keeping the initialization and setting selectiontracker to the adapter at the very end.

    capturedThumbnailListAdapter = new CapturedThumbnailListAdapter(this);
    capturedThumbnailListAdapter.setCapturedThumbnailList(capturedThumbnailList);
    viewBinding.capturedImagesRv.setLayoutManager(new LinearLayoutManager(this,
            LinearLayoutManager.HORIZONTAL, true));
    viewBinding.capturedImagesRv.setAdapter(capturedThumbnailListAdapter);

    SelectionTracker<Long> tracker = new SelectionTracker.Builder<Long>("thumb_selection",
            viewBinding.capturedImagesRv, new StableIdKeyProvider(viewBinding.capturedImagesRv),
            new CapturedThumbnailListAdapter.ItemLookUp(viewBinding.capturedImagesRv),
            StorageStrategy.createLongStorage())
            .withSelectionPredicate(SelectionPredicates.createSelectAnything())
            .build();
    capturedThumbnailListAdapter.setSelectionTracker(tracker);
like image 119
Napolean Avatar answered Sep 19 '22 06:09

Napolean


My problem was solved by setting setHasStableIds(true) in Recycle view adapter and overriding getItemId, It seems that Tracker require both setHasStableIds(true) and overrindinggetItemId in adapter I got this error after setting stable Ids true without overriding getItemId

 init {
    setHasStableIds(true)
}
override fun getItemId(position: Int) = position.toLong()
override fun getItemViewType(position: Int) = position
like image 41
masokaya Avatar answered Sep 19 '22 06:09

masokaya