web-dev-qa-db-fra.com

Incohérence détectée dans RecyclerView, Comment modifier le contenu de RecyclerView lors du défilement

J'utilise RecyclerView pour afficher le nom des éléments. Ma ligne contient single TextView. Les noms d'éléments sont stockés dans List<String> mItemList.

Pour changer le contenu de RecyclerView, je remplace Strings dans mItemList et appelle notifyDataSetChanged () à RecyclerViewAdapter.

Mais si j'essaie de changer le contenu de la mItemList pendant que RecyclerView défile, cela me donne parfois Java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 157(offset:157).state:588

Cela se produit si la taille de mItemList est inférieure à celle d’avant. Alors, quelle est la bonne façon de changer le contenu de la RecyclerView? Est-ce un bug dans RecyclerView?

Voici la trace complète de la pile de l'exception:

Java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 157(offset:157).state:588
        at Android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.Java:3300)
        at Android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.Java:3258)
        at Android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.Java:1803)
        at Android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.Java:1302)
        at Android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.Java:1265)
        at Android.support.v7.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.Java:1093)
        at Android.support.v7.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.Java:956)
        at Android.support.v7.widget.RecyclerView$ViewFlinger.run(RecyclerView.Java:2715)
        at Android.view.Choreographer$CallbackRecord.run(Choreographer.Java:725)
        at Android.view.Choreographer.doCallbacks(Choreographer.Java:555)
        at Android.view.Choreographer.doFrame(Choreographer.Java:524)
        at Android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.Java:711)
        at Android.os.Handler.handleCallback(Handler.Java:615)
        at Android.os.Handler.dispatchMessage(Handler.Java:92)
        at Android.os.Looper.loop(Looper.Java:137)
        at Android.app.ActivityThread.main(ActivityThread.Java:4921)
        at Java.lang.reflect.Method.invokeNative(Native Method)
        at Java.lang.reflect.Method.invoke(Method.Java:511)
        at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:1027)
        at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:794)
        at dalvik.system.NativeStart.main(Native Method)

Code AdapterView:

private static class FileListAdapter extends RecyclerView.Adapter<FileHolder> {
    private final Context mContext;
    private final SparseBooleanArray mSelectedArray;
    private final List<String> mList;

    FileListAdapter(Context context, List<String> list, SparseBooleanArray selectedArray) {
        mList = list;
        mContext = context;
        mSelectedArray = selectedArray;
    }


    @Override
    public FileHolder onCreateViewHolder(ViewGroup viewGroup, int i) {

        View view = LayoutInflater.from(viewGroup.getContext()).inflate(
                R.layout.file_list_item, viewGroup, false);

        TextView tv = (TextView) view
                .findViewById(R.id.file_name_text);
        Typeface font = Typeface.createFromAsset(viewGroup.getContext().getAssets(),
                viewGroup.getContext().getString(R.string.roboto_regular));
        tv.setTypeface(font);

        return new FileHolder(view, tv);
    }

    @Override
    public void onBindViewHolder(FileHolder fileHolder, final int i) {

        String name = mList.get(i);

        // highlight view if selected
        setSelected(fileHolder.itemView, mSelectedArray.get(i));

        // Set text
        fileHolder.mTextView.setText(name);
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }
}

private static class FileHolder extends RecyclerView.ViewHolder {

    public final TextView mTextView;

    public FileHolder(View itemView, TextView tv) {
        super(itemView);
        mTextView = tv;
    }
}
52
jimmy0251

Edit: Le bogue est corrigé. Si vous obtenez toujours la même exception, assurez-vous de mettre à jour votre source de données de l'adaptateur uniquement à partir du thread principal et d'appeler la méthode de notification d'adaptateur appropriée après celle-ci.

Ancienne réponse: Il semble s'agir d'un bogue dans RecyclerView, il est rapporté ici et ici . Espérons que cela sera corrigé dans la prochaine version.

36
jimmy0251

Aucun problème pour moi. Utilisez NotifyDataSetChanged ();

public class MyFragment extends Fragment{

    private MyAdapter adapter;

    // Your code

    public void addArticle(){
        ArrayList<Article> list = new ArrayList<Article>();
        //Add one article in this list

        adapter.addArticleFirst(list); // or adapter.addArticleLast(list);
    }
}

