RecyclerView: incohérence détectée. Position de l'article non valide

271

Notre QA a détecté un bug: lors de la rotation de l'appareil Android (Droid Turbo), le crash lié à RecyclerView suivant s'est produit:

java.lang.IndexOutOfBoundsException: incohérence détectée. Position de l'élément non valide 2 (décalage: 2). État: 3

Pour moi, cela ressemble à une erreur interne à l'intérieur de RecyclerView, car je ne pense pas que cela puisse être causé directement par notre code ...

est-ce que quelqu'un à rencontré ce problème?

Quelle serait la solution?

Une solution de contournement brutale pourrait être de rattraper l'exception lorsqu'elle se produit et de recréer l'instance RecyclverView à partir de zéro, pour éviter de se retrouver avec un état corrompu.

Mais, si possible, j'aimerais mieux comprendre le problème (et peut-être le résoudre à sa source), au lieu de le masquer.

Le bogue n'est pas facile à reproduire, mais il est fatal lorsqu'il se produit.

La trace de pile complète:

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
KarolDepka
la source
2
Une question: dans quelle mesure votre repro est-elle cohérente? Je sais que c'est une erreur dans le code de google ici et ici . Mais cela peut être évité. Alors, cela se produit-il à chaque rotation?
VicVu
Salut. Cela n'arrive que rarement, mais quand cela arrive, cela est fatal à l'application.
KarolDepka
Merci pour les liens vers les bugs. Le premier semble plus pertinent que le second.
KarolDepka
1
Oui, je pense que votre meilleur pari est de ne pas autoriser les modifications de la liste lors de la rotation.
VicVu
1
Si vous pouviez reproduire facilement, je suggérerais d'imprimer la valeur de «getItemCount» avant tous les appels à «notifier *» ... vous pourriez découvrir que le nombre d'articles ne correspond pas à vos hypothèses.
Rich Ehmer

Réponses:

209

J'ai eu un problème (peut-être) lié - la saisie d'une nouvelle instance d'une activité avec RecyclerView, mais avec un adaptateur plus petit déclenchait ce crash pour moi.

RecyclerView.dispatchLayout() peut essayer de retirer des éléments de la ferraille avant d'appeler mRecycler.clearOldPositions() . La conséquence étant qu'il tirait des éléments du pool commun qui avaient des positions supérieures à la taille de l'adaptateur.

Heureusement, il ne fait cela que si PredictiveAnimationssont activés, donc ma solution était de sous-classer GridLayoutManager( LinearLayoutManagera le même problème et «corriger»), et de remplacer supportsPredictiveItemAnimations()pour retourner false:

