RecyclerView: Inconsistencia detectada. Posición inválida del artículo

Nuestro QA detectó un error: al girar el dispositivo Android (Droid Turbo), se produjo el siguiente locking relacionado con RecyclerView:

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:3 

Para mí, parece un error interno dentro de RecyclerView, ya que no puedo pensar en ninguna forma de que esto sea causado directamente por nuestro código …

¿Alguien ha encontrado este problema?

¿Cuál sería la solución?

Una solución brutal podría ser quizás atrapar la excepción cuando sucede, y volver a crear la instancia de RecyclverView desde cero, para evitar quedarse con un estado dañado.

Pero, si es posible, me gustaría entender mejor el problema (y quizás arreglarlo en su origen), en lugar de enmascararlo.

El error no es fácil de reproducir, pero es fatal cuando ocurre.

El stack-trace completo:

  >W/dalvikvm( 7546): threadid=1: thread exiting with uncaught exception (group=0x41987d40) >E/AndroidRuntime( 7546): FATAL EXCEPTION: main >E/AndroidRuntime( 7546): Process: com.oblong.mezzedroid, PID: 7546 >E/AndroidRuntime( 7546): java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:3 >E/AndroidRuntime( 7546): at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3382) >E/AndroidRuntime( 7546): at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3340) >E/AndroidRuntime( 7546): at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1810) >E/AndroidRuntime( 7546): at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1306) >E/AndroidRuntime( 7546): at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1269) >E/AndroidRuntime( 7546): at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:523) >E/AndroidRuntime( 7546): at org.liboid.recycler_view.RecyclerViewContainer$LiLinearLayoutManager.onLayoutChildren(RecyclerViewContainer.java:179) >E/AndroidRuntime( 7546): at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:1942) >E/AndroidRuntime( 7546): at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2237) >E/AndroidRuntime( 7546): at org.liboid.recycler_view.LiRecyclerView.onLayout(LiRecyclerView.java:30) >E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946) >E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651) >E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453) >E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388) >E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946) >E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651) >E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453) >E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388) >E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946) >E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651) >E/AndroidRuntime( 7546): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671) >E/AndroidRuntime( 7546): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525) >E/AndroidRuntime( 7546): at android.widget.LinearLayout.onLayout(LinearLayout.java:1434) >E/AndroidRuntime( 7546): at com.oblong.mezzedroid.workspace.content.bins.BinsContainerLayout.onLayout(BinsContainerLayout.java:22) >E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946) >E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651) >E/AndroidRuntime( 7546): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671) >E/AndroidRuntime( 7546): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525) >E/AndroidRuntime( 7546): at android.widget.LinearLayout.onLayout(LinearLayout.java:1434) >E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946) >E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651) >E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453) >E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388) >E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946) >E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651) >E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453) >E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388) >E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946) >E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651) >E/AndroidRuntime( 7546): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671) >E/AndroidRuntime( 7546): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525) >E/AndroidRuntime( 7546): at android.widget.LinearLayout.onLayout(LinearLayout.java:1434) >E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946) >E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651) >E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453) >E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388) >E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946) >E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651) >E/AndroidRuntime( 7546): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671) >E/AndroidRuntime( 7546): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525) >E/AndroidRuntime( 7546): at android.widget.LinearLayout.onLayout(LinearLayout.java:1434) >E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946) >E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651) >E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453) >E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388) >E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946) >E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651) >E/AndroidRuntime( 7546): at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2132) >E/AndroidRuntime( 7546): at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1872) >E/AndroidRuntime( 7546): at andro 

Tuve un problema (posiblemente) relacionado: ingresé una nueva instancia de una actividad con RecyclerView, pero con un adaptador más pequeño estaba activando este locking por mí.

RecyclerView.dispatchLayout() puede intentar extraer elementos de la chatarra antes de llamar a mRecycler.clearOldPositions() . La consecuencia es que estaba extrayendo elementos del grupo común que tenían posiciones más altas que el tamaño del adaptador.

