问题
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 ViewHolder
s 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