public class ArticleAdapterRecycler extends RecyclerView.Adapter<ArticleAdapterRecycler.ViewHolder> {

    private ArrayList<Article> Articles = new ArrayList<Article>();
    private Context context;


    // Some functions from RecyclerView.Adapter<ArticleAdapterRecycler.ViewHolder>    

    // Add at the top of the list.

    public void addArticleFirst(ArrayList<Article> list) {
        Articles.addAll(0, list);
        notifyDataSetChanged();
    }

    // Add at the end of the list.

    public void addArticleLast(ArrayList<Article> list) {
        Articles.addAll(Articles.size(), list);
        notifyDataSetChanged();
    }
}
17
Cocorico

Il suffit d’interdire le défilement de RecyclerView lorsque les données changent.

Comme mon code:

mRecyclerView.setOnTouchListener(
        new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (mIsRefreshing) {
                    return true;
                } else {
                    return false;
                }
            }
        }
);

Plus d'informations sur: http://drakeet.me/recyclerview-bug-indexoutofboundsexception-inconsistency-detected-invalid-itval-item-position-solution

10
drakeet

Bien que la réponse acceptée donne quelques liens hypertextes utiles concernant ce problème, il n’est pas vrai que ce comportement de RecyclerView lors du défilement est un bogue .

Si vous voyez cette exception, vous oubliez probablement d’avertir l’adaptateur une fois que le contenu de RecyclerView est "modifié". Les gens appellent notifyDataSetChanged() uniquement après l’ajout d’un élément au jeu de données. Toutefois, l'incohérence se produit non seulement après le réapprovisionnement de l'adaptateur, mais également lorsque vous supprimez un élément ou effacez le jeu de données, vous devez actualiser la vue en informant l'adaptateur de cette modification:

public void refillAdapter(Item item) {

    adapter.add(item);
    notifyDataSetChanged();

}

public void cleanUpAdapter() {

    adapter.clear();
    notifyDataSetChanged(); /* Important */

}

Dans mon cas, j'ai essayé de nettoyer l'adaptateur dans onStop() et de le remplir dans onStart(). J'ai oublié d'appeler notifyDataSetChanged() après le nettoyage de l'adaptateur à l'aide de clear(). Ensuite, chaque fois que j'ai changé l'état de onStop() à onStart() et que j'ai rapidement fait défiler la RecyclerView pendant le rechargement du jeu de données, j'ai constaté cette exception. Si j'attendais la fin du rechargement sans défilement, il n'y aurait aucune exception puisque l'adaptateur peut être rétabli sans à-coups cette fois-ci.

En bref, la variable RecyclerView n'est pas cohérente lorsqu'elle est modifiée. Si vous essayez de faire défiler la vue pendant le traitement des modifications dans le jeu de données, vous voyez Java.lang.IndexOutOfBoundsException: Inconsistency detected. Pour éliminer ce problème, vous devez avertir l'adaptateur immédiatement après la modification du jeu de données.

7
Dorukhan Arslan

Le problème n’est certainement pas dû au défilement de recyclerview, mais il est lié à notifyDataSetChanged () . J'avais une vue de recycleur dans laquelle je changeais constamment de données, c'est-à-dire en ajoutant et en supprimant des données. J'appelais notifyDataSetChanged () à chaque fois que j'ajoutais des éléments à ma liste, mais ne rafraîchissait pas l'adaptateur à chaque fois que l'élément était supprimé ou la liste effacée.

Donc, pour réparer le:

Java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:12 at Android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.Java:5456)

J'ai appelé adapter.notifyDataSetChanged () après list.clear () , partout où cela était nécessaire.

if (!myList.isEmpty()) {
        myList.clear();
        myListAdapter.notifyDataSetChanged();
    }

Depuis lors, je n'ai jamais rencontré l'exception . J'espère que cela fonctionne de la même manière pour les autres. :)

5
Udit Kapahi

J'ai eu un problème similaire mais avec la suppression du contenu. Je voulais aussi garder les animations aussi. J'ai fini par utiliser le notifyRemove, puis en passant la plage. Cela semble résoudre tous les problèmes ...

public void deleteItem(int index) {
    try{
        mDataset.remove(index);
        notifyItemRemoved(index);
        notifyItemRangeRemoved(index,1);
    } catch (IndexOutOfBoundsException e){
        notifyDataSetChanged();
        e.printStackTrace();
    }
}

