ClassCastException after updateDataSet with StickyHeaders #696
Description
I'm having an issue when I update the adapter's data set (via updateDataSet()
), but only when sticky headers are enabled, AND a new header is introduced as part of the update, AND a scroll occurs, such that previously sticky header's position changes. I'm not using expandable headers. In addition, it's very hard to reproduce. It can take hours, but eventually it will occur. Here's the stack:
java.lang.ClassCastException: com.myapp.MyHeaderItem$ViewHolder cannot be cast to com.myapp.MySectionedItem$ViewHolder
at com.myapp.MySectionedItem.unbindViewHolder(AbstractSegmentItem.java:61)
at eu.davidea.flexibleadapter.FlexibleAdapter.onViewRecycled(FlexibleAdapter.java:1816)
at eu.davidea.flexibleadapter.helpers.StickyHeaderHelper.swapHeader(StickyHeaderHelper.java:245)
at eu.davidea.flexibleadapter.helpers.StickyHeaderHelper.updateHeader(StickyHeaderHelper.java:162)
at eu.davidea.flexibleadapter.helpers.StickyHeaderHelper.updateOrClearHeader(StickyHeaderHelper.java:138)
at eu.davidea.flexibleadapter.helpers.StickyHeaderHelper.onScrolled(StickyHeaderHelper.java:64)
at androidx.recyclerview.widget.RecyclerView.dispatchOnScrolled(RecyclerView.java:4961)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep3(RecyclerView.java:4021)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3652)
at androidx.recyclerview.widget.RecyclerView.consumePendingUpdateOperations(RecyclerView.java:1888)
at androidx.recyclerview.widget.RecyclerView$1.run(RecyclerView.java:407)
...
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
at android.view.Choreographer.doCallbacks(Choreographer.java:580)
at android.view.Choreographer.doFrame(Choreographer.java:549)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5343)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:905)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:700)
As you can see, when the StickyHeaderHelper
manually recycles its sticky header (normally via its scroll handler, as it scrolls out of the view), it thinks its item is a MySectionedItem
instance (i.e. a subclass of AbstractSectionableItem
, not AbstractHeaderItem
. So the ViewHolder
passed to AbstractSectionableItem#unbindViewHolder()
is that of the header.
As far as I can tell (I'm in no way sure), this is because the StickyHeaderHelper
keeps a reference to the ViewHolder
for the stuck item, so when the data set is updated, and the time comes to swap out them item (i.e. recycle it), and it requests its position in the adapter (via FlexibleAdapter#onViewRecycled()
's call to RecyclerView.ViewHolder#getAdapterPosition()
) it receives the position it was at before the update, which is still a valid index, but no longer referencing the header's actual position in the new data set.
I should also add that my data set is unusual in that its updates tend to be at the head of the list, so new headers tend to be created at index 0, thus offsetting all items after it. If headers were to be created at the end of the list it would be less likely to occur.
As I say, I'm not 100% sure my explanation is correct, but it's the best I can think of. It might also be related to FlexibleAdapter.AdapterDataObserver#updateStickyHeader()
's delayed call to StickyHeaderHelper#updateOrClearHeader()
, which might explain why it doesn't happen every time.
I've checked that my item layout ids are unique, and that Object#equals
works OK. I think it might be related to issues #568 and #575. Any help (or just a workaround) would be greatly appreciated.