Indexoutofbounexception In Search Adapter Onbind Method?
Solution 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:
@OverridepublicFiltergetFilter() {
// WRONG: Return new instance of Filter every time getFilter() is calledreturnnewFilter() {
@OverrideprotectedFilterResultsperformFiltering(CharSequence charSequence) {
// Perform filtering...return anything;
}
@OverrideprotectedvoidpublishResults(CharSequence charSequence, FilterResults filterResults) {
// Update and Notify adapter...
}
};
}
Instead replace with:
private final Filter filter = newFilter() {
@OverrideprotectedFilterResultsperformFiltering(CharSequence charSequence) {
// Perform filtering...return anything;
}
@OverrideprotectedvoidpublishResults(CharSequence charSequence, FilterResults filterResults) {
// Update and Notify adapter...
}
};
@OverridepublicFiltergetFilter() {
// CORRECT: Always use the same Filter instance.returnthis.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:
@OverrideprotectedFilterResultsperformFiltering(CharSequence charSequence) {
// Here is Background Thread, never alter adapter list in this methodString charString = charSequence.toString();
List<RegisterGuestList.Guest> filteredList = newArrayList<>();
if (charString.isEmpty()) {
filteredList.addAll(mSearchGuestListResponseList);
} else {
... // fill filteredList as you did previously
}
FilterResults filterResults = newFilterResults();
filterResults.values = filteredList;
filterResults.count = filteredList.size(); // count is optionalreturn filterResults;
}
@OverrideprotectedvoidpublishResults(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:
@OverridepublicbooleanonQueryTextChange(String newText) {
mSearchGuestListAdapter.getFilter().filter(newText, newFilter.FilterListener() {
@OverridepublicvoidonFilterComplete(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();returnfalse;
}
Solution 2:
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
**/privatestaticclassNpaLinearLayoutManagerextendsLinearLayoutManager{
/**
* 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
publicboolean supportsPredictiveItemAnimations() {
returnfalse;
}
// .....
}
Solution 3:
Replace all occurrences of mSearchGuestListResponseList
in SearchGuestListAdapter#getItemCount()
,
and in SearchGuestListAdapter#getItemViewType()
with mSearchGuestListResponseListFiltered
;
Post a Comment for "Indexoutofbounexception In Search Adapter Onbind Method?"