/**
 * 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);
    }
}
Kas Hunt
la source
4
Cela a fonctionné pour moi et la désactivation des animations prédictives ne vous fait pas perdre les animations ensemble. Bravo.
Robert Liberatore
8
Merci beaucoup monsieur! Fonctionné instantanément avec LinearLayoutManager, ce qui m'a probablement fait gagner des jours.
levavare
8
Merci beaucoup. Cette solution est utilisée avec LinearLayoutManager.
Pruthviraj
8
Je pense que ce gars mérite que nous construisions une statue en l'honneur de sa précieuse aide ... C'est l'un des pires problèmes documentés sur le Web, mais il semble que beaucoup de développeurs rencontrent ce problème ... Je me demande simplement comment l' avez-vous trouvé être ignoré si PredictiveAnimations est faux, @KasHunt? Parce que le stacktrace est très flou ...
PAD
4
Quelqu'un sait comment résoudre ce problème sans ce hack? Parce que notifyDatasetChanged a été abandonné en faveur de DiffUtil
Anton Shkurenko
83

Dans mon cas (supprimer / insérer des données dans ma structure de données), j'avais besoin d'effacer le pool de recyclage, puis de notifier l'ensemble de données modifié!

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

MatejC
la source
6
Je ne dis pas normalement cela, MAIS MERCI BEAUCOUP. J'ai TOUT essayé de corriger ce plantage qui se produit sporadiquement lorsque je déplace rapidement un tas d'éléments sur la liste. J'ai passé littéralement probablement une semaine entière à essayer de résoudre ce problème. Je m'en suis éloigné pendant quelques mois pour essayer de donner à mon cerveau une chance de l'approcher différemment, puis j'ai trouvé cela lors de ma première tentative sur Google. Soyez bénis!
Chantell Osejo
Permettez-moi de vous procurer une tonne de cookies car vous méritez chacun d'entre eux. Je vous remercie.
antonis_st
pourquoi devez-vous faire cela?
dabluck
9
C'est une opération assez lourde qui va en quelque sorte à l'encontre du but du recyclage des vues.
gjsalot
@gjsalot Donc, si j'utilise ceci, cela peut-il causer des problèmes?
Sreekanth Karumanaghat
38

Utilisez notifyDataSetChanged()plutôt notifyItem...dans ce cas.

Khaintt
la source
5
Dans certains cas, c'est la voie à suivre. J'ai eu une situation où j'ai remplacé tous mes éléments, mais je n'étais pas honnête avec l'adaptateur, je lui ai seulement dit que j'avais inséré de nouveaux éléments (notifyItemRangeInsert), sans d'abord lui dire que j'avais également supprimé des éléments. L'adaptateur s'attendait alors à ce qu'il y ait plus d'articles qu'il n'y en avait réellement. Si vous utilisez l'une des méthodes de notification de l'adaptateur, attendez notifyDataSetChanged, telle que notifyItemRangeRemoved / Inserted / Updated, l'appelant a l'entière responsabilité de dire à l'adaptateur exactement ce qui a été modifié, ou vous pouvez vous retrouver avec cet "état incohérent".
JHH
19
Ce n'est pas du tout une solution.
Miha_x64
Ce n'est pas la voie à suivre. Si cela fonctionne, cela signifie que vous avez juste gâché la plage notifyItem...et la correction qui commenceront à fonctionner au lieu de restituer tous les éléments.
Ranjan
12

J'ai résolu cela en retardant la mRecycler.setAdapter(itemsAdapter)caisse après avoir ajouté tous les éléments à l'adaptateur avec mRecycler.addAll(items)et cela a fonctionné. Je ne sais pas pourquoi j'ai fait ça pour commencer, c'est à partir du code d'une bibliothèque que j'ai regardé et vu ces lignes dans le "mauvais ordre", je suis à peu près sûr que c'est bien cela, s'il vous plaît si quelqu'un peut le confirmer expliquer pourquoi c'est alors? Je ne sais pas si c'est une réponse valable même

Odaym
la source
Je pense que c'est la solution, une fois que j'ai retardé l'adaptateur, ça allait, je pense ... maintenant, il apparaît lorsque l'adaptateur est défini dans le fil d'interface utilisateur et y ajoute des éléments.
EngineSense
18
J'ai utilisé swapAdapter(adapter, true)au lieu de setAdapter(adapter)et cela a aidé.
frangulyan
11

J'ai eu un problème similaire mais pas exactement le même. Dans mon cas, à un moment donné, je nettoyais le tableau qui a été transmis à la vue d'ensemble

mObjects.clear();

et ne pas appeler notifyDataSetChanged, car je ne voulais pas que la vue de recyclage efface immédiatement les vues. Je remplissais de nouveau le tableau mObjects dans AsyncTask.

Aalap
la source
9

J'ai eu le même problème avec recyclerView. Je viens donc d'informer l'adaptateur du changement de jeu de données juste après l'effacement de la liste.

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

mList.addAll(newData);
mAdapter.notifyDataSetChanged();
Reza
la source
1
Cette simple erreur m'a fait perdre tellement de temps, merci beaucoup!
leb1755
7

J'ai le même problème. Cela s'est produit lorsque je faisais défiler rapidement et appelais l'API et mettais à jour les données.Après avoir tout essayé pour éviter un crash, j'ai trouvé une solution.

mRecyclerView.stopScroll();

Ça va marcher.

Anand Savjani
la source
Il s'agit d'une solution de contournement pas un correctif. Vous le forcez à arrêter le défilement. Bad UX
Dr. aNdRO
1
@ Dr.aNdRO: L'adaptateur doit définir la position et si vous continuez à faire défiler le recylerview, l'adaptateur ne peut pas définir les données qui sont la cause du crash. Ce n'est pas mal UX
Anand Savjani
1
faire sens. L'arrêt du défilement n'est pas mauvais ux depuis l'actualisation des données.
Sush
6

Je modifie les données de RecyclerViewl'arrière-plan Thread. J'ai obtenu la même chose Exceptionque 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

kashyap jimuliya
la source
Merci mec! c'est la seule réponse qui a du sens du point de vue du développement Android.
user347187
Bien que j'ai également résolu avec l'aide de view.recycler_view.post, j'ai utilisé notifyItemInserted. Dans mon cas, il s'agit déjà d'un thread d'interface utilisateur.
CoolMind
6

Cette erreur se produit lorsque la liste dans l'adaptateur s'efface lors du défilement de l'utilisateur qui modifie la position du support d'élément, la référence perdue entre la liste et l'élément sur l'interface utilisateur, une erreur se produit dans le prochain "notifyDataSetChanged" .

Réparer:

Passez en revue votre méthode de liste de mise à jour. Si vous faites quelque chose comme

mainList.clear();
...
mainList.add() or mainList.addAll()
...
notifyDataSetChanged();

===> Error occur

Comment réparer. Créez un nouvel objet de liste pour le traitement du tampon et attribuez-le à nouveau à la liste principale après cela

List res = new ArrayList();
…..
res.add();  //add item or modify list
….
mainList = res;
notifyDataSetChanged();

Merci à Nhan Cao pour cette grande aide :)

Ramesh Pokharel
la source
4

Mon problème a disparu après avoir modifié mon Adapterimplémentation pour utiliser une copie du tableau des éléments au lieu d'une référence. La setItems()méthode est appelée chaque fois que nous avons de nouveaux éléments à afficher dans le RecyclerView.

Au lieu de:

private class MyAdapter extends RecyclerView.Adapter<ItemHolder> {
     private List<MyItem> mItems;  

    (....)

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

J'ai fait:

void setItems(List<MyItem> items) {
    mItems = new ArrayList<>(items);
}
Miguel A. Gabriel
la source
Cela résoudra le problème, mais ne prendra-t-il pas deux fois la mémoire d'origine?
Sreekanth Karumanaghat
@ MiguelA.Gabriel cela affectera la performance? par exemple dans mon cas, je mets à jour le tableau de recylerview trop souvent, donc actuellement je le fais suggestionsRecyclerView.swapAdapter(new CandidatesAdapter(mSuggestions), true); et c'est mon constructeur public CandidatesAdapter(List<String> suggestionsList) { this.suggestionsList = new ArrayList<>(suggestionsList); }
Mateen Chaudhry
@ mateen-chaudhry Ce sera probablement le cas. Vous devrez le tester dans votre cas et décider, ou essayez d'utiliser une autre des solutions proposées. Comme je l'ai dit, ce n'est qu'une solution de contournement et cela fonctionne pour moi dans mon cas.
Miguel A. Gabriel
3

J'ai fait face à la même situation. Et cela a été résolu en ajoutant des codes avant d'effacer votre collection.

mRecyclerView.getRecycledViewPool().clear();

MagicDroidX
la source
3

Dans mon cas, je mettais à jour les éléments et appelais notifyDataSetChangedun thread non UI. La plupart du temps, cela fonctionnait, mais lorsque de nombreux changements se produisaient rapidement, cela plantait. Quand je l'ai fait, à la place, essentiellement

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

puis il a cessé de s'écraser.

Erhannis
la source
3

Il vous suffit de vider votre liste OnPostExecute()et non en faisantPull to Refresh

// Setup refresh listener which triggers new data loading
        swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {

                AsyncTask<String,Void,String> task = new get_listings();
                task.execute(); // clear listing inside onPostExecute

            }
        });

J'ai découvert que cela se produit lorsque vous faites défiler pendant une traction pour actualiser , car j'effaçais la liste avant le async task, ce qui entraîne java.lang.IndexOutOfBoundsException: Inconsistency detected.

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

De cette façon, vous ne terminerez pas avec une incohérence

Fahad
la source
2

Il peut également être lié à la configuration de l'adaptateur plusieurs fois en même temps. J'avais une méthode de rappel qui a été déclenchée 5-6 fois en même temps et je configurais l'adaptateur dans ce rappel afin que RecycledViewPool ne puisse pas gérer toutes ces données simultanément. C'est une grosse chance mais vous feriez mieux de le vérifier de toute façon.

Mustafa Güven
la source
1
oui même problème .. Mais solution? Vous donnez la raison seulement .. Comment réparer?
Ranjith Kumar,
@RanjithKumar, veuillez partager votre façon de résoudre le problème ci-dessus. Je l'ai résolu en utilisant mRecyclerView.getRecycledViewPool (). Clear (); avant notifyDataSetChanged et utilisation du bloc synchronisé autour de la fonction de mise à jour de l'adaptateur
Attiq ur Rehman
pouvez-vous s'il vous plaît jeter un oeil à mon code je pense que mon problème est comme le vôtre pouvez-vous m'aider stackoverflow.com/questions/50213362/…
Mateen Chaudhry
2

Utilisation

notifyDataSetChanged()

au lieu

notifyItemRangeInserted(0, YourArrayList.size())

dans ce cas.

Pankaj Talaviya
la source
1
mais ce n'est pas bon pour les performances non? notifyItemRangeInserred est meilleur, le problème ne réside pas ici
Derekyy
2

Pour résoudre ce problème, appelez simplement notifyDataSetChanged () avec une liste vide avant de mettre à jour la vue de recyclage.

Par exemple

//Method for refresh recycle view

    if (!hcpArray.isEmpty())

hcpArray.clear (); // La liste pour la vue de recyclage des mises à jour

adapter.notifyDataSetChanged();
EKN
la source
2
Pas une solution.
Miha_x64
@Milha Je n'ai obtenu aucune autre solution pour résoudre le problème de plantage. Mais la solution ci-dessus fonctionne pour moi. Si ce n'est pas une solution, dites-moi la solution appropriée.
EKN
Ça dépend. Vous pouvez essayer d'utiliser DiffUtil - un outil polyvalent pour mettre à jour le contenu de RecyclerView.
Miha_x64
2

Dans mon cas, je viens de supprimer la ligne avec setHasStableIds(true);

darkchaos
la source
Mais HasStableIds (true) améliore les performances du Rv, existe-t-il une solution alternative?
Sreekanth Karumanaghat
En fait, je pense que cela pourrait être dû à différentes raisons, donc il pourrait y avoir différentes solutions à ce problème en fonction de la cause première.
Sreekanth Karumanaghat
2

Dans mon cas, j'essayais de modifier le contenu de mon adaptateur sur un thread d'arrière-plan, mais j'ai appelé notify * sur le thread principal / ui.

Ce n'est pas possible! La raison pour laquelle notifier est forcée sur le thread principal est que la vue de recyclage veut que vous modifiez votre adaptateur de sauvegarde sur le thread principal, même sur la même pile d'appels.

Pour résoudre le problème, assurez-vous que chaque opération sur votre adaptateur ainsi que chaque notification ... est effectuée sur le thread ui / main !

Johannes Muenichsdorfer
la source
2
l'ajout d'éléments à la liste dans votre adaptateur doit se faire sur un fil d'arrière-plan et appeler notifier sur postexecute. l'ajout de données dans le thread ui fait geler l'application pendant quelques millisecondes ou secondes si l'ajout de nombreuses données
dione llorera
d'accord avec @dionellorera, il convient de préciser qu'un "changement dans le contenu de l'adaptateur" signifie spécifiquement la modification directe de toutes les données, qu'il s'agisse de valeurs primitives, de propriétés d'objets ou d'objets eux
OzzyTheGiant
2

J'ai rencontré cette méchante trace de pile avec les nouveaux composants d'architecture Android récemment. Essentiellement, j'ai une liste d'éléments dans mon ViewModel qui sont observés par mon fragment, en utilisant LiveData. Lorsque le ViewModel publie une nouvelle valeur pour les données, le fragment met à jour l'adaptateur, transmettant ces nouveaux éléments de données et notifiant à l'adaptateur qu'il y a eu des modifications.

Malheureusement, lors de la transmission des nouveaux éléments de données à l'adaptateur, je n'ai pas tenu compte du fait que le ViewModel et l'adaptateur pointeraient vers la même référence d'objet! Cela signifie que si je mets à jour les données et appellepostValue() depuis le ViewModel, il y a une très petite fenêtre où les données pourraient être mises à jour et l'adaptateur pas encore notifié!

Mon correctif consistait à instancier une nouvelle copie des éléments une fois transmis à l'adaptateur:

mList = new ArrayList<>(passedList);

Avec cette solution super simple, vous pouvez être assuré que les données de votre adaptateur ne changeront pas juste avant que votre adaptateur ne soit notifié.

Steve
la source
2

Ce n'est que la solution qui a fonctionné pour moi, même en essayant de nombreuses solutions ci-dessus.

1.) Intilization

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

2.) Écrivez cette méthode dans l'adaptateur

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

stockListModels -> cette liste est celle que vous utilisez dans l'adaptateur.

Ramkesh Yadav
la source
2

Pour moi, cela a fonctionné après avoir ajouté cette ligne de code:

mRecyclerView.setItemAnimator(null);
Fatih Gee
la source
2
ce n'est pas un correctif dans la plupart des cas, si vous voulez une animation, vous devez réécrire votre code d'adaptateur et trouver vos erreurs en notifiant les modifications
Dragos Rachieru
Cela fonctionne très bien pour moi.J'utilise un vrai adaptateur, donc je ne peux pas contrôler le flux et j'ai activé windowActivityTransitions dans un style qui cause ce problème, merci à vous de sauver ma journée.
Arul Mani
1

ce problème peut se produire lorsque vous essayez d'effacer votre liste, si vous voulez effacer votre liste de données, en particulier lorsque vous utilisez pull pour actualiser, essayez d'utiliser un indicateur booléen, initialisez-le comme faux et à l'intérieur de la méthode OnRefresh rendez-le vrai, effacez votre dataList si l'indicateur est vrai juste avant d'y ajouter les nouvelles données et ensuite de 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() ; 
 }

}
Moaz H
la source
1

J'ai eu un même problème auparavant. Enfin trouvé une solution de contournement pour cela

Ce que je fais est d'aviser l'adaptateur que l'élément a été supprimé, puis de notifier la plage de jeu de données de l'adaptateur modifiée

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

 }
Cheng
la source
1

J'ai rencontré un problème similaire et je l'ai découvert. J'ai codé en dur quelques exemples pour un cas de test, mais je ne me suis pas assuré qu'ils retournaient chacun un ID unique et cela a provoqué le crash ci-dessous pour moi. La correction des ID a résolu le problème, j'espère que cela aidera quelqu'un d'autre!

DocBot
la source
1

j'ai aussi eu une fois l'erreur:

Cause: j'essayais de mettre à jour une vue Recycler à partir d'une tâche asynchrone tout en essayant d'obtenir simultanément les anciens viewHolders supprimés;

Code: je génère des données en appuyant sur un bouton, logique comme suit

  1. Effacez les derniers éléments dans la vue du recycleur
  2. Appelez une tâche asynchrone pour générer des données
  3. OnPostExecute Mettre à jour la vue Recycler et NotifyDataSetChanged

Problème: chaque fois que je défile rapidement avant de générer mes données, je reçois

Incohérence détectée. Position de l'adaptateur de support de vue non valide ViewViewHolder java.lang.IndexOutOfBoundsException: incohérence détectée. Position de l'élément non valide 20 (décalage: 2). État: 3

Solution: au lieu d'effacer le RecyclerView avant de générer mes données, je le laisse à la place, puis je le remplace par les nouvelles données, l'appel NotifyDatasetChanged, comme indiqué ci-dessous;

       @Override
        protected void onPostExecute(List<Objects> o) {
            super.onPostExecute(o);
            recyclerViewAdapter.setList(o);
            mProgressBar.setVisibility(View.GONE);
            mRecyclerView.setVisibility(View.VISIBLE);
        }
EdgeDev
la source
pouvez-vous s'il vous plaît jeter un oeil à mon code je pense que mon problème est comme le vôtre [link] ( stackoverflow.com/questions/50213362/… )
Mateen Chaudhry
1

Supprimez simplement toutes les vues de votre gestionnaire de mise en page avant de le notifier. comme:

myLayoutmanager.removeAllViews();
BaBaX Ra
la source
Ça marche. J'ai eu un problème avec le chargement du défilement et le changement d'onglet.
Warwicky
1

Utiliser l' ListAdapter (androidx.recyclerview.widget.ListAdapter)appel adapter.submitList(null)avant d'appeler adapter.submitList(list):

adapter.submitList(null)
adapter.submitList(someDataList)
Sergey
la source
1

Cette exception levée sur API 19, 21 (mais pas nouvelle). Dans Kotlin coroutine, j'ai chargé des données (dans le fil d'arrière-plan) et dans le fil d'interface utilisateur ajouté et leur ai montré:

adapter.addItem(item)

Adaptateur:

var list: MutableList<Item> = mutableListOf()

init {
    this.setHasStableIds(true)
}

open fun addItem(item: Item) {
    list.add(item)
    notifyItemInserted(list.lastIndex)
}

Pour une raison quelconque, Android ne s'affiche pas assez rapidement ou autre chose, donc, je mets à jour une liste dans la postméthode de RecyclerView(ajouter, supprimer, mettre à jour les événements des éléments):

view.recycler_view.post { adapter.addItem(item) }

Cette exception est similaire à "Impossible d'appeler cette méthode dans un rappel de défilement. Les rappels de défilement peuvent être exécutés lors d'une passe de mesure et de mise en page où vous ne pouvez pas modifier les données RecyclerView. Tout appel de méthode susceptible de modifier la structure de RecyclerView ou le contenu de l'adaptateur doit être reporté sur le image suivante. ": Recyclerview - ne peut pas appeler cette méthode dans un rappel de défilement .

CoolMind
la source
0

J'ai trouvé que la définition de mRecycler.setLayoutFrozen (true); dans la méthode onRefresh du swipeContainer.

résolu le problème pour moi.

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

        }
    });
Mark Sheekey
la source
0

C'est un bug assez désagréable.

Pour gérer le clic de mon article, j'ai utilisé une implémentation RecyclerView.OnItemTouchListenersimilaire à la solution trouvée dans cette question .

Après plusieurs fois d'actualisation de la RecyclerViewsource de données de et en cliquant sur un élément, cela IndexOutOfBoundsExceptionferait planter mon application. Lorsqu'un élément est cliqué, le RecyclerViewlogiciel recherche en interne la vue sous-jacente correcte et lui redonne sa position. En vérifiant le code source, j'ai vu qu'il y en avait TasksetThreads prévu. Pour résumer l'histoire, il s'agit simplement d'un état illégal où deux sources de données sont mélangées et non synchronisées et le tout se déchaîne.

Sur cette base, j'ai supprimé mon implémentation du RecyclerView.OnItemTouchListeneret j'ai simplement pris le clic sur ViewHolderle Adaptermoi - même:

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
      }
    });

}

Ce n'est peut-être pas la meilleure solution, mais une solution sans crash pour le moment .. J'espère que cela vous fera gagner du temps :).

DroidBender
la source
La création d'un nouvel objet à chaque appel à onBind entraînera un grand nombre d'objets qui seront récupérés et l'utilisateur risque de se bloquer.
dephinera