android - make - RecyclerView y java.lang.IndexOutOfBoundsException: Inconsistencia detectada. Posición del adaptador del soporte de visualización no válida ViewHolder en dispositivos Samsung
recyclerview in fragment (27)
Este problema es causado por RecyclerView Data modificado en diferentes hilos
Puede confirmar que el subproceso es un problema y, dado que me encontré con el problema y RxJava se está volviendo cada vez más popular: asegúrese de estar usando
.observeOn(AndroidSchedulers.mainThread())
cada vez que llame
notify[whatever changed]
ejemplo de código del adaptador:
myAuxDataStructure.getChangeObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<AuxDataStructure>() {
[...]
@Override
public void onNext(AuxDataStructure o) {
[notify here]
}
});
Tengo una vista de reciclador que funciona perfectamente en todos los dispositivos, excepto Samsung. En Samsung, tengo
java.lang.IndexOutOfBoundsException: Inconsistencia detectada. Posición del adaptador del soporte de la vista no válida
cuando vuelvo al fragmento con la vista de reciclador de otra actividad.
Código del adaptador:
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);
}
}
}
Excepción:
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)
¿Cómo puedo arreglar esto?
De acuerdo con este problema , el problema se ha resuelto y probablemente se publicó en algún momento cerca del comienzo de 2015. Una cita de ese mismo hilo :
Está específicamente relacionado con llamar a notifyDataSetChanged. [...]
Por cierto, le recomiendo no utilizar notifyDataSetChanged porque mata las animaciones y el rendimiento. También para este caso, el uso de eventos de notificación específicos evitará el problema.
Si todavía tiene problemas con una versión reciente de la biblioteca de soporte, le sugiero que revise sus llamadas para
notifyXXX
(específicamente, su uso de
notifyDataSetChanged
) dentro de su adaptador, para asegurarse de que se adhiere al
RecyclerView.Adapter
(algo delicado / oscuro) Contrato de
RecyclerView.Adapter
.
También asegúrese de emitir esas notificaciones en el hilo principal.
El error puede deberse a que sus cambios son inconsistentes con lo que está notificando. En mi caso:
myList.set(position, newItem);
notifyItemInserted(position);
Lo que, por supuesto, tuve que hacer:
myList.add(position, newItem);
notifyItemInserted(position);
En mi caso, cada vez que llamo a notifyItemRemoved (0), se bloquea.
Resultó que configuré
setHasStableIds(true)
y en
getItemId
acabo de devolver la posición del elemento.
Terminé actualizándolo para devolver el elemento
hashCode()
o la identificación única autodefinida, lo que resolvió el problema.
En mi caso, el problema era que usé notifyDataSetChanged cuando la cantidad de datos recién cargados era menor que los datos iniciales. Este enfoque me ayudó a:
adapter.notifyItemRangeChanged(0, newAmountOfData + 1);
adapter.notifyItemRangeRemoved(newAmountOfData + 1, previousAmountOfData);
En mi caso, estaba cambiando los datos previamente dentro de un hilo con mRecyclerView.post (nuevo Runnable ...) y luego nuevamente cambié los datos en el hilo de la interfaz de usuario, lo que causó inconsistencia.
En mi caso, he tenido más de 5000 artículos en la lista. Mi problema fue que al desplazar la vista del reciclador, a veces se llama al "onBindViewHolder" mientras el método "myCustomAddItems" está alterando la lista.
Mi solución fue agregar "sincronizado (syncObject) {}" a todos los métodos que alteran la lista de datos. De esta manera, en cualquier momento, solo un método puede leer esta lista.
En mi caso, los datos del adaptador cambiaron. Y utilicé incorrectamente notifyItemInserted () para estos cambios. Cuando uso notifyItemChanged, el error ha desaparecido.
En mi caso, recibí este problema debido a que recibí actualizaciones de datos del servidor (estoy usando Firebase Firestore) y mientras DiffUtil procesa el primer conjunto de datos en segundo plano, aparece otro conjunto de actualización de datos que causa un problema de concurrencia iniciando otro DiffUtil.
En resumen, si está utilizando DiffUtil en un subproceso en segundo plano que luego vuelve al subproceso principal para enviar los resultados a RecylerView, entonces tiene la posibilidad de obtener este error cuando llegan varias actualizaciones de datos en poco tiempo.
Resolví esto siguiendo los consejos de esta maravillosa explicación: https://medium.com/@jonfhancock/get-threading-right-with-diffutil-423378e126d2
Solo para explicar que la solución es enviar las actualizaciones mientras la actual se está ejecutando en Deque. El deque puede ejecutar las actualizaciones pendientes una vez que finalice la actual, por lo tanto, manejar todas las actualizaciones posteriores, ¡pero también evita errores de inconsistencia!
¡Espero que esto ayude porque este me hizo rascarme la cabeza!
Este error aún no se solucionó en 23.1.1, pero una solución común sería detectar la excepción.
Este es un ejemplo para actualizar datos con contenido completamente nuevo. Puede modificarlo fácilmente para adaptarlo a sus necesidades. Resolví esto en mi caso llamando:
notifyItemRangeRemoved(0, previousContentSize);
antes de:
notifyItemRangeInserted(0, newContentSize);
Esta es la solución correcta y también es mencionada en esta publicación por un miembro del proyecto AOSP.
Este problema es causado por
RecyclerView
Data modificado en un hilo diferente.
La mejor manera es verificar todos los accesos a los datos.
Y una solución alternativa es envolver
LinearLayoutManager
.
Respuesta anterior
En realidad, hubo un error en RecyclerView y el soporte 23.1.1 aún no se solucionó.
Para una solución alternativa, tenga en cuenta que las pilas de traza inversa, si podemos detectar esta
Exception
en alguna clase, puede omitir este bloqueo.
Para mí, creo un
LinearLayoutManagerWrapper
y
onLayoutChildren
el
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("TAG", "meet a IOOBE in RecyclerView");
}
}
}
Luego
RecyclerView
en
RecyclerView
:
RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new WrapContentLinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false));
En realidad, capta esta excepción, y parece que todavía no hay ningún efecto secundario.
Además, si usa
GridLayoutManager
o
StaggeredGridLayoutManager
, debe crear un contenedor para ello.
Aviso:
RecyclerView
puede estar en un estado interno incorrecto.
Esto sucede cuando especifica la posición incorrecta para notifyItemChanged, notifyItemRangeInserted, etc. Para mí:
Antes: (erróneo)
public void addData(List<ChannelItem> list) {
int initialSize = list.size();
mChannelItemList.addAll(list);
notifyItemRangeChanged(initialSize - 1, mChannelItemList.size());
}
Después: (correcto)
public void addData(List<ChannelItem> list) {
int initialSize = mChannelItemList.size();
mChannelItemList.addAll(list);
notifyItemRangeInserted(initialSize, mChannelItemList.size()-1); //Correct position
}
Mi problema fue que, aunque borré la lista de la matriz que contiene el modelo de datos para la vista del reciclador, no notifiqué al adaptador ese cambio, por lo que tenía datos obsoletos del modelo anterior. Lo que causó la confusión sobre la posición del titular de la vista. Para solucionar esto, siempre notifique al adaptador que el conjunto de datos ha cambiado antes de actualizar nuevamente.
Nueva respuesta: use DiffUtil para todas las actualizaciones de RecyclerView. Esto ayudará con el rendimiento y el error anterior. Mira aquí
Respuesta anterior: Esto funcionó para mí.
La clave es no utilizar
notifyDataSetChanged()
y hacer lo correcto en el orden correcto:
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManagerWrapper(context, LinearLayoutManager.VERTICAL, false);
Otra razón por la que ocurre este problema es cuando llama a estos métodos con índices incorrectos (los índices que NO han sucedido, insértelos o elimínelos)
-notifyItemRangeRemoved
-notifyItemRemoved
-notifyItemRangeInserted
-notifyItemInserted
verifique los parámetros de indexación de estos métodos y asegúrese de que sean precisos y correctos.
Recibí este error porque estaba llamando a "notifyItemInserted" dos veces por error.
Recibí este error porque estaba llamando por error a un método para eliminar una fila específica de mi vista de reciclador varias veces. Tenía un método como:
void removeFriends() {
final int loc = data.indexOf(friendsView);
data.remove(friendsView);
notifyItemRemoved(loc);
}
Accidentalmente estaba llamando a este método tres veces en lugar de una vez, por lo que la segunda vez
loc
fue -1 y se produjo el error cuando intentó eliminarlo.
Las dos soluciones fueron para garantizar que el método solo se llamara una vez, y también para agregar un control de cordura como este:
void removeFriends() {
final int loc = data.indexOf(friendsView);
if (loc > -1) {
data.remove(friendsView);
notifyItemRemoved(loc);
}
}
Si sus datos cambian mucho, puede usar
mAdapter.notifyItemRangeChanged(0, yourData.size());
o algunos elementos individuales en su conjunto de datos cambian, puede usar
mAdapter.notifyItemChanged(pos);
Para el uso detallado de los métodos, puede consultar el
doc
, de alguna manera, intente no usar directamente
mAdapter.notifyDataSetChanged()
.
Tengo el mismo problema y he leído que esto sucedió solo en teléfonos Samsung ... Pero la realidad mostró que esto sucede en muchas marcas.
Después de la prueba, me di cuenta de que esto sucede solo cuando se desplaza rápidamente el RecyclerView y luego retrocede con el botón Atrás o el botón Arriba. Así que puse dentro el botón Arriba y presioné el fragmento a continuación:
someList = new ArrayList<>();
mainRecyclerViewAdapter = new MainRecyclerViewAdapter(this, someList, this);
recyclerViewMain.setAdapter(mainRecyclerViewAdapter);
finish();
Con esta solución, solo carga una nueva Arraylist en el adaptador y un nuevo adaptador en recyclerView y luego finaliza la actividad.
Espero que ayude a alguien
Tuve el mismo problema.
Mi aplicación utiliza componentes de navegación con un fragmento que contiene mi recyclerView. Mi lista se mostró bien la primera vez que se cargó el fragmento ... pero al navegar y regresar, se produjo este error.
Al navegar, el ciclo de vida del fragmento solo pasó por onDestroyView y al regresar comenzó en onCreateView. Sin embargo, mi adaptador se inicializó en el fragmento onCreate y no se reinicializó al regresar.
La solución fue inicializar el adaptador en onCreateView.
Espero que esto pueda ayudar a alguien.
Tuve un problema similar.
Problema en el código de error a continuación:
int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize - 1, messageListHistory.size() -1);
Solución:
int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize, messageListHistory.size() -prevSize);
Yo tuve el mismo problema. Fue causado porque retrasé la notificación del adaptador sobre la inserción del elemento.
Pero
ViewHolder
intentó volver a dibujar algunos datos en su vista y comenzó el recuento de niños
RecyclerView
midiendo y contando, en ese momento se bloqueó (la lista de elementos y su tamaño ya se actualizaron, pero el adaptador aún no se notificó).
Yo tuve el mismo problema. La recreación de la nueva lista como esta solucionó el problema perfectamente:
// en la clase Adaptador
public void updateItems(List<ComponentEntity> newItems) {
List<ComponentEntity> updatedItems = new ArrayList<>();
updatedItems = newItems;
items = updatedItems;
notifyDataSetChanged();
}
LayoutManager
este problema una vez, y lo resolví envolviendo el
LayoutManager
y desactivando las animaciones predictivas.
Aquí un ejemplo:
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());
}
Y
RecyclerView
en
RecyclerView
:
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;
}
}
El problema ocurrió solo para mí cuando:
Creé el adaptador con una
lista vacía
.
Luego
notifyItemRangeInserted
elementos y llamé a
notifyItemRangeInserted
.
Solución:
Resolví esto creando el Adaptador solo después de tener la primera porción de datos e inicializándolo con él de inmediato.
El siguiente fragmento se podría insertar y
notifyItemRangeInserted
sin ningún problema.
Las razones causaron este problema:
- Un problema interno en Recycler cuando las animaciones de elementos están habilitadas
- Modificación en los datos del Reciclador en otro hilo
- Llamar a métodos de notificación de manera incorrecta
SOLUCIÓN:
----------------- SOLUCIÓN 1 ---------------
- Capturar la excepción (no recomendado especialmente por la razón # 3)
Cree un LinearLayoutManager personalizado como el siguiente y configúrelo en ReyclerView
public class CustomLinearLayoutManager extends LinearLayoutManager {
//Generate constructors
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Inconsistency detected");
}
}
}
Luego configure RecyclerVIew Layout Manager de la siguiente manera:
recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));
----------------- SOLUCIÓN 2 ---------------
- Deshabilite las animaciones de elementos (corrige el problema si causó el motivo # 1):
Nuevamente, cree un Administrador de diseño lineal personalizado de la siguiente manera:
public class CustomLinearLayoutManager extends LinearLayoutManager {
//Generate constructors
@Override
public boolean supportsPredictiveItemAnimations() {
return false;
}
}
Luego configure RecyclerVIew Layout Manager de la siguiente manera:
recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));
----------------- SOLUCIÓN 3 ---------------
- Esta solución soluciona el problema si es causado por la razón # 3. Debe asegurarse de que está utilizando los métodos de notificación de la manera correcta. Alternativamente, use DiffUtil para manejar el cambio de una manera inteligente, fácil y sin problemas. Usando DiffUtil en Android RecyclerView
----------------- SOLUCIÓN 4 ---------------
- Por la razón # 2, debe verificar todos los datos de acceso a la lista de recicladores y asegurarse de que no haya modificaciones en otro hilo.