0

I need help with the DiffUtil for RecyclerView Adapter. I have made a Custom Adapter with the possibility to add custom Views like a loading view or empty view etc. Everything works fine without using DiffUtil but when I use it I have sometimes an inconsistency detected exception. I think the problem lies within the getItemCount() method but I'm not sure. If someone could give me an advise it would be very helpful. Here is the code I'm using.

This is my DiffUtil Adapter Class:

public abstract class DiffRecyclerViewAdapter<T extends DiffComparable<T>> extends BaseRecyclerViewAdapter<T> {

private DifferenceCalculator<T> mDiffCalculator = new DifferenceCalculator<>(
        new DifferenceCalculator.DiffCallback<>(getPayloadCallback()),
        new UpdateCallback(this),
        TaskExecutor.getInstance().getExecutor());

public DiffRecyclerViewAdapter(Context mContext, List<T> itemList) {
    super(mContext, itemList);
    setDataIsLoading(true);
}

public DiffRecyclerViewAdapter(Context mContext, List<T> itemList, int itemsLeftToLoadMore, LoadMoreDataCallback listener) {
    super(mContext, itemList, itemsLeftToLoadMore, listener);
    setDataIsLoading(true);
}

public void setItemList(List<T> list) {
    if (isLoading()) {
        setDataIsLoading(false, true);
    }
    mDiffCalculator.calculateDifference(list);
}

@Override
public int getItemCount() {
    int superCount = super.getItemCount();
    log("getItemCount() called with count=" + mDiffCalculator.getCurrentList().size());
    return superCount > mDiffCalculator.getCurrentList().size() ? superCount : mDiffCalculator.getCurrentList().size();
}

@Override
public List<T> getList() {
    return mDiffCalculator.getCurrentList();
}

protected abstract PayloadCallback<T> getPayloadCallback();

protected void onNewList() {
}

protected void onItemsInserted(int position, int count) {
    log("onItemsInserted(position=" + position + ", count=" + count + ")");
    notifyItemRangeInserted(position, count);
}

@SuppressWarnings("WeakerAccess")
protected void onItemsRemoved(int position, int count) {
    log("onItemsRemoved(position=" + position + ", count=" + count + ")");
    notifyItemRangeRemoved(position, count);
}

@SuppressWarnings("WeakerAccess")
protected void onItemMoved(int fromPosition, int toPosition) {
    log("onItemMoved(fromPosition=" + fromPosition + ", toPosition=" + toPosition + ")");
    notifyItemMoved(fromPosition, toPosition);
}

@SuppressWarnings("WeakerAccess")
protected void onItemsChanged(int position, int count, @Nullable Object payload) {
    log("onItemsChanged(position=" + position + ", count=" + count + ", payload=" + payload + ")");
    notifyItemRangeChanged(position, count, payload);
}

public void log(String msg) {
    if (IN_DEBUG_MODE) {
        Log.i(getClass().getSimpleName(), msg);
    }
}

public static class UpdateCallback implements ListUpdateCallback {

    private DiffRecyclerViewAdapter adapter;

    private UpdateCallback(DiffRecyclerViewAdapter adapter) {
        this.adapter = adapter;
    }

    @SuppressWarnings("WeakerAccess")
    public void onUpdateStart() {
        Log.w(getClass().getSimpleName(), "onUpdateStart()");
        adapter.setListUpdateInProgress(true);
    }

    @SuppressWarnings("WeakerAccess")
    public void onUpdateFinish() {
        Log.w(getClass().getSimpleName(), "onUpdateFinish()");
        adapter.setListUpdateInProgress(false);
    }

    @SuppressWarnings("WeakerAccess")
    public void onNewList() {
        adapter.onNewList();
    }

    @Override
    public void onInserted(int position, int count) {
        adapter.onItemsInserted(position, count);
    }

    @Override
    public void onRemoved(int position, int count) {
        adapter.onItemsRemoved(position, count);
    }

    @Override
    public void onMoved(int fromPosition, int toPosition) {
        adapter.onItemMoved(fromPosition, toPosition);
    }

    @Override
    public void onChanged(int position, int count, @Nullable Object payload) {
        adapter.onItemsChanged(position, count, payload);
    }
}

public interface PayloadCallback<T> {
    @Nullable
    Object getChangePayload(T oldItem, T newItem);
}

}

And this is my Difference Calculator Class:

