RecyclerView and java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder in Samsung devices

Multi tool use
RecyclerView and java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder in Samsung devices
I have a recycler view that works perfectly on all devices except Samsung. On Samsung, I'm get
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder
when I'm going back to the fragment with the recycler view from another activity.
Adapter code:
public class FeedRecyclerAdapter extends RecyclerView.Adapter<FeedRecyclerAdapter.MovieViewHolder> {
public static final String getUserPhoto = APIConstants.BASE_URL + APIConstants.PICTURE_PATH_SMALL;
Movie mMovies = null;
Context mContext = null;
Activity mActivity = null;
LinearLayoutManager mManager = null;
private Bus uiBus = null;
int mCountOfLikes = 0;
//Constructor
public FeedRecyclerAdapter(Movie movies, Context context, Activity activity,
LinearLayoutManager manager) {
mContext = context;
mActivity = activity;
mMovies = movies;
mManager = manager;
uiBus = BusProvider.getUIBusInstance();
}
public void setMoviesAndNotify(Movie movies, boolean movieIgnored) {
mMovies = movies;
int firstItem = mManager.findFirstVisibleItemPosition();
View firstItemView = mManager.findViewByPosition(firstItem);
int topOffset = firstItemView.getTop();
notifyDataSetChanged();
if(movieIgnored) {
mManager.scrollToPositionWithOffset(firstItem - 1, topOffset);
} else {
mManager.scrollToPositionWithOffset(firstItem, topOffset);
}
}
// Create new views (called by layout manager)
@Override
public MovieViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.feed_one_recommended_movie_layout, parent, false);
return new MovieViewHolder(view);
}
// Replaced contend of each view (called by layout manager)
@Override
public void onBindViewHolder(MovieViewHolder holder, int position) {
setLikes(holder, position);
setAddToCollection(holder, position);
setTitle(holder, position);
setIgnoreMovieInfo(holder, position);
setMovieInfo(holder, position);
setPosterAndTrailer(holder, position);
setDescription(holder, position);
setTags(holder, position);
}
// returns item count (called by layout manager)
@Override
public int getItemCount() {
return mMovies != null ? mMovies.length : 0;
}
private void setLikes(final MovieViewHolder holder, final int position) {
List<Reason> likes = new ArrayList<>();
for(Reason reason : mMovies[position].reasons) {
if(reason.title.equals("Liked this movie")) {
likes.add(reason);
}
}
mCountOfLikes = likes.size();
holder.likeButton.setText(mContext.getString(R.string.like)
+ Html.fromHtml(getCountOfLikesString(mCountOfLikes)));
final MovieRepo repo = MovieRepo.getInstance();
final int pos = position;
final MovieViewHolder viewHolder = holder;
holder.likeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mMovies[pos].isLiked) {
repo.unlikeMovie(AuthStore.getInstance()
.getAuthToken(), mMovies[pos].id, new Callback<Movie>() {
@Override
public void success(Movie movie, Response response) {
Drawable img = mContext.getResources().getDrawable(R.drawable.ic_like);
viewHolder.likeButton
.setCompoundDrawablesWithIntrinsicBounds(img, null, null, null);
if (--mCountOfLikes <= 0) {
viewHolder.likeButton.setText(mContext.getString(R.string.like));
} else {
viewHolder.likeButton
.setText(Html.fromHtml(mContext.getString(R.string.like)
+ getCountOfLikesString(mCountOfLikes)));
}
mMovies[pos].isLiked = false;
}
@Override
public void failure(RetrofitError error) {
Toast.makeText(mContext.getApplicationContext(),
mContext.getString(R.string.cannot_like), Toast.LENGTH_LONG)
.show();
}
});
} else {
repo.likeMovie(AuthStore.getInstance()
.getAuthToken(), mMovies[pos].id, new Callback<Movie>() {
@Override
public void success(Movie movie, Response response) {
Drawable img = mContext.getResources().getDrawable(R.drawable.ic_liked_green);
viewHolder.likeButton
.setCompoundDrawablesWithIntrinsicBounds(img, null, null, null);
viewHolder.likeButton
.setText(Html.fromHtml(mContext.getString(R.string.like)
+ getCountOfLikesString(++mCountOfLikes)));
mMovies[pos].isLiked = true;
setComments(holder, position);
}
@Override
public void failure(RetrofitError error) {
Toast.makeText(mContext,
mContext.getString(R.string.cannot_like), Toast.LENGTH_LONG).show();
}
});
}
}
});
}
private void setComments(final MovieViewHolder holder, final int position) {
holder.likeAndSaveButtonLayout.setVisibility(View.GONE);
holder.commentsLayout.setVisibility(View.VISIBLE);
holder.sendCommentButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (holder.commentsInputEdit.getText().length() > 0) {
CommentRepo repo = CommentRepo.getInstance();
repo.sendUserComment(AuthStore.getInstance().getAuthToken(), mMovies[position].id,
holder.commentsInputEdit.getText().toString(), new Callback<Void>() {
@Override
public void success(Void aVoid, Response response) {
Toast.makeText(mContext, mContext.getString(R.string.thanks_for_your_comment),
Toast.LENGTH_SHORT).show();
hideCommentsLayout(holder);
}
@Override
public void failure(RetrofitError error) {
Toast.makeText(mContext, mContext.getString(R.string.cannot_add_comment),
Toast.LENGTH_LONG).show();
}
});
} else {
hideCommentsLayout(holder);
}
}
});
}
private void hideCommentsLayout(MovieViewHolder holder) {
holder.commentsLayout.setVisibility(View.GONE);
holder.likeAndSaveButtonLayout.setVisibility(View.VISIBLE);
}
private void setAddToCollection(final MovieViewHolder holder, int position) {
final int pos = position;
if(mMovies[position].isInWatchlist) {
holder.saveButton
.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_check_green, 0, 0, 0);
}
final CollectionRepo repo = CollectionRepo.getInstance();
holder.saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(!mMovies[pos].isInWatchlist) {
repo.addMovieToCollection(AuthStore.getInstance().getAuthToken(), 0, mMovies[pos].id, new Callback<MovieCollection>() {
@Override
public void success(MovieCollection movieCollections, Response response) {
holder.saveButton
.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_check_green, 0, 0, 0);
mMovies[pos].isInWatchlist = true;
}
@Override
public void failure(RetrofitError error) {
Toast.makeText(mContext, mContext.getString(R.string.movie_not_added_to_collection),
Toast.LENGTH_LONG).show();
}
});
} else {
repo.removeMovieFromCollection(AuthStore.getInstance().getAuthToken(), 0,
mMovies[pos].id, new Callback<MovieCollection>() {
@Override
public void success(MovieCollection movieCollections, Response response) {
holder.saveButton
.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_plus, 0, 0, 0);
mMovies[pos].isInWatchlist = false;
}
@Override
public void failure(RetrofitError error) {
Toast.makeText(mContext,
mContext.getString(R.string.cannot_delete_movie_from_watchlist),
Toast.LENGTH_LONG).show();
}
});
}
}
});
}
private String getCountOfLikesString(int countOfLikes) {
String countOfLikesStr;
if(countOfLikes == 0) {
countOfLikesStr = "";
} else if(countOfLikes > 999) {
countOfLikesStr = " " + (countOfLikes/1000) + "K";
} else if (countOfLikes > 999999){
countOfLikesStr = " " + (countOfLikes/1000000) + "M";
} else {
countOfLikesStr = " " + String.valueOf(countOfLikes);
}
return "<small>" + countOfLikesStr + "</small>";
}
private void setTitle(MovieViewHolder holder, final int position) {
holder.movieTitleTextView.setText(mMovies[position].title);
holder.movieTitleTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MovieDetailActivity.openView(mContext, mMovies[position].id, true, false);
}
});
}
private void setIgnoreMovieInfo(MovieViewHolder holder, final int position) {
holder.ignoreMovie.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MovieRepo repo = MovieRepo.getInstance();
repo.hideMovie(AuthStore.getInstance().getAuthToken(), mMovies[position].id,
new Callback<Void>() {
@Override
public void success(Void aVoid, Response response) {
Movie newMovies = new Movie[mMovies.length - 1];
for (int i = 0, j = 0; j < mMovies.length; i++, j++) {
if (i != position) {
newMovies[i] = mMovies[j];
} else {
if (++j < mMovies.length) {
newMovies[i] = mMovies[j];
}
}
}
uiBus.post(new MoviesChangedEvent(newMovies));
setMoviesAndNotify(newMovies, true);
Toast.makeText(mContext, mContext.getString(R.string.movie_ignored),
Toast.LENGTH_SHORT).show();
}
@Override
public void failure(RetrofitError error) {
Toast.makeText(mContext, mContext.getString(R.string.movie_ignored_failed),
Toast.LENGTH_LONG).show();
}
});
}
});
}
private void setMovieInfo(MovieViewHolder holder, int position) {
String imdp = "IMDB: ";
String sources = "", date;
if(mMovies[position].showtimes != null && mMovies[position].showtimes.length > 0) {
int countOfSources = mMovies[position].showtimes.length;
for(int i = 0; i < countOfSources; i++) {
sources += mMovies[position].showtimes[i].name + ", ";
}
sources = sources.trim();
if(sources.charAt(sources.length() - 1) == ',') {
if(sources.length() > 1) {
sources = sources.substring(0, sources.length() - 2);
} else {
sources = "";
}
}
} else {
sources = "";
}
imdp += mMovies[position].imdbRating + " | ";
if(sources.isEmpty()) {
date = mMovies[position].releaseYear;
} else {
date = mMovies[position].releaseYear + " | ";
}
holder.movieInfoTextView.setText(imdp + date + sources);
}
private void setPosterAndTrailer(final MovieViewHolder holder, final int position) {
if (mMovies[position] != null && mMovies[position].posterPath != null
&& !mMovies[position].posterPath.isEmpty()) {
Picasso.with(mContext)
.load(mMovies[position].posterPath)
.error(mContext.getResources().getDrawable(R.drawable.noposter))
.into(holder.posterImageView);
} else {
holder.posterImageView.setImageResource(R.drawable.noposter);
}
holder.posterImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MovieDetailActivity.openView(mActivity, mMovies[position].id, false, false);
}
});
if(mMovies[position] != null && mMovies[position].trailerLink != null
&& !mMovies[position].trailerLink.isEmpty()) {
holder.playTrailer.setVisibility(View.VISIBLE);
holder.playTrailer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MovieDetailActivity.openView(mActivity, mMovies[position].id, false, true);
}
});
}
}
private void setDescription(MovieViewHolder holder, int position) {
String text = mMovies[position].overview;
if(text == null || text.isEmpty()) {
holder.descriptionText.setText(mContext.getString(R.string.no_description));
} else if(text.length() > 200) {
text = text.substring(0, 196) + "...";
holder.descriptionText.setText(text);
} else {
holder.descriptionText.setText(text);
}
final int pos = position;
holder.descriptionText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MovieDetailActivity.openView(mActivity, mMovies[pos].id, false, false);
}
});
}
private void setTags(MovieViewHolder holder, int position) {
List<String> tags = Arrays.asList(mMovies[position].tags);
if(tags.size() > 0) {
CastAndTagsFeedAdapter adapter = new CastAndTagsFeedAdapter(tags,
mContext, ((FragmentActivity) mActivity).getSupportFragmentManager());
holder.tags.setItemMargin(10);
holder.tags.setAdapter(adapter);
} else {
holder.tags.setVisibility(View.GONE);
}
}
// class view holder that provide us a link for each element of list
public static class MovieViewHolder extends RecyclerView.ViewHolder {
TextView movieTitleTextView, movieInfoTextView, descriptionText, reasonsCountText;
TextView reasonText1, reasonAuthor1, reasonText2, reasonAuthor2;
EditText commentsInputEdit;
Button likeButton, saveButton, playTrailer, sendCommentButton;
ImageButton ignoreMovie;
ImageView posterImageView, userPicture1, userPicture2;
TwoWayView tags;
RelativeLayout mainReasonsLayout, firstReasonLayout, secondReasonLayout, reasonsListLayout;
RelativeLayout commentsLayout;
LinearLayout likeAndSaveButtonLayout;
ProgressBar progressBar;
public MovieViewHolder(View view) {
super(view);
movieTitleTextView = (TextView)view.findViewById(R.id.movie_title_text);
movieInfoTextView = (TextView)view.findViewById(R.id.movie_info_text);
descriptionText = (TextView)view.findViewById(R.id.text_description);
reasonsCountText = (TextView)view.findViewById(R.id.reason_count);
reasonText1 = (TextView)view.findViewById(R.id.reason_text_1);
reasonAuthor1 = (TextView)view.findViewById(R.id.author_1);
reasonText2 = (TextView)view.findViewById(R.id.reason_text_2);
reasonAuthor2 = (TextView)view.findViewById(R.id.author_2);
commentsInputEdit = (EditText)view.findViewById(R.id.comment_input);
likeButton = (Button)view.findViewById(R.id.like_button);
saveButton = (Button)view.findViewById(R.id.save_button);
playTrailer = (Button)view.findViewById(R.id.play_trailer_button);
sendCommentButton = (Button)view.findViewById(R.id.send_button);
ignoreMovie = (ImageButton)view.findViewById(R.id.ignore_movie_imagebutton);
posterImageView = (ImageView)view.findViewById(R.id.poster_image);
userPicture1 = (ImageView)view.findViewById(R.id.user_picture_1);
userPicture2 = (ImageView)view.findViewById(R.id.user_picture_2);
tags = (TwoWayView)view.findViewById(R.id.list_view_feed_tags);
mainReasonsLayout = (RelativeLayout)view.findViewById(R.id.reasons_main_layout);
firstReasonLayout = (RelativeLayout)view.findViewById(R.id.first_reason);
secondReasonLayout = (RelativeLayout)view.findViewById(R.id.second_reason);
reasonsListLayout = (RelativeLayout)view.findViewById(R.id.reasons_list);
commentsLayout = (RelativeLayout)view.findViewById(R.id.comments_layout);
likeAndSaveButtonLayout = (LinearLayout)view
.findViewById(R.id.like_and_save_buttons_layout);
progressBar = (ProgressBar)view.findViewById(R.id.centered_progress_bar);
}
}
}
Exception:
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{42319ed8 position=1 id=-1, oldPos=0, pLpos:0 scrap tmpDetached no parent}
at android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:4166)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4297)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4278)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1947)
at android.support.v7.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:434)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1322)
at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:556)
at android.support.v7.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:171)
at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:2627)
at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2971)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:562)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.support.v4.view.ViewPager.onLayout(ViewPager.java:1626)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.support.v4.view.ViewPager.onLayout(ViewPager.java:1626)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
07-30 12:48:22.688 9590-9590/com.Filmgrail.android.debug W/System.err? at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2356)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2069)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1254)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6630)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:803)
at android.view.Choreographer.doCallbacks(Choreographer.java:603)
at android.view.Choreographer.doFrame(Choreographer.java:573)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:789)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5479)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1283)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
at dalvik.system.NativeStart.main(Native Method)
How can I fix this?
24 Answers
24
This problem is caused by RecyclerView
Data modified in different thread. The best way is checking all data access. And a workaround is wrapping LinearLayoutManager
.
RecyclerView
LinearLayoutManager
There was actually a bug in RecyclerView and the support 23.1.1 still not fixed.
For a workaround, notice that backtrace stacks, if we can catch this Exception
in one of some class it may skip this crash. For me, I create a LinearLayoutManagerWrapper
and override the onLayoutChildren
:
Exception
LinearLayoutManagerWrapper
onLayoutChildren
public class WrapContentLinearLayoutManager extends LinearLayoutManager {
//... constructor
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
Log.e("probe", "meet a IOOBE in RecyclerView");
}
}
}
Then set it to RecyclerView
:
RecyclerView
RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new WrapContentLinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false));
Actually catch this exception, and seems no any side-effect yet.
Also, if you use GridLayoutManager
or StaggeredGridLayoutManager
you must create a wrapper for it.
GridLayoutManager
StaggeredGridLayoutManager
Notice: The RecyclerView
may be in a wrong internal state.
RecyclerView
where exactly do you put this? on adapter or activity?
– Steve Kamau
Nov 28 '15 at 20:41
extend a
LinearLayoutManager
and override this. I will an addition in my answer.– sakiM
Nov 30 '15 at 7:39
LinearLayoutManager
code.google.com/p/android/issues/detail?id=158046 answer #12 said don't do that.
– Robert
Dec 15 '15 at 9:09
This does not work . Galaxy s7 ,android 6.0.1
– Vlado Pandžić
Jul 11 '16 at 12:06
For my case I am doing on the same thread. mDataHolder.get().removeAll(mHiddenGenre); mAdapter.notifyItemRangeRemoved(mExpandButtonPosition, mHiddenGenre.size());
– JehandadK
Sep 14 '17 at 5:18
This is an example for refreshing data with completely new content.
You can easily modify it to fit your needs.
I solved this in my case by calling:
notifyItemRangeRemoved(0, previousContentSize);
before:
notifyItemRangeInserted(0, newContentSize);
This is the correct solution and is also mentioned in this post by an AOSP project member.
This solution works for me I tried lot of answers here but they don't work (I didn't test the first solution thaugh)
– Da nose
May 22 '16 at 20:42
The problem is using these methods create that inconsistency, even when done on the same thread.
– JehandadK
Sep 14 '17 at 5:17
According to this issue, the problem has been resolved and was likely released some time near the beginning of 2015. A quote from that same thread:
It is specifically related to calling notifyDataSetChanged. [...]
Btw, I strongly advice not using notifyDataSetChanged because it kills animations and performance. Also for this case, using specific notify events will work around the issue.
If you are still having issues with a recent version of the support library, I would suggest reviewing your calls to notifyXXX
(specifically, your use of notifyDataSetChanged
) inside your adapter, to make sure you are adhering to the (somewhat delicate/obscure) RecyclerView.Adapter
contract. Also be sure to issue those notifications on the main thread.
notifyXXX
notifyDataSetChanged
RecyclerView.Adapter
not really, i agree with your part about performance but notifyDataSetChanged() does not kill animations, to animate using notifyDataSetChanged(), a) call setHasStableIds(true) on your RecyclerView.Adapter object and b) override getItemId inside your Adapter to return a unique long value for each row and check it out, the animations do work
– PirateApp
Nov 18 '15 at 10:51
@PirateApp You should consider making your comment as an answer. I have tried it and it's working fine.
– mr5
May 30 at 9:45
I had a similar problem.
Problem in error code below:
int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize - 1, messageListHistory.size() -1);
Solution:
int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize, messageListHistory.size() -prevSize);
This worked for me. The key is to not use notifyDataSetChanged()
and to do the right things in the correct order:
notifyDataSetChanged()
public void setItems(ArrayList<Article> newArticles) {
//get the current items
int currentSize = articles.size();
//remove the current items
articles.clear();
//add all the new items
articles.addAll(newArticles);
//tell the recycler view that all the old items are gone
notifyItemRangeRemoved(0, currentSize);
//tell the recycler view how many new items we added
notifyItemRangeInserted(0, newArticles.size());
}
This is the most thorough solution with a good explanation. Thanks!
– Sakiboy
May 7 '17 at 20:34
No. This is really bad for UI performance.
– FilipLuch
Jan 22 at 22:59
then what is the purpose of using the notifyitemrangeinserted instead of notifydatasetchanged() , @Bolling.
– Ankur_009
Apr 17 at 5:32
@FilipLuch Can you explain why?
– Sreekanth Karumanaghat
May 4 at 5:28
@SreekanthKarumanaghat sure, dunno why I did not explain the reason. Basically he clears then recreates all items in the list. Like in search results, very often you get same items, or when refresh is done you get same items and then you'd end up recreating everything, which is a waste of performance. Use DiffUtils instead and only update the changes rather than all items. It's like going from A to Z every time, but you only changed the F in there.
– FilipLuch
May 16 at 15:51
I had the same problem.
It was caused because I delayed notification for adapter about item insert.
But ViewHolder
tried to redraw some data in it's view and it started the RecyclerView
measuring and recounting children count - at that moment it crashed (items list and it's size was already updated, but the adapter was not notified yet).
ViewHolder
RecyclerView
I faced this issue once, and I solved this by wrapping the LayoutManager
and disabling predictive animations.
LayoutManager
Here an example:
public class LinearLayoutManagerWrapper extends LinearLayoutManager {
public LinearLayoutManagerWrapper(Context context) {
super(context);
}
public LinearLayoutManagerWrapper(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public LinearLayoutManagerWrapper(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean supportsPredictiveItemAnimations() {
return false;
}
}
And set it to RecyclerView
:
RecyclerView
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManagerWrapper(context, LinearLayoutManager.VERTICAL, false);
this seems to work for me, but can you tell why this works?
– Dennis Anderson
May 17 at 11:57
This bug is still not fixed in 23.1.1, but a common workaround would be to catch the exception.
Catch it where, exactly? The only code in the stack trace is native Android code.
– howettl
Mar 8 '16 at 22:28
Catch it just like @saki_M answer.
– Renan Bandeira
Mar 10 '16 at 14:43
Does this actually work though for you though Renan? Have you tested the fix for a while? The bug only occurs occasionally so I will only see whether this works over time.
– Simon
Mar 12 '16 at 12:35
This actually work but some child views in mine are inconsitently remains.
– david
Apr 12 '16 at 16:58
@david Inconsistently remains what is the consequence of this?
– Sreekanth Karumanaghat
May 4 at 6:09
This problem is caused by RecyclerView Data modified in different
thread
Can confirm threading as one problem and since I ran into the issue and RxJava is becoming increasingly popular: make sure that you are using .observeOn(AndroidSchedulers.mainThread())
whenever you're calling notify[whatever changed]
.observeOn(AndroidSchedulers.mainThread())
notify[whatever changed]
code example from adapter:
myAuxDataStructure.getChangeObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<AuxDataStructure>() {
[...]
@Override
public void onNext(AuxDataStructure o) {
[notify here]
}
});
I am on main Thread while calling DiffUtil.calculateDiff(diffUtilForecastItemChangesAnlayser(this.mWeatherForecatsItemWithMainAndWeathers, weatherForecastItems)).dispatchUpdatesTo(this); the log is clear onThread:Thread[main,5,main]
– Mathias Seguy Android2ee
Jul 3 at 10:27
This happens when you specify the incorrect position for the notifyItemChanged ,
notifyItemRangeInserted etc.For me :
Before : (Erroneous)
public void addData(List<ChannelItem> list) {
int initialSize = list.size();
mChannelItemList.addAll(list);
notifyItemRangeChanged(initialSize - 1, mChannelItemList.size());
}
After : (Correct)
public void addData(List<ChannelItem> list) {
int initialSize = mChannelItemList.size();
mChannelItemList.addAll(list);
notifyItemRangeInserted(initialSize, mChannelItemList.size()-1); //Correct position
}
In my case every time when I call notifyItemRemoved(0), it crashed. Turned out that I set setHasStableIds(true)
and in getItemId
I just returned the item position.
I ended out updating it to return the item's hashCode()
or self-defined unique id, which solved the issue.
setHasStableIds(true)
getItemId
hashCode()
Problem occured for me only when:
I created the Adapter with an empty list.
Then I inserted items and called notifyItemRangeInserted
.
notifyItemRangeInserted
Solution:
I solved this by creating the Adapter only after I have the first chunk of data and initialzing it with it right away. The next chunk could then be inserted and notifyItemRangeInserted
called with no problem .
notifyItemRangeInserted
The error can be caused by your changes being inconsistent with what you are notifying. In my case:
myList.set(position, newItem);
notifyItemInserted(position);
What I of course had to do:
myList.add(position, newItem);
notifyItemInserted(position);
My problem was that even though i clear both the array list containing the data model for the recycler view, i did not notify the adapter of that change, so it had stale data from previous model. Which caused the confusion about the view holder position. To Fix this always notify the adapter that the data-set as changed before updating again.
or simply notify if item removed instead
– Remario
Apr 23 '17 at 17:04
my model uses the reference of the container thats why
– Remario
Apr 23 '17 at 17:04
In my case the problem was that I used notifyDataSetChanged when amount of newly loaded data was less than initial data.
This approach helped me:
adapter.notifyItemRangeChanged(0, newAmountOfData + 1);
adapter.notifyItemRangeRemoved(newAmountOfData + 1, previousAmountOfData);
In my case I was changing the data previously inside a thread with mRecyclerView.post(new Runnable...) and then again later changed data in the UI thread, which caused inconsistency.
another reason this problem happens is when you call these methods with wrong indexes (indexes which there has NOT happened insert or remove in them)
-notifyItemRangeRemoved
-notifyItemRemoved
-notifyItemRangeInserted
-notifyItemInserted
check indexe parameters to these methods and make sure they are precise and correct.
I got this error because I was mistakenly calling a method to remove a specific row from my recyclerview multiple times. I had a method like:
void removeFriends() {
final int loc = data.indexOf(friendsView);
data.remove(friendsView);
notifyItemRemoved(loc);
}
I was accidentally calling this method three times instead of once, so the second time loc
was -1 and the error was given when it tried to remove it. The two fixes were to ensure the method was only called once, and also to add a sanity check like this:
loc
void removeFriends() {
final int loc = data.indexOf(friendsView);
if (loc > -1) {
data.remove(friendsView);
notifyItemRemoved(loc);
}
}
I also encountered the same issue.
Solution is one line of code: myRecyclerViewReference.getRecycledViewPool().clear();
myRecyclerViewReference.getRecycledViewPool().clear();
Use before notifyDataSetChanged
and new Adapter()
initialization for setAdapter
notifyDataSetChanged
new Adapter()
setAdapter
Get it resolved from
https://readyandroid.wordpress.com/java-lang-indexoutofboundsexception-inconsistency-detected-invalid-item-position-android-recyclerview/
Nice blog with Android tricks: https://readyandroid.wordpress.com/
I got the same problem and I have read that this happened in Samsung phones only...But the reality showed that this happens in a lot of brands.
After testing I realized that this happens only when you scroll fast the RecyclerView and then you go back either with the back button or the Up button. So I put inside Up button and onBackpressed the below snippet:
someList = new ArrayList<>();
mainRecyclerViewAdapter = new MainRecyclerViewAdapter(this, someList, this);
recyclerViewMain.setAdapter(mainRecyclerViewAdapter);
finish();
With this solution you just load a new Arraylist to the adapter and new adapter to recyclerView and then you finish activity.
Hope it helps someone
I had the same problem. Recreating the new list like this fixed the problem perfectly:
// in Adapter class
public void updateItems(List<ComponentEntity> newItems) {
List<ComponentEntity> updatedItems = new ArrayList<>();
updatedItems = newItems;
items = updatedItems;
notifyDataSetChanged();
}
In my case, I was getting this problem because of getting data updates from server (I am using Firebase Firestore) and while the first set of data is being processed by DiffUtil in the background, another set of data update comes and causes a concurrency issue by starting another DiffUtil.
In short, if you are using DiffUtil on a Background thread which then comes back to the Main Thread to dispatch the results to the RecylerView, then you run the chance of getting this error when multiple data updates come in short time.
I solved this by following the advice in this wonderful explanation:
https://medium.com/@jonfhancock/get-threading-right-with-diffutil-423378e126d2
Just to explain the solution is to push the updates while the current one is running to a Deque. The deque can then run the pending updates once the current one finishes, hence handling all subsequent updates but avoiding inconsistency errors as well!
Hope this helps because this one made me scratch my head!
I got this error because i was calling "notifyItemInserted" twice by mistake.
If your data changes a lot, you can use
mAdapter.notifyItemRangeChanged(0, yourData.size());
or some single items in your data set changes, your can use
mAdapter.notifyItemChanged(pos);
For detailed methods usage, you can refer the doc, in a way, try not to directly use mAdapter.notifyDataSetChanged()
.
mAdapter.notifyDataSetChanged()
using
notifyItemRangeChanged
also produces the same crash.– lionelmessi
Jan 11 '16 at 4:10
notifyItemRangeChanged
That suits some situation. Maybe you updated your dataset both in background thread and UI thread, this will also cause inconsistency. If you only update the dataset in UI thread, it will work.
– Arron Cao
Jan 15 '16 at 21:29
Thank you for your interest in this question.
Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).
Would you like to answer one of these unanswered questions instead?
when you're back, is your data same as when you leave the page?
– khusrav
Sep 5 '15 at 17:37