IndexOutofBounexception in search adapter onbind method?

吃可爱长大的小学妹 提交于 2019-12-10 23:09:39

问题


I keep getting IndexOutofBoundsException from my onBind method in search adapter as soon as I start to search /filter my recyclerview list results. Here's the exception :

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 0(offset:0).state:25 android.support.v7.widget.RecyclerView{3247b3d VFED..... ......ID 0,0-1440,5550 #7f090216 app:id/search_guests_recycler_view}, adapter:com.myapp.ui.registervisitor.searchGuests.SearchGuestListAdapter@2433f32, layout:android.support.v7.widget.LinearLayoutManager@68f5483, context:com.myapp.ui.registervisitor.searchGuests.SearchGuestActivity@843713
        at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5923)
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
        at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
        at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
        at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
        at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
        at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3924)
        at android.support.v7.widget.RecyclerView.onMeasure(RecyclerView.java:3336)
        at android.view.View.measure(View.java:24545)
        at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:735)
        at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:481)
        at android.view.View.measure(View.java:24545)
        at android.widget.ScrollView.measureChildWithMargins(ScrollView.java:1414)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.widget.ScrollView.onMeasure(ScrollView.java:452)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.support.design.widget.CoordinatorLayout.onMeasureChild(CoordinatorLayout.java:733)
        at android.support.design.widget.CoordinatorLayout.onMeasure(CoordinatorLayout.java:805)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.support.design.widget.CoordinatorLayout.onMeasureChild(CoordinatorLayout.java:733)
        at android.support.design.widget.CoordinatorLayout.onMeasure(CoordinatorLayout.java:805)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:24545)
        at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:735)
        at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:481)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:143)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:24545)

I tried a couple of articles online, but no luck. some of the articles that I tried are : RecyclerView Adapter onBind method and RecyclerView: Inconsistency detected. Invalid item position

My adapter code for this issue is at : https://pastebin.com/VxsWWMiS

and corresponding activity code for filtering :

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                mSearchGuestListAdapter.getFilter().filter(query);

                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                mSearchGuestListAdapter.getFilter().filter(newText);
                mSearchGuestListAdapter.notifyDataSetChanged();
                mSearchGuestListAdapter.setFilter(newText);

                if(mSearchGuestListAdapter.getItemCount() == 0){


                    String sourceString = "No match found for <b>" + newText + "</b> ";
                    mNoMatchTextView.setText(Html.fromHtml(sourceString));
                } else {
                    mEmptyRelativeLayout.setVisibility(View.GONE);
                    mRecyclerView.setVisibility(View.VISIBLE);
                }
                return false;
            }
        });

Happy to share any other details if needed. Any ideas how to resolve this?

Here's my entire activity code: https://pastebin.com/5qDN4yh9

This is the presenter class where the API is called : https://pastebin.com/YGiPGn8Z

This is the model class: https://pastebin.com/WCkPFnvU

This is a json sample : https://pastebin.com/82R1zBHP

Here's the xml code where the recyclerview is called : https://pastebin.com/Z3QxPkSL "search_guests_recycler_view"


回答1:


There are multiple considerations in your code that may cause that inconsistency in your adapter.

To understand how Filter class works you should note that, according to official documentation:

Filtering operations performed by calling filter(java.lang.CharSequence) or filter(java.lang.CharSequence, android.widget.Filter.FilterListener) are performed asynchronously. When these methods are called, a filtering request is posted in a request queue and processed later. Any call to one of these methods will cancel any previous non-executed filtering request.


1. Use a single instance of Filter

The Filter class performs a background task in which the performFiltering() method is called. To avoid overlapping filters while typing you should avoid using multiple instances of Filter. In your SearchGuestListAdapter class:

Incorrect:

@Override
public Filter getFilter() {
    // WRONG: Return new instance of Filter every time getFilter() is called
    return new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence charSequence) {
            // Perform filtering...
            return anything;
        }

        @Override
        protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
            // Update and Notify adapter...
        }
    };
}

Instead replace with:

private final Filter filter = new Filter() {
    @Override
    protected FilterResults performFiltering(CharSequence charSequence) {
        // Perform filtering...
        return anything;
    }

    @Override
    protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
        // Update and Notify adapter...
    }
};

@Override
public Filter getFilter() {
    // CORRECT: Always use the same Filter instance.
    return this.filter;
}

2. You should not modify adapter variables in performFiltering()

performFiltering() method is executed in a background thread. Here you can do a long job, read data from a database, even make a webservice synchronous request if you need it.

But, if you alter the adapter list here you will cause inconsistency between RecyclerView is showing on the screen and the contents of the adapter list. Instead you must build a temporary list that you will return using FilterResults.

Fix performFiltering() with the following structure:

@Override
protected FilterResults performFiltering(CharSequence charSequence) {
    // Here is Background Thread, never alter adapter list in this method
    String charString = charSequence.toString();
    List<RegisterGuestList.Guest> filteredList = new ArrayList<>();

    if (charString.isEmpty()) {
        filteredList.addAll(mSearchGuestListResponseList);
    } else {
        ... // fill filteredList as you did previously
    }

    FilterResults filterResults = new FilterResults();
    filterResults.values = filteredList;
    filterResults.count = filteredList.size(); // count is optional
    return filterResults;
}

@Override
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
    // Here is Main Thread, safe to update list and notify adapter
    mSearchGuestListResponseListFiltered = (ArrayList<RegisterGuestList.Guest>) filterResults.values;
    searchText = charSequence.toString();
    notifyDataSetChanged();
}

3. Use FilterListener to get notified when filter completed.

In your onQueryTextChange() of SearchGuestActivity, you want check adapter list size to show or hide a empty view. As Filter works in background thread you must perform filter call with a FilterListener to check empty list.

Remove your setFilter() method of SearchGuestListAdapter and Fix onQueryTextChange() of SearchGuestActivity with the following structure:

@Override
public boolean onQueryTextChange(String newText) {
    mSearchGuestListAdapter.getFilter().filter(newText, new Filter.FilterListener() {
        @Override
        public void onFilterComplete(int count) {
            if (count == 0) {
                // Show empty view...
            } else {
                // Show recyclerView...
            }
        }
    });

    // Don't call notifyDataSetChanged() or setFilter() here!
    // Adaper will notified by publishResults() method
    // mSearchGuestListAdapter.setFilter(newText);
    // mSearchGuestListAdapter.notifyDataSetChanged();

    return false;
}



回答2:


Replace all occurrences of mSearchGuestListResponseList
in SearchGuestListAdapter#getItemCount(),
and in SearchGuestListAdapter#getItemViewType()
with mSearchGuestListResponseListFiltered;




回答3:


The stack trace above, seems like doesn't related on your code implicitly. And issue caused by internal RecyclerView implementation of animations. Also described here.

1) Possible workarounds for it, just clearing active ViewHolders pool, which could be pending on animation. And could throw IndexOfBoundException. Call below methods, before any notifyDataSetChanged.

mRecyclerView.getRecycledViewPool().clear();
mAdapter.notifyDataSetChanged();

2) Make sure you don't have notifyItemRangeChanged or notifyItemAdded(and similar) methods in your adapters. Use notifyDataSetChanged. Of course you can avoid this, but you need to use single collection and debug correct position every time to use this features.

3) And finally as you can see, it's all about animation for your items and internal behavior for RecyclerView. To resolve it from another side, you could override LayoutManager and remove animation. This will ensure, there is no issue with positions within your implementation.

/**
 * Could be the same for Grid LayoutManager or Linear
 **/
private static class NpaLinearLayoutManager extends LinearLayoutManager {
    /**
     * Disable predictive animations. There is a bug in RecyclerView which causes views that
     * are being reloaded to pull invalid ViewHolders from the internal recycler stack if the
     * adapter size has decreased since the ViewHolder was recycled.
     */
    @Override
    public boolean supportsPredictiveItemAnimations() {
        return false;
    }
// .....
}


来源:https://stackoverflow.com/questions/58570090/indexoutofbounexception-in-search-adapter-onbind-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!