luizgrp / SectionedRecyclerViewAdapter

An Adapter that allows a RecyclerView to be split into Sections with headers and/or footers. Each Section can have its state controlled individually.
MIT License
1.68k stars 372 forks source link

java.lang.IndexOutOfBoundsException during sectionedRecyclerViewAdapter.getSectionItemViewType #133

Closed yccheok closed 3 years ago

yccheok commented 6 years ago

Describe the bug Randomly and rarely, we encounter strange java.lang.IndexOutOfBoundsException during sectionedRecyclerViewAdapter.getSectionItemViewType

Expected behavior No random crash.

Screenshots If applicable, add screenshots to help explain your problem.

SectionedRecyclerViewAdapter library:

Additional context Here's the online crash report.

java.lang.IndexOutOfBoundsException: 

  at io.github.luizgrp.sectionedrecyclerviewadapter.SectionedRecyclerViewAdapter.getItemViewType (SectionedRecyclerViewAdapter.java:378)

  at io.github.luizgrp.sectionedrecyclerviewadapter.SectionedRecyclerViewAdapter.getSectionItemViewType (SectionedRecyclerViewAdapter.java:396)

  at com.yocto.wenote.trash.TrashFragment$3.getSpanSize (TrashFragment.java:544)

  at android.support.v7.widget.GridLayoutManager$SpanSizeLookup.getSpanGroupIndex (GridLayoutManager.java:971)

  at android.support.v7.widget.GridLayoutManager.getSpanGroupIndex (GridLayoutManager.java:451)

  at android.support.v7.widget.GridLayoutManager.getRowCountForAccessibility (GridLayoutManager.java:125)

  at android.support.v7.widget.RecyclerView$LayoutManager.onInitializeAccessibilityNodeInfo (RecyclerView.java:9991)

  at android.support.v7.widget.RecyclerView$LayoutManager.onInitializeAccessibilityNodeInfo (RecyclerView.java:9951)

  at android.support.v7.widget.RecyclerViewAccessibilityDelegate.onInitializeAccessibilityNodeInfo (RecyclerViewAccessibilityDelegate.java:61)

  at android.support.v4.view.AccessibilityDelegateCompat$AccessibilityDelegateApi16Impl$1.onInitializeAccessibilityNodeInfo (AccessibilityDelegateCompat.java:126)

  at android.view.View.onInitializeAccessibilityNodeInfo (View.java:7308)

  at android.view.View.createAccessibilityNodeInfoInternal (View.java:7269)

  at android.view.View$AccessibilityDelegate.createAccessibilityNodeInfo (View.java:26024)

  at android.view.View.createAccessibilityNodeInfo (View.java:7252)

  at android.view.accessibility.AccessibilityRecord.setSource (AccessibilityRecord.java:146)

  at android.view.accessibility.AccessibilityRecord.setSource (AccessibilityRecord.java:119)

  at android.view.View.onInitializeAccessibilityEventInternal (View.java:7206)

  at android.view.View$AccessibilityDelegate.onInitializeAccessibilityEvent (View.java:25907)

  at android.support.v4.view.AccessibilityDelegateCompat.onInitializeAccessibilityEvent (AccessibilityDelegateCompat.java:309)

  at android.support.v7.widget.RecyclerViewAccessibilityDelegate.onInitializeAccessibilityEvent (RecyclerViewAccessibilityDelegate.java:67)

  at android.support.v4.view.AccessibilityDelegateCompat$AccessibilityDelegateApi16Impl$1.onInitializeAccessibilityEvent (AccessibilityDelegateCompat.java:120)

  at android.view.View.onInitializeAccessibilityEvent (View.java:7192)

  at android.view.View.sendAccessibilityEventUncheckedInternal (View.java:7056)

  at android.view.View$AccessibilityDelegate.sendAccessibilityEventUnchecked (View.java:25846)

  at android.support.v4.view.AccessibilityDelegateCompat.sendAccessibilityEventUnchecked (AccessibilityDelegateCompat.java:248)

  at android.support.v4.view.AccessibilityDelegateCompat$AccessibilityDelegateApi16Impl$1.sendAccessibilityEventUnchecked (AccessibilityDelegateCompat.java:148)

  at android.view.View.sendAccessibilityEventUnchecked (View.java:7039)

  at android.support.v7.widget.RecyclerView.sendAccessibilityEventUnchecked (RecyclerView.java:3421)

  at android.view.ViewRootImpl$SendWindowContentChangedAccessibilityEvent.run (ViewRootImpl.java:8020)

  at android.view.ViewRootImpl$SendWindowContentChangedAccessibilityEvent.runOrPost (ViewRootImpl.java:8046)

  at android.view.ViewRootImpl.postSendWindowContentChangedCallback (ViewRootImpl.java:7122)

  at android.view.ViewRootImpl.notifySubtreeAccessibilityStateChanged (ViewRootImpl.java:7297)

  at android.view.ViewGroup.notifySubtreeAccessibilityStateChanged (ViewGroup.java:3574)

  at android.view.ViewGroup.notifySubtreeAccessibilityStateChanged (ViewGroup.java:3574)

  at android.view.ViewGroup.notifySubtreeAccessibilityStateChanged (ViewGroup.java:3574)

  at android.view.ViewGroup.notifySubtreeAccessibilityStateChanged (ViewGroup.java:3574)

  at android.view.ViewGroup.notifySubtreeAccessibilityStateChanged (ViewGroup.java:3574)

  at android.view.ViewGroup.notifySubtreeAccessibilityStateChanged (ViewGroup.java:3574)

  at android.view.ViewGroup.notifySubtreeAccessibilityStateChanged (ViewGroup.java:3574)

  at android.view.ViewGroup.notifySubtreeAccessibilityStateChanged (ViewGroup.java:3574)

  at android.view.ViewGroup.notifySubtreeAccessibilityStateChanged (ViewGroup.java:3574)

  at android.view.View.notifySubtreeAccessibilityStateChangedIfNeeded (View.java:11196)

  at android.view.ViewGroup.notifySubtreeAccessibilityStateChangedIfNeeded (ViewGroup.java:3598)

  at android.view.ViewGroup.removeViewInternal (ViewGroup.java:5251)

  at android.view.ViewGroup.removeViewAt (ViewGroup.java:5174)

  at android.support.v7.widget.RecyclerView$5.removeViewAt (RecyclerView.java:804)

  at android.support.v7.widget.ChildHelper.removeViewAt (ChildHelper.java:168)

  at android.support.v7.widget.RecyclerView$LayoutManager.removeViewAt (RecyclerView.java:8260)

  at android.support.v7.widget.RecyclerView$LayoutManager.removeAndRecycleViewAt (RecyclerView.java:8532)

  at android.support.v7.widget.RecyclerView$LayoutManager.removeAndRecycleAllViews (RecyclerView.java:9944)

  at android.support.v7.widget.RecyclerView.setLayoutManager (RecyclerView.java:1245)

  at com.yocto.wenote.trash.TrashFragment.refreshLayout (TrashFragment.java:508)

  at com.yocto.wenote.trash.TrashFragment.onChanged (TrashFragment.java:630)

  at com.yocto.wenote.trash.TrashFragment.onChanged (TrashFragment.java:608)

  at com.yocto.wenote.trash.TrashFragment.access$200 (TrashFragment.java:75)

  at com.yocto.wenote.trash.TrashFragment.access$602 (TrashFragment.java:75)

  at com.yocto.wenote.trash.TrashFragment.access$1200 (TrashFragment.java:75)

  at com.yocto.wenote.trash.TrashFragment.access$1600 (TrashFragment.java:75)

  at com.yocto.wenote.trash.TrashFragment.access$1700 (TrashFragment.java:75)

  at com.yocto.wenote.trash.TrashFragment.access$1800 (TrashFragment.java:75)

  at com.yocto.wenote.trash.TrashFragment.access$1900 (TrashFragment.java:75)

  at com.yocto.wenote.trash.TrashFragment$NotesObserver.onChanged (TrashFragment.java:277)

  at com.yocto.wenote.trash.TrashFragment$NotesObserver.onChanged (TrashFragment.java:274)

  at android.arch.lifecycle.LiveData.a (LiveData.java:109)

  at android.arch.lifecycle.LiveData.b (LiveData.java:126)

  at android.arch.lifecycle.LiveData.b (LiveData.java:282)

  at android.arch.lifecycle.LiveData$1.run (LiveData.java:87)

  at android.os.Handler.handleCallback (Handler.java:789)

  at android.os.Handler.dispatchMessage (Handler.java:98)

  at android.os.Looper.loop (Looper.java:164)

  at android.app.ActivityThread.main (ActivityThread.java:6592)

  at java.lang.reflect.Method.invoke (Method.java)

  at com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:240)

  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:769)

MVCE

    if (!(GridLayoutManager.class.equals(getLayoutManagerClass())) || Utils.getGridSpanCount() != getCurrentSpanCount()) {
        GridLayoutManager gridLayoutManager = new GridLayoutManager(this.getContext(), Utils.getGridSpanCount());

        gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                switch(sectionedRecyclerViewAdapter.getSectionItemViewType(position)){      <--- java.lang.IndexOutOfBoundsException:  ????
                    case SectionedRecyclerViewAdapter.VIEW_TYPE_ITEM_LOADED:
                        return 1;
                    default:
                        return gridLayoutManager.getSpanCount();
                }
            }
        });

        this.recyclerView.setLayoutManager(gridLayoutManager);
    }
rickie commented 5 years ago

This is a serious issue and should be fixed. I'm having the same issues..

jenlai1345 commented 5 years ago

I'm having the same issue. Is there a way to pre-check the array size?

luizgrp commented 5 years ago

Hi @yccheok, in order to return the correct type,getItemViewType depends a lot on Section properties (isVisible, hasHeader, hasFooter, getState, getSectionItemsTotal..). If those properties are changed during the execution of getItemViewType it might cause IndexOutOfBoundsException.

Since by design the adapter has a map of mutable Sections, and Section is implemented by the user, it's out of the adapter's control to make sure that they are not changed while they are iterated on. So if you want to prevent this issue, you would have to make sure that the Sections properties are not changed when getItemViewType is running.

I'm afraid I don't think we cannot make the library prevent this issue without a big change on the design. The Section class would have to provide a Collection to hold its items instead of letting the user to use whatever collection they want to. The Section class would have to also provide methods to add/remove/update its items, in other words, the library would have control on every item that it's added to the adapter.

I'm marking as help wanted for now, but if no further investigation is done, I will mark it as "wontfix".

yccheok commented 5 years ago

Hem...

Thanks for the tip. Your hypothesis does make sense. However, when I re-exam my code, which perform mutate operation on Sections, all of them are performed within UI thread. Hence, if the Section mutate operations are truly performed within UI thread, it seems like modifying Section's properties, and calling getItemViewType shouldn't happen simultaneously.

Unless, the value passed from Android system int position is wrong...

Kazi-paywell commented 5 years ago

I'm having the same issues..