简体   繁体   English

使用自定义添加的视图(例如对于空列表)时的Android DiffUtil不一致异常

[英]Android DiffUtil inconsistency exceptions when using custom added views like i.e. for empty lists

I need help with the DiffUtil for RecyclerView Adapter. 我需要有关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. 我制作了一个自定义适配器,可以添加自定义视图,例如加载视图或空视图等。在不使用DiffUtil的情况下一切正常,但是当我使用它时,有时会检测到不一致情况。 I think the problem lies within the getItemCount() method but I'm not sure. 我认为问题出在getItemCount()方法之内,但我不确定。 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: 这是我的DiffUtil适配器类:

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. 从我从stacktrace中读取的内容来看,适配器或RecyclerView似乎对最后一个项目接收了错误的位置。 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? 它总是尝试为位置== itemCount的最后一个项目获取Viewholder,当然,这是不起作用的,因为位置从0开始。所以为什么它不为最后一个项目接收位置==(itemCount-1) ?

Solved it! 解决了! The problem occurred when my empty view was visible and I inserted a new not empty list. 当我的空视图可见并且我插入了一个新的非空列表时,发生了问题。 I forgot to call notifyItemChanged(0) before I inserted the new list. 在插入新列表之前,我忘记了调用notifyItemChanged(0)。 I changed the onUpdateStart() method of my Update callback class and everything is working fine. 我更改了Update回调类的onUpdateStart()方法,一切正常。 Here is the change 这是改变

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用DiffUtil和自定义适配器会导致检测到不一致 - Using DiffUtil and custom adapter cause Inconsistency detected 在Android中,如何使用相对布局在另一个视图下显示视图(例如textview),但仅在有文本的情况下-即不为空 - How, in Android, using relative layout, display a view (example textview) under another one, but only if has text - i.e. not empty java.lang.IndexOutOfBoundsException:检测到不一致。 使用 DiffUtil 时视图持有者适配器无效 - java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter when using DiffUtil 如何将我的活动显示在Android联系人菜单中,例如facebook app? - How can I have my activity display in android contact menu, like i.e. facebook app? 如何在 Android Studio 的所有页面上保存功能(即 SFX 或音乐等设置)? - How to save a feature (i.e. setting like SFX or Music) across all pages in Android Studio? 完成任何形式的退出操作后即使用HOME,SWITCH和BACK按钮完全关闭Android应用程序 - Complete shutting down of Android app when any form of exiting is done i.e. using HOME, SWITCH and BACK button 在 android jetpack compose 中使用伴奏权限时如何检测用户是否撤销权限即两次拒绝权限 - In android jetpack compose when using accompanist permission how to detect if user revoked permission i.e. denied permission twice android 使用 DiffUtil 时更改 RecyclerView 中奇数行的背景颜色 - android Change background color of odd rows in RecyclerView when using DiffUtil 当我单击按钮时重新转换(例如,从当前活动到自己的活动,如“意图”) - Re-Transition when I click on button (e.g./i.e. Like Intent from current Activity to self Activity) 在Android应用程序中,'ListActivity'可以作为主要活动吗?即打开应用程序时可以启动“ListActivity”吗? - In an Android application can a 'ListActivity' be the main activity? i.e. can a 'ListActivity' start when I open an app?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM