10

I have an RxJava2 Observable that takes two lists, calculate diff result for them and send this data to adapter. Adapter dispatch updates on Main Thread.

Code of dispatching in adapter:

 public void dispatchStreams(List<StreamV3> streams, @Nullable DiffUtil.DiffResult diffResult) {

    if (streams == null) return;

    streamsList.clear();
    streamsList.addAll(streams);

    if (diffResult != null) {
        diffResult.dispatchUpdatesTo(this);
    }
}

I've got 'Inconsistency detected. Invalid view holder adapter positionViewHolder' error sometime on some devices. And I can't figure out what's wrong with my code. Min SDK 21, Target SDK 26, RecyclerView version is 26.0.0. I know about workaround with extending LinearLayoutManager and silently catching this error but this is bad solution and I believe here should be better one.

Could anyone provide any help please?

Olexii Muraviov
  • 1,456
  • 1
  • 12
  • 36

2 Answers2

16

I found a solution for this issue in this answer

It seems that issue is caused by supportsPredictiveItemAnimations property on layout managers. When I set it to false no crash happens anymore.

public class LinearLayoutManagerWrapper extends LinearLayoutManager {

 public LinearLayoutManagerWrapper(Context context) {
   super(context);
 } 

 public LinearLayoutManagerWrapper(Context context, int orientation, boolean reverseLayout) {
   super(context, orientation, reverseLayout);
 }

 public LinearLayoutManagerWrapper(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
   super(context, attrs, defStyleAttr, defStyleRes);
 }

 @Override
 public boolean supportsPredictiveItemAnimations() {
   return false;
 }
}
Olexii Muraviov
  • 1,456
  • 1
  • 12
  • 36
2

Got into this issue while writing a Rx RV Diff to run in the background thread. Setting the supportsPredictiveItemAnimations to false is a workaround that prevents the crash but does not really solve the problem.

What was causing this exception on my case was the mutation of the data set in the background thread.

// Diff and update the RV asynchronously
fun update(list: List<D>) {
    Observable
        .create<Pair<List<D>, DiffUtil.DiffResult>> {
            // runs it asynchronous
            val result = DiffUtil.calculateDiff(
                diffCallback.apply {
                    newList = list
                }
            )

            it.onNext(Pair(diffCallback.newList, result))
            it.onComplete()
        }
        .takeUntil(destroyObservable) // observe the Lifecycle of the Frag
        .subscribeOn(Schedulers.computation()) // run it async
        .observeOn(AndroidSchedulers.mainThread()) // jump to the main thread
        .subscribe {
            // Set the new list
            dataSet = it.first.map { it }
            it.second.dispatchUpdatesTo(this@ListComponentAdapter)
        }
}
Juliano Moraes
  • 169
  • 2
  • 3
  • I'm having the same problem. I like your solution, but can you explain how you use "destroyObservable"? Maybe "update" could be an adapter method and return that observable so the View can manage it's disposal. – GuilhE Sep 07 '19 at 00:13