Afortunadamente, solo hace esto si PredictiveAnimations está habilitado, por lo que mi solución fue crear la subclase GridLayoutManager (LinearLayoutManager tiene el mismo problema y ‘corregir’), y anular admitePredictiveItemAnimations () para devolver falso:

 /** * No Predictive Animations GridLayoutManager */ private static class NpaGridLayoutManager extends GridLayoutManager { /** * 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; } public NpaGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public NpaGridLayoutManager(Context context, int spanCount) { super(context, spanCount); } public NpaGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { super(context, spanCount, orientation, reverseLayout); } } 

En mi caso (eliminar / insertar datos en mi estructura de datos) necesitaba limpiar el grupo de reciclaje y luego notificar el cambio de conjunto de datos.

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

Utilice notifyDataSetChanged() en notifyItem... lugar notifyItem... en este caso.

Lo resolví retrasando el mRecycler.setAdapter(itemsAdapter) hasta después de agregar todos los elementos al adaptador con mRecycler.addAll(items) y funcionó. No tengo idea de por qué lo hice para empezar, fue a través del código de una biblioteca que revisé y vi esas líneas en el “orden incorrecto”, estoy bastante seguro de que esto es así, por favor, si alguien puede confirmarlo, explique por qué es ¿asi que? No estoy seguro de si esta es una respuesta válida, incluso

Tuve un problema similar, pero no exactamente lo mismo. En mi caso, en 1 punto estaba borrando la matriz que se pasó a la vista de reciclador

 mObjects.clear(); 

y no llamar a notifyDataSetChanged, ya que no quería que la vista de reciclador borre las vistas de inmediato. Estaba volviendo a llenar la matriz mObjects en AsyncTask.

Tengo el mismo problema. Se produjo cuando estaba desplazándose rápidamente y llamando a API y actualizando datos. Después de probar todas las cosas para evitar el locking, encontré la solución.

 mRecyclerView.stopScroll(); 

Funcionará.

Utilizar

 notifyDataSetChanged() 

en lugar

 notifyItemRangeInserted(0, YourArrayList.size()) 

en este caso.

Para solucionar este problema, simplemente llame a notifyDataSetChanged () con la lista vacía antes de actualizar la vista de reciclaje.

Por ejemplo

 //Method for refresh recycle view if (!hcpArray.isEmpty()) 

hcpArray.clear (); // La lista para actualizar la vista de reciclaje

 adapter.notifyDataSetChanged(); 

Mi problema desapareció después de modificar la implementación de mi Adapter para usar una copia de la matriz de elementos en lugar de una referencia. El método setItems() se llama cada vez que tenemos nuevos elementos para mostrar en RecyclerView .

En lugar de:

 private class MyAdapter extends RecyclerView.Adapter { private List mItems; (....) void setItems(List items) { mItems = items; } } 

Yo si:

 void setItems(List items) { mItems = new ArrayList<>(items); } 

En mi caso, estaba actualizando los artículos y llamando a notifyDataSetChanged en un hilo no relacionado con la interfaz de usuario. La mayoría de las veces funcionaba, pero cuando se producían muchos cambios rápidamente, se bloqueaba. Cuando lo hice, en cambio, básicamente

 activity.runOnUiThread(new Runnable() { @Override public void run() { changeData(); notifyDataSetChanged(); } }); 

entonces dejó de estrellarse.

Estoy alterando datos para el RecyclerView en el Thread fondo. Obtuve la misma Exception que el OP. Añadí esto después de cambiar los datos:

 myRecyclerView.post(new Runnable() { @Override public void run() { myRecyclerAdapter.notifyDataSetChanged(); } }); 

Espero eso ayude

Solo debe borrar su lista en OnPostExecute() y no mientras hace Pull to Refresh

 // Setup refresh listener which triggers new data loading swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { AsyncTask task = new get_listings(); task.execute(); // clear listing inside onPostExecute } }); 

Descubrí que esto sucede cuando se desplaza durante una extracción para actualizar , ya que estaba borrando la lista antes de la async task , lo que resulta en java.lang.IndexOutOfBoundsException: Inconsistency detected.

  swipeContainer.setRefreshing(false); //TODO : This is very crucial , You need to clear before populating new items listings.clear(); 

De esa forma no terminará con una inconsistencia

este problema puede ocurrir cuando intenta borrar su lista, si va a borrar su lista de datos, especialmente cuando usa pull para refrescar intente usar un indicador booleano, inicialícelo como falso y dentro del método OnRefresh hágalo verdadero, borre su dataList si la bandera es verdadera justo antes de agregarle los datos nuevos y luego hacerla falsa.

tu código podría ser así

  private boolean pullToRefreshFlag = false ; private ArrayList dataList ; private Adapter adapter ; public class myClass extend Fragment implements SwipeRefreshLayout.OnRefreshListener{ private void requestUpdateList() { if (pullToRefresh) { dataList.clear pullToRefreshFlag = false; } dataList.addAll(your data); adapter.notifyDataSetChanged; @Override OnRefresh() { PullToRefreshFlag = true reqUpdateList() ; } } 

También se puede relacionar con configurar el adaptador varias veces al mismo tiempo. Tenía un método de callback que se activó 5-6 veces al mismo tiempo y estaba configurando el adaptador en esa callback para que RecycledViewPool no pudiera manejar todos esos datos de forma simultánea. Es una gran posibilidad, pero es mejor que lo revises de todos modos.

Me he enfrentado con la misma situación. Y se resolvió agregando códigos antes de borrar tu colección.

mRecyclerView.getRecycledViewPool().clear();

Me encontré con un problema similar y lo descubrí. Codifiqué algunos ejemplos para un caso de prueba, pero no me aseguré de que cada uno devolviera un ID único y eso causó el locking que se produjo a continuación. La solución de los ID resolvió el problema, ¡espero que esto ayude a otra persona!

Resolví el problema, agregando elementos uno por uno cuando obtiene nuevos datos. Yo uso esta función dentro del adaptador.

 public void add(Data item) { if(!params.contains(item){ params.add(item); notifyItemInserted(getItemCount() - 1); } } } 

Encontré esa configuración mRecycler.setLayoutFrozen (true); en el método onRefresh del swipeContainer.

resuelto el problema para mí

 swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { orderlistRecycler.setLayoutFrozen(true); loadData(false); } }); 

Tuve un problema anterior. Finalmente encontré una solución para eso

Lo que hago es notificar al adaptador que el elemento se ha eliminado y luego notificar el cambio en el rango del conjunto de datos del adaptador.

  public void setData(List dataList) { if (this.dataList.size() > 0) { notifyItemRangeRemoved(0, dataList.size()); this.dataList.clear(); } this.dataList.addAll(dataList) notifyItemRangeChanged(0, dataList.size()); } 

Este es un error bastante desagradable.

Para manejar mi elemento clic, utilicé una implementación de RecyclerView.OnItemTouchListener similar a la solución que se encuentra en esta pregunta .

Después de muchas veces de actualizar el origen de datos de RecyclerView y hacer clic en un elemento, este IndexOutOfBoundsException bloqueará mi aplicación. Cuando se hace clic en un elemento, el RecyclerView busca internamente la vista subyacente correcta y le devuelve la posición. Revisando el código fuente, vi que había algunas Tasks e Threads progtwigdos. Para abreviar, básicamente se trata de un estado ilegal en el que dos fonts de datos están entremezcladas y no sincronizadas, y todo se vuelve loco.

Basado en esto, ViewHolder mi implementación de RecyclerView.OnItemTouchListener y simplemente atrapé el clic en ViewHolder del Adapter yo mismo:

 public void onBindViewHolder (final BaseContentView holder, final int position) { holder.itemView.setOnClickListener(new OnClickListener() { @Override public void onClick (View view) { // do whatever you like here } }); } 

Puede que esta no sea la mejor solución, pero sin complicaciones por ahora. Espero que esto te ahorre algo de tiempo :).

una vez tuve el error también:

Causa: estaba intentando actualizar una vista de reciclador desde una tarea de sincronización mientras, al mismo tiempo, intentaba obtener viejos viewHolders borrados;

Código: genero datos con solo presionar un botón, lógica de la siguiente manera

  1. Borre los últimos elementos en la vista de reciclador
  2. Llamar a la tarea asíncrona para generar datos
  3. OnPostExecute Actualiza la vista de Recycler y NotifyDataSetChanged

Problema: cada vez que me desplazo rápidamente antes de generar mis datos, obtengo

Inconsistencia detectada. El soporte de la vista no es válido. PositionViewHolder java.lang.IndexOutOfBoundsException: Inconsistency detected. Posición inválida 20 (offset: 2) .state: 3

Solución: en lugar de borrar el RecyclerView antes de generar mis datos, en su lugar lo dejo y luego lo reemplazo con los nuevos datos, la llamada NotifyDatasetChanged, como se muestra a continuación;

  @Override protected void onPostExecute(List o) { super.onPostExecute(o); recyclerViewAdapter.setList(o); mProgressBar.setVisibility(View.GONE); mRecyclerView.setVisibility(View.VISIBLE); } 

En mi caso, simplemente setHasStableIds(true); línea con setHasStableIds(true);

Lint me dio un consejo sobre la incoherencia: escribí (enBindViewHolder ()):

 pholder.mRlayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { doStuff(position); } }); 

que tuvo que ser reemplazado por:

 pholder.mRlayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { doStuff(pholder.getAdapterPosition()); } }); 

Ejecuta ambos códigos en tu código y luego ejecuta Lint para obtener la explicación completa.

Simplemente elimine todas las vistas de su Administrador de diseño antes de notificarlo. me gusta:

 myLayoutmanager.removeAllViews(); 

En mi caso, estaba tratando de cambiar el contenido de mi adaptador en un hilo de fondo, pero llamé notify * en el hilo principal / ui.

¡Eso no es posible! La razón por la cual se obliga a la notificación al hilo principal es que la vista recicladora quiere que edite su adaptador de respaldo en el hilo principal, incluso en la misma stack de llamadas.

Para resolver el problema, asegúrese de que cada operación de su adaptador y cada notificación … ¡la llamada se realiza en el hilo ui / principal !

Tuve el mismo problema con recyclerView Así que acabo de notificar al adaptador sobre el cambio del conjunto de datos justo después de que se borrara la lista.

 mList.clear(); mAdapter.notifyDataSetChanged(); mList.addAll(newData); mAdapter.notifyDataSetChanged(); 

add_location.removeAllViews ();

  for (int i=0;i 

Lo siento por la solución tardía pero perfecta: cuando intentes eliminar un elemento específico, simplemente llama a notifydatasetchange () y obtén este elemento en bindviewholder y quítalo y vuelve a agregarlo a la última lista y luego revisa la posición de la lista si es el último índice y luego elimina ít. Básicamente, el problema se presenta cuando intentas eliminar un objeto del centro. si elimina el elemento del último índice, entonces no habrá más reciclaje y su conteo de adpteres se mantendrá (este es el punto crítico que se estrelló aquí) y el locking se resuelve con el siguiente fragmento de código.

  holder.itemLayout.setVisibility( View.GONE );//to hide temprory it show like you have removed item Model current = list.get( position ); list.remove( current ); list.add( list.size(), current );//add agine to last index if(position==list.size()-1){// remove from last index list.remove( position ); } 

Me encontré con este desagradable rastro de stack con los nuevos componentes de architecture de Android recientemente. Esencialmente, tengo una lista de elementos en mi ViewModel que mi Fragmento observa, usando LiveData. Cuando ViewModel publica un nuevo valor para los datos, el Fragment actualiza el adaptador, pasa estos nuevos elementos de datos y notifica al adaptador que se han producido cambios.

Lamentablemente, al pasar los nuevos elementos de datos al adaptador, no pude explicar el hecho de que tanto ViewModel como el Adapter estarían apuntando a la misma referencia de objeto. Lo que significa que si actualizo los datos y llamo a postValue() desde el ViewModel, ¡hay una ventana muy pequeña donde los datos podrían actualizarse y el adaptador aún no se notifica!

Mi solución fue crear una instancia de una copia nueva de los elementos cuando se pasó al adaptador:

mList = new ArrayList<>(passedList);

Con esta solución súper fácil, puede estar seguro de que los datos de su adaptador no cambiarán hasta justo antes de que se notifique su adaptador.

Esta es la única solución que funcionó para mí, incluso probando muchas de las soluciones anteriores.

1.) Intilización

 CustomAdapter scrollStockAdapter = new CustomAdapter(mActivity, new ArrayList()); list.setAdapter(scrollStockAdapter); scrollStockAdapter.updateList(stockListModels); 

2.) Escribe este método en el adaptador

 public void updateList(List list) { stockListModels.clear(); stockListModels.addAll(list); notifyDataSetChanged(); } 

stockListModels -> esta lista es la que está usando en el adaptador.