Semble travailler et se débarrasser de l'exception IOB ...

3
Phill Wiggins

Je réalise que pour moi cette exception survient lorsque deux événements se produisent simultanément, c.-à-d. 

1) Défilement de recyclerview 

2) jeu de données en cours de modification

Donc, j'ai résolu ce problème en désactivant le défilement jusqu'à ce que le notifydatasetchanged soit appelé.

leaderAdapter.notifyDataSetChanged();
pDialog.hide();

Pour désactiver le défilement, j'ai utilisé une boîte de dialogue de progression, dont la valeur setCancelable est false.

pDialog = new ProgressDialog(getActivity());
pDialog.setMessage("Please wait...");
pDialog.setCancelable(false);

L'astuce consiste à activer le défilement uniquement lorsque le jeu de données a été mis à jour.

3
Sparsh

Je faisais face au même problème, Java.lang.IndexOutOfBoundsException: incohérence détectée .

Créez Custom LinearLayoutManager

HPLinearLayoutManager.Java  

public class HPLinearLayoutManager extends LinearLayoutManager {

    public HPLinearLayoutManager(Context context) {
        super(context);
    }

    public HPLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public HPLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    /**
     * Magic here
     */
    @Override
    public boolean supportsPredictiveItemAnimations() {
        return false;
    }
}

créer instance of HPLinearLayoutManager.

HPLinearLayoutManager hpLinearLayoutManager = new HPLinearLayoutManager(mContext);
recyclerView.setLayoutManager(hpLinearLayoutManager);

J'espère que cela vous aiderait.

3
Hiren Patel

Ce problème vient à recyclerview si vous utilisez 

adapter.setHasStableIds(true);

si vous le définissez, supprimez-le et mettez à jour votre jeu de données à l'intérieur de l'adaptateur;
si le problème persiste, invalidez toutes les vues une fois que vous avez obtenu de nouvelles données, puis mettez à jour votre jeu de données.

3
ShockWave

J'ai réussi à utiliser cette suggestion en utilisant la suggestion de Cocorico dans une réponse précédente ( https://stackoverflow.com/a/26927186/3660638 ), mais il y a un problème: depuis que j'utilise une SortedList, j'utilise notifyDataSetChanged () chaque fois qu'il y a un changement dans les données (ajout, suppression, etc.) vous fait perdre les animations d'éléments que vous obtenez avec notifyItemXXXXX(position), donc ce que j'ai fini par faire, c'est de l'utiliser seulement lorsque je modifie les données par lot, comme:

public void addAll(SortedList<Entity> items) {
    movieList.beginBatchedUpdates();
    for (int i = 0; i < items.size(); i++) {
        movieList.add(items.get(i));
    }
    movieList.endBatchedUpdates();
    notifyDataSetChanged();
}  
2
miguel_rdp

Vous devez utiliser dans votre compte getitem 

public int getItemCount() {

            if (mList!= null)
                return mList.size();
            else
                return 0;
        }

Actualiser également la vue recycleur s'il vous plaît utiliser cette

if (recyclerView.getAdapter() == null) {

            recyclerView.setHasFixedSize(true);
            mFileListAdapter= new FileListAdapter(this);
            recyclerView.setAdapter(mFileListAdapter);
            recyclerView.setItemAnimator(new DefaultItemAnimator());
        } else {
            mFileListAdapter.notifyDataSetChanged();        

        }

En utilisant cette solution, vous ne pouvez pas résoudre le problème Vous utilisez simplement une condition à l'intérieur de onBindViewHolder pour résoudre le problème Java.lang.IndexOutOfBoundsException

 public void onBindViewHolder(FileHolder fileHolder, final int i) {
        if(i < mList.size)
        {
           String name = mList.get(i);       
           setSelected(fileHolder.itemView, mSelectedArray.get(i));
           fileHolder.mTextView.setText(name);
        }
    }
2
Sadh

J'ai reproduit ce problème . Cela s'est produit lorsque nous avons supprimé les éléments du thread d'arrière-plan de mList mais que nous n'avons pas appelé notifyDataSetChanged () Maintenant, si nous faisons défiler Cette exception est à venir.

Java.lang.IndexOutOfBoundsException: incohérence détectée. Position de l'article invalide 86 (offset: 86) .state: 100

Au départ, j'avais 100 éléments et j'ai retiré quelques éléments du fil d'arrière-plan.

On dirait que Recyclerview appelle getItemCount () lui-même pour valider l'état.

1
Vinod Kollipara

créer CustomLinearLayoutManager:

public class CustomLinearLayoutManager extends LinearLayoutManager {

public CustomLinearLayoutManager(Context context) {
        super(context);
    }

    public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public CustomLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean supportsPredictiveItemAnimations() {
        return false;
    }

    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();

        }
    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            return super.scrollVerticallyBy(dy, recycler, state);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }
}
1
Nhan Nguyen

Évitez notifyDatasetHasChanged () et procédez comme suit:

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, articles.size());
}
1
Bolling

Je modifie les données pour la variable RecyclerView en arrière-plan Thread. J'ai la même Exception que l'OP. J'ai ajouté ceci après avoir changé les données:

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

J'espère que ça aide

1

J'ai le même problème avec ce problème, je suis très fatigué de chercher et de le résoudre. Mais j'ai trouvé une réponse à résoudre et les exceptions n'ont pas encore été rejetées.

public class MyLinearLayoutManager extends LinearLayoutManager 
{
    public MyLinearLayoutManager(Context context) {
        super(context);
    }

    public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public MyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean supportsPredictiveItemAnimations() {
        return false;
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        //override this method and implement code as below
        try {
            super.onLayoutChildren(recycler, state);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

J'espère que cette réponse résoudra votre problème.

1
mainam

J'avais le même problème lorsque je configurais une nouvelle instance d'adaptateur en fonction de certains critères de sélection.

J'ai résolu mon problème en utilisant RecyclerView.swapAdapter(adapter, true) Lorsque nous définissons un nouvel adaptateur.

1
Dharmendra

il y a une phrase dans le code sonore:
/** * Utilisé lorsque LayoutState est construit dans un état de défilement. Cela devrait * être réglé la quantité de défilement que nous pouvons faire sans créer un nouveau. * vue. Paramètres requis pour un recyclage efficace des vues . */ int mScrollingOffset;

0
wl2112

J'ai également eu le même problème et je l'ai corrigé en n'utilisant pas la méthode notifyItemRangeChanged (). Il est bien expliqué à 

https://code.google.com/p/Android/issues/detail?id=77846#c10

0
Umanda

J'ai eu un problème similaire pendant que j'essayais d'ajouter le premier élément dans recyclerView avec la méthode notifyItemInserted, alors j'ai modifié la fonction addItem sur mon adaptateur comme indiqué ci-dessous et le problème a été résolu. 

Problème étrange, espérons qu'il sera bientôt résolu!

public void addItem(int position, TableItem item) {
    boolean firstEntry = false;
    if (items.size() == 0) {
        firstEntry = true;
    }

    items.add(position, item);

    if (firstEntry) {
        notifyDataSetChanged();
    } else {
        notifyItemInserted(position);
    }
}
0
yahya

Je ne vois rien de mal avec le code que vous avez posté. La seule chose qui me soit bizarre est cette ligne

setSelected(fileHolder.itemView, mSelectedArray.get(i));

dans votre méthode onBindViewHolder de l'adaptateur .. mettez-vous également à jour ce tableau lorsque vous modifiez la taille de votre liste d'éléments dans le tableau?

0
nunoh123

faites cela quand vous voulez ajouter une vue (commenotifyDataouaddViewou quelque chose comme ça)

if(isAdded()){ 
    // 
    //  add view like this.
    //
    //  celebrityActionAdapter.notifyItemRangeInserted(pageSize, 10);
    //
    //
}
0

essayez d'utiliser un indicateur booléen, initialisez-le à false et, dans la méthode OnRefresh, rendez-le vrai, effacez votre liste de données si indicateur est vrai juste avant d'y ajouter les nouvelles données et ensuite, le rendre faux.

votre code pourrait être comme ça

 private boolean pullToRefreshFlag = false ;
 private ArrayList<your object> 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() ; 
 }

}
0
Moaz H

Dans mon cas, cela a été résolu en changeant mRecyclerView.smoothScrollToPosition(0) en

mRecyclerView.scrollToPosition(0)
0
Krishna Meena