public class DifferenceCalculator<T extends DiffComparable<T>> {

private MainThreadExecutor mMainThreadExecutor;
private Executor mBackgroundThreadExecutor;
private DiffCallback<T> mDiffUtilCallback;
private DiffRecyclerViewAdapter.UpdateCallback mUpdateCallback;
private List<T> mList;
private List<T> mReadOnlyList = Collections.emptyList();
private int mMaxScheduledGeneration;

@SuppressWarnings("unused")
public DifferenceCalculator(DiffCallback<T> diffCallback, DiffRecyclerViewAdapter.UpdateCallback callback) {
    this(diffCallback, callback, null);
}

@SuppressWarnings("WeakerAccess")
public DifferenceCalculator(DiffCallback<T> diffCallback,
                            DiffRecyclerViewAdapter.UpdateCallback callback,
                            Executor backgroundThreadExecutor) {
    mDiffUtilCallback = diffCallback;
    mUpdateCallback = callback;
    mMainThreadExecutor = new MainThreadExecutor();
    mBackgroundThreadExecutor = backgroundThreadExecutor != null ? backgroundThreadExecutor :
            new ThreadPoolExecutor(
                    2,
                    2,
                    30,
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>()
            );
}

@SuppressWarnings("WeakerAccess")
@NonNull
public List<T> getCurrentList() {
    return mReadOnlyList;
}

@SuppressWarnings("WeakerAccess")
public void calculateDifference(@Nullable final List<T> newList) {
    final int runGeneration = ++mMaxScheduledGeneration;
    log("calculating difference for process=" + runGeneration);
    mUpdateCallback.onUpdateStart();
    if (newList == mList) {
        mUpdateCallback.onUpdateFinish();
        log("abandoned because no change!");
        return;
    }
    if (newList == null) {
        int countRemoved = mList.size();
        mList = null;
        mReadOnlyList = Collections.emptyList();
        mUpdateCallback.onRemoved(0, countRemoved);
        mUpdateCallback.onUpdateFinish();
        log("New List is null!");
        return;
    }
    if (mList == null) {
        mList = newList;
        mReadOnlyList = Collections.unmodifiableList(newList);
        mUpdateCallback.onInserted(0, newList.size());
        mUpdateCallback.onNewList();
        mUpdateCallback.onUpdateFinish();
        log("Complete new List arrived.");
        return;
    }
    final List<T> oldList = mList;
    mBackgroundThreadExecutor.execute(() -> {
        final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
            @Override
            public int getOldListSize() {
                return oldList.size();
            }

            @Override
            public int getNewListSize() {
                return newList.size();
            }

            @Override
            public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
                T oldItem = oldList.get(oldItemPosition);
                T newItem = newList.get(newItemPosition);
                if (oldItem != null && newItem != null) {
                    return mDiffUtilCallback.areItemsTheSame(oldItem, newItem);
                }
                return oldItem == null && newItem == null;
            }

            @Override
            public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
                T oldItem = oldList.get(oldItemPosition);
                T newItem = newList.get(newItemPosition);
                if (oldItem != null && newItem != null) {
                    return mDiffUtilCallback.areContentsTheSame(oldItem, newItem);
                }
                if (oldItem == null && newItem == null) {
                    return true;
                }
                throw new AssertionError();
            }

            @Nullable
            @Override
            public Object getChangePayload(int oldItemPosition, int newItemPosition) {
                T oldItem = oldList.get(oldItemPosition);
                T newItem = newList.get(newItemPosition);
                if (oldItem != null && newItem != null) {
                    return mDiffUtilCallback.getChangePayload(oldItem, newItem);
                }
                throw new AssertionError();
            }
        });
        mMainThreadExecutor.execute(() -> {
            if (mMaxScheduledGeneration == runGeneration) {
                dispatchResult(newList, result);
            } else {
                mUpdateCallback.onUpdateFinish();
                log("result not dispatched because other pending calculations in queue!");
            }
        });
    });
}

private void dispatchResult(@NonNull List<T> newList, @NonNull DiffUtil.DiffResult diffResult) {
    log("dispatching result");
    mList = newList;
    mReadOnlyList = Collections.unmodifiableList(newList);
    diffResult.dispatchUpdatesTo(mUpdateCallback);
    mUpdateCallback.onUpdateFinish();
}

private static class MainThreadExecutor implements Executor {

    private Handler handler = new Handler(Looper.getMainLooper());

    @Override
    public void execute(Runnable command) {
        handler.post(command);
    }
}

public static class DiffCallback<T extends DiffComparable<T>> {

    private DiffRecyclerViewAdapter.PayloadCallback<T> callback;

    public DiffCallback(DiffRecyclerViewAdapter.PayloadCallback<T> callback){
        this.callback = callback;
    }

    boolean areItemsTheSame(T oldItem, T newItem) {
        return oldItem.isItemTheSame(newItem);
    }

    boolean areContentsTheSame(T oldItem, T newItem) {
        return oldItem.isContentTheSame(newItem);
    }

    @Nullable
    public Object getChangePayload(T oldItem, T newItem) {
        if(callback != null){
            return callback.getChangePayload(oldItem, newItem);
        }
        return null;
    }

}

private void log(String msg){
    if(IN_DEBUG_MODE){
        Log.w(getClass().getSimpleName(), msg);
    }
}
}

From what I can read from the stacktrace it seems that the adapter or RecyclerView receives a wrong position for the last item. It is always trying to get a Viewholder for the last item with position == itemCount and of course this is not working because the positions start at 0. So why doesn't it receive position == (itemCount - 1) for the last item?

4

1 回答 1

1

解决了!当我的空视图可见并且我插入了一个新的非空列表时出现了问题。在插入新列表之前,我忘记调用 notifyItemChanged(0)。我更改了 Update 回调类的 onUpdateStart() 方法,一切正常。这里是变化

 @SuppressWarnings("WeakerAccess")
 public void onUpdateStart() {
    Log.w(getClass().getSimpleName(), "onUpdateStart()");
    adapter.setListUpdateInProgress(true);
    if(adapter.isEmptyViewVisible()){
         adapter.notifyItemChanged(0);
    }
  }
于 2019-03-11T17:10:11.320 回答