RecyclerView: Wykryto niespójność. Nieprawidłowa pozycja elementu


271

Nasz dział kontroli jakości wykrył błąd: podczas obracania urządzenia z Androidem (Droid Turbo) miała miejsce następująca awaria związana z RecyclerView :

java.lang.IndexOutOfBoundsException: Wykryto niespójność. Nieprawidłowa pozycja 2 pozycji (przesunięcie: 2). Stan: 3

Dla mnie wygląda to na wewnętrzny błąd w RecyclerView, ponieważ nie mogę sobie wyobrazić, aby to było spowodowane bezpośrednio przez nasz kod ...

Czy ktoś napotkał ten problem?

Jakie byłoby rozwiązanie?

Brutalnym obejściem może być złapanie wyjątku, kiedy to nastąpi, i odtworzenie instancji RecyclverView od zera, aby uniknąć pozostawienia uszkodzonego stanu.

Ale jeśli to możliwe, chciałbym lepiej zrozumieć problem (i być może naprawić go u źródła), zamiast maskować.

Błąd nie jest łatwy do odtworzenia, ale jest fatalny w skutkach.

Pełny ślad stosu:

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

2
Pytanie: Jak spójne jest twoje repro? Wiem, że to błąd w kodzie Google tutaj i tutaj . Można tego jednak uniknąć. Czy to się dzieje przy każdym obrocie?
VicVu,

Cześć. Zdarza się to rzadko, ale gdy tak się dzieje, jest to fatalne dla aplikacji.
KarolDepka

Dzięki za linki do błędów. Pierwszy wydaje się bardziej odpowiedni niż drugi.
KarolDepka

1
Tak, myślę, że najlepiej jest po prostu nie zezwalać na zmiany widoku listy podczas rotacji.
VicVu

1
Jeśli mógłbyś się łatwo odtworzyć, sugerowałbym wydrukowanie wartości dla „getItemCount” przed wszystkimi wywołaniami „powiadom * *”… możesz odkryć, że liczba pozycji nie odpowiada twoim założeniom.
Rich Ehmer,

Odpowiedzi:


209

Miałem (prawdopodobnie) związany z tym problem - wejście w nową instancję działania za pomocą RecyclerView, ale z mniejszym adapterem spowodowało to dla mnie awarię.

RecyclerView.dispatchLayout()może spróbować wyciągnąć przedmioty ze złomu przed wezwaniem mRecycler.clearOldPositions(). Konsekwencją tego jest to, że wyciągał przedmioty ze wspólnej puli, które miały pozycje wyższe niż rozmiar adaptera.

Na szczęście robi to tylko wtedy, gdy PredictiveAnimationssą włączone, więc moim rozwiązaniem było podklasę GridLayoutManager( LinearLayoutManagerma ten sam problem i „naprawę”) i zastąpienie, supportsPredictiveItemAnimations()aby zwrócić 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);
    }
}

4
To zadziałało dla mnie, a wyłączenie animacji predykcyjnych nie powoduje, że tracisz je wszystkie razem. Brawo.
Robert Liberatore,

8
Dziękuję Panu bardzo! Natychmiastowo współpracowałem z LinearLayoutManager, prawdopodobnie oszczędzając mi dni.
levavare

8
Wielkie dzięki. To rozwiązanie działa z LinearLayoutManager.
Pruthviraj

8
Myślę, że ten facet zasługuje na zbudowanie statuy na cześć jego cennej pomocy ... To jeden z najgorzej udokumentowanych problemów w sieci, ale wydaje się, że wielu deweloperów napotyka ten problem ... Zastanawiam się, jak to się stało należy pominąć, jeśli PredictiveAnimations są fałszywe, @KasHunt? Ponieważ śledzenie stosu jest bardzo niejasne ...
PAD

4
Ktoś wie, jak to naprawić bez tego hacka? Ponieważ powiadomienie DataDatasetChanged zostało odrzucone na korzyść DiffUtil
Anton Shkurenko

83

W moim przypadku (usuń / wstaw dane w mojej strukturze danych) musiałem wyczyścić pulę recyklingu, a następnie powiadomić o zmianie zestawu danych!

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


6
Zwykle tego nie mówię, ALE DZIĘKUJĘ TAK DUŻO. Próbowałem WSZYSTKO, aby naprawić tę awarię, która zdarza się sporadycznie, gdy szybko przesuwam kilka pozycji na liście. Dosłownie cały tydzień spędziłem na próbach rozwiązania tego problemu. Odszedłem od tego na kilka miesięcy, aby dać mózgowi szansę podejść do tego inaczej, a potem znalazłem to przy pierwszej próbie Google. Na zdrowie!
Chantell Osejo

Pozwól, że przyniosę ci mnóstwo plików cookie, ponieważ zasługujesz na każdy z nich. Dziękuję Ci.
antonis_st

dlaczego musisz to zrobić
dabluck

9
To dość ciężka operacja, która w pewnym sensie nie pozwala na przetworzenie widoków.
gjsalot

@gjsalot Więc jeśli go użyję, czy może to powodować problemy?
Sreekanth Karumanaghat

38

W takim przypadku użyj notifyDataSetChanged()zamiast notifyItem...tego.


5
W niektórych przypadkach jest to właściwy sposób. Miałem sytuację, w której wymieniłem wszystkie moje elementy, ale nie byłem szczery z adapterem, tylko powiedziałem mu, że wstawiłem kilka nowych elementów (powiadomienieItemRangeInserted), bez uprzedniej informacji, że usunąłem również elementy. Adapter spodziewał się wtedy, że będzie więcej elementów niż w rzeczywistości. Jeśli przy użyciu dowolnej z metod powiadamiania adaptera oczekuje się funkcji powiadomieńDataSetChanged, takich jak powiadomienieItemRangeRemoved / Wstawiony / Zaktualizowany, osoba dzwoniąca ponosi pełną odpowiedzialność za poinformowanie adaptera dokładnie o tym , co zostało zmienione, w przeciwnym razie może dojść do „niespójnego stanu”.
JHH,

19
To wcale nie jest rozwiązanie.
Miha_x64

To nie jest droga. Jeśli to działa, oznacza to, że po prostu zawiodłeś zakres notifyItem...i poprawianie, które zacznie działać zamiast ponownego renderowania wszystkich elementów.
Ranjan

12

Rozwiązałem to, opóźniając mRecycler.setAdapter(itemsAdapter)kasę po dodaniu wszystkich elementów do adaptera mRecycler.addAll(items)i działało. Nie mam pojęcia, dlaczego to zrobiłem, zacząłem od kodu biblioteki, który przejrzałem i zobaczyłem te wiersze w „niewłaściwej kolejności”, ale jestem pewien, że to jest to, proszę, jeśli ktoś może to potwierdzić, wyjaśnić, dlaczego to jest więc? Nie jestem pewien, czy jest to nawet poprawna odpowiedź


Myślę, że to jest rozwiązanie, kiedy raz opóźniłem adapter, było w porządku, wierzę ... teraz wyskakuje, gdy ustawia adapter w wątku interfejsu użytkownika i dodaje do niego elementy.
EngineSense

18
Użyłem swapAdapter(adapter, true)zamiast setAdapter(adapter)i to pomogło.
francja

11

Miałem podobny problem, ale nie dokładnie taki sam. W moim przypadku w 1 punkcie wyczyściłem tablicę, która została przekazana do widoku recyklera

mObjects.clear();

i nie wywoływanie powiadomieniaDataSetChanged, ponieważ nie chciałem, aby widok recyklera natychmiast wyczyścił widoki. Ponownie wypełniałem tablicę mObjects w AsyncTask.


9

Miałem ten sam problem z recyclinglerView Więc właśnie powiadomiłem adapter o zmianie zestawu danych zaraz po wyczyszczeniu listy.

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

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

1
Ten prosty błąd sprawił, że straciłem tyle czasu, dziękuję bardzo!
leb1755,

7

Mam ten sam problem. Wystąpiło, gdy przewijałem szybko i dzwoniłem do API i aktualizowałem dane. Po wypróbowaniu wszystkich rzeczy, aby zapobiec awarii, znalazłem rozwiązanie.

mRecyclerView.stopScroll();

To będzie działać.


Jest to obejście, a nie poprawka. Zmuszasz go do zatrzymania przewijania. Bad UX
Dr. aNdRO,

1
@ Dr.aNdRO: Adapter musi ustawić pozycję, a jeśli kontynuujesz przewijanie widoku recylerview, adapter nie może ustawić danych, które są przyczyną awarii. Nie jest źle UX
Anand Savjani

1
ma sens. Zatrzymanie przewijania nie jest złe, ponieważ odświeżanie danych ma miejsce.
Sush

6

Zmieniam dane RecyclerVieww tle Thread. Mam to samo Exceptionco OP. Dodałem to po zmianie danych:

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

Mam nadzieję, że to pomoże


dzięki! to jedyna odpowiedź, która ma sens z punktu widzenia rozwoju Androida.
user347187

Chociaż rozwiązałem również za pomocą view.recycler_view.post, użyłem notifyItemInserted. W moim przypadku był to już wątek interfejsu użytkownika.
CoolMind

6

Ten błąd występuje, gdy lista w adapterze zostaje wyczyszczona podczas przewijania przez użytkownika, co powoduje zmianę pozycji uchwytu elementu, utratę referencji między listą a pozycją w interfejsie użytkownika, błąd pojawia się w następnym żądaniu „replaceDataSetChanged” .

Naprawić:

Sprawdź metodę listy aktualizacji. Jeśli zrobisz coś takiego

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

===> Error occur

Jak naprawić. Utwórz nowy obiekt listy do przetwarzania bufora i następnie przypisz ponownie do listy głównej

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

Dzięki Nhan Cao za tę wspaniałą pomoc :)


4

Mój problem zniknął po zmodyfikowaniu mojej Adapterimplementacji w celu użycia kopii tablicy elementów zamiast odwołania. setItems()Metoda jest wywoływana za każdym razem mamy nowych elementów do pokazania w RecyclerView.

Zamiast:

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

    (....)

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

Zrobiłem:

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

To rozwiąże problem, ale czy nie zajmie to dwukrotnie więcej pamięci niż oryginalna?
Sreekanth Karumanaghat

@ MiguelA.Gabriel wpłynie to na wydajność? na przykład w moim przypadku zbyt często aktualizuję tablicę recylerview, więc obecnie to robię suggestionsRecyclerView.swapAdapter(new CandidatesAdapter(mSuggestions), true); i to jest mój konstruktor public CandidatesAdapter(List<String> suggestionsList) { this.suggestionsList = new ArrayList<>(suggestionsList); }
Mateen Chaudhry

@ mateen-chaudhry Prawdopodobnie tak będzie. Musisz to przetestować w swoim przypadku i zdecydować lub spróbować użyć innego z proponowanych rozwiązań. Jak powiedziałem, jest to tylko obejście problemu i działa w moim przypadku.
Miguel A. Gabriel

3

Mam do czynienia z tą samą sytuacją. Zostało to rozwiązane poprzez dodanie kodów przed wyczyszczeniem kolekcji.

mRecyclerView.getRecycledViewPool().clear();


3

W moim przypadku aktualizowałem elementy i dzwoniłem notifyDataSetChangedw wątku innym niż interfejs użytkownika. Przez większość czasu działało, ale gdy wiele zmian nastąpiło szybko, zawalił się. Kiedy to zrobiłem, w zasadzie

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

potem przestało się zawieszać.


3

Musisz tylko wyczyścić listę, OnPostExecute()a nie robić toPull 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

            }
        });

Odkryłem, że dzieje się tak, gdy przewijasz podczas ściągania, aby odświeżyć , ponieważ wyczyściłem listę przed async task, w wyniku czego java.lang.IndexOutOfBoundsException: Inconsistency detected.

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

W ten sposób nie skończysz z niekonsekwencją


2

Można to również powiązać z wielokrotnym ustawianiem adaptera jednocześnie. Miałem metodę wywołania zwrotnego, która była wyzwalana 5-6 razy w tym samym czasie i ustawiałem adapter w tym wywołaniu zwrotnym, aby RecycledViewPool nie mógł obsługiwać wszystkich tych danych jednocześnie. To wielka szansa, ale i tak lepiej to sprawdzić.


1
tak ten sam problem .. Ale rozwiązanie? Podajesz tylko powód ... Jak to naprawić?
Ranjith Kumar

@RanjithKumar, uprzejmie podziel się swoją drogą, aby rozwiązać powyższy problem. Rozwiązałem go za pomocą mRecyclerView.getRecycledViewPool (). Clear (); przed powiadomieniemDataSetChanged i przy użyciu zsynchronizowanego bloku wokół funkcji aktualizacji adaptera
Attiq ur Rehman

czy możesz spojrzeć na mój kod, myślę, że mój problem jest podobny do twojego, czy możesz mi pomóc stackoverflow.com/questions/50213362/...
Mateen Chaudhry

2

Posługiwać się

notifyDataSetChanged()

zamiast

notifyItemRangeInserted(0, YourArrayList.size())

w tym przypadku.


1
ale to nie jest dobre dla wydajności, prawda? powiadomićItemRangeInserted jest lepszy, problem nie leży tutaj
Derekyy

2

Aby rozwiązać ten problem, po prostu wywołaj powiadomienieDataSetChanged () z pustą listą przed aktualizacją widoku kosza.

Na przykład

//Method for refresh recycle view

    if (!hcpArray.isEmpty())

hcpArray.clear (); // Lista widoków przeglądu aktualizacji

adapter.notifyDataSetChanged();

2
Nie rozwiązanie.
Miha_x64

@Milha Nie znalazłem żadnego innego rozwiązania, aby naprawić problem awarii. Ale powyższe rozwiązanie działa dla mnie. Jeśli nie jest to rozwiązanie, powiedz mi właściwą poprawkę.
EKN,

To zależy. Możesz spróbować użyć DiffUtil - narzędzia ogólnego zastosowania do aktualizacji zawartości RecyclerView.
Miha_x64,

2

W moim przypadku właśnie usunąłem linię z setHasStableIds(true);


Ale HasStableIds (true) poprawia wydajność Rv, czy istnieje jakieś alternatywne rozwiązanie?
Sreekanth Karumanaghat

Właściwie myślę, że może to wynikać z różnych przyczyn, więc mogą istnieć różne rozwiązania tego problemu w zależności od tego, co jest jego główną przyczyną.
Sreekanth Karumanaghat

2

W moim przypadku próbowałem zmienić zawartość adaptera w wątku w tle, ale nazwałem powiadomienie * w wątku main / ui.

Nie jest możliwe! Powodem, dla którego powiadomienie jest zmuszane do głównego wątku, jest to, że widok recyklera chce, abyś edytował adapter kopii zapasowej w głównym wątku, nawet na tym samym stosie wywołań.

Aby rozwiązać problem, upewnij się, że każda operacja na twoim adapterze, a także każde powiadomienie ... jest wykonywane w wątku interfejsu użytkownika / głównym !


2
dodawanie elementów do listy w adapterze powinno odbywać się w wątku w tle, a powiadomienie o połączeniu należy wykonać po zakończeniu. dodanie danych w wątku interfejsu użytkownika powoduje zawieszenie aplikacji na milisekundę lub sekundy, jeśli dodaje się wiele danych
dione llorera

uzgodniono z @dionellorera, należy wyjaśnić, że „zmiana zawartości adaptera” oznacza konkretnie bezpośrednią modyfikację dowolnych danych, czy to pierwotnych wartości, właściwości obiektów, czy samych obiektów
OzzyTheGiant

2

Niedawno natrafiłem na ten paskudny ślad stosu z nowymi komponentami architektury Androida. Zasadniczo mam listę elementów w moim ViewModel, które są obserwowane przez mój Fragment, używając LiveData. Gdy ViewModel publikuje nową wartość danych, Fragment aktualizuje adapter, przekazując te nowe elementy danych i powiadamiając adapter o zmianach.

Niestety, przekazując nowe elementy danych do adaptera, nie uwzględniłem faktu, że zarówno ViewModel, jak i Adapter wskazywałyby na to samo odwołanie do obiektu! Oznacza to, że jeśli zaktualizuję dane i zadzwoniępostValue() z poziomu ViewModel, pojawi się bardzo małe okno, w którym dane mogą zostać zaktualizowane, a karta jeszcze nie powiadomiona!

Moją poprawką było utworzenie nowej kopii elementów po przekazaniu do adaptera:

mList = new ArrayList<>(passedList);

Dzięki tej bardzo łatwej poprawce możesz mieć pewność, że dane adaptera nie zmienią się, dopóki nie zostanie powiadomiony o tym adapter.


2

To jest jedyne rozwiązanie, które działało dla mnie, nawet próbując wielu z powyższych rozwiązań.

1.) Intilizacja

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

2.) Napisz tę metodę w adapterze

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

stockListModels -> ta lista jest używana w adapterze.


2

Dla mnie zadziałało po dodaniu następującego wiersza kodu:

mRecyclerView.setItemAnimator(null);

2
w większości przypadków nie jest to poprawka, jeśli chcesz animacji, musisz przepisać kod adaptera i znaleźć błędy w powiadamianiu o zmianach
Dragos Rachieru

Działa dla mnie dobrze. Używam prawdziwego adaptera, więc nie mogę kontrolować przepływu i włączyłem windowActivityTransitions w stylu, który powoduje ten problem dzięki człowiekowi, że uratowałeś mi dzień.
Arul Mani

1

ten problem może wystąpić podczas próby wyczyszczenia listy, jeśli zamierzasz wyczyścić listę danych, zwłaszcza gdy używasz metody pull do odświeżenia, spróbuj użyć flagi boolean, zainicjuj ją jako false, a wewnątrz metody OnRefresh spraw, aby była prawdziwa, wyczyść swoją listę danych jeśli flaga jest prawdziwa tuż przed dodaniem do niej nowych danych, a następnie ustaw ją jako fałsz.

twój kod może być taki

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

}

1

Wcześniej miałem ten sam problem. W końcu znalazłem obejście tego problemu

Chcę powiadomić adapter, że element został usunięty, a następnie powiadomić o zmianie zestawu danych adaptera

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

 }

1

Natknąłem się na podobny problem i właśnie go rozgryzłem. Na stałe zapisałem kilka przykładów dla przypadku testowego, ale nie upewniłem się, że każdy zwrócił unikalny identyfikator, co spowodowało dla mnie poniższą awarię. Naprawienie identyfikatorów rozwiązało problem, mam nadzieję, że pomoże to komuś innemu!


1

raz też dostałem błąd:

Przyczyna: Próbowałem zaktualizować widok Recycler View z zadania Async, jednocześnie próbując odzyskać stare usunięte viewHolders;

Kod: Generuję dane jednym naciśnięciem przycisku, logika jest następująca

  1. Wyczyść ostatnie elementy w widoku recyklera
  2. Wywołaj zadanie asynchroniczne, aby wygenerować dane
  3. OnPostExecute Zaktualizuj widok Recycler i NotifyDataSetChanged

Problem: Ilekroć przewijam szybko przed wygenerowaniem moich danych, dostaję

Wykryto niespójność. Nieprawidłowa pozycja adaptera uchwytu widokuViewHolder java.lang.IndexOutOfBoundsException: Wykryto niespójność. Nieprawidłowa pozycja 20 (przesunięcie: 2). Stan: 3

Rozwiązanie: zamiast wyczyścić RecyclerView przed wygenerowaniem moich danych, zamiast tego zostawiam go, a następnie zastępuję nowymi danymi, Call NotifyDatasetChanged, jak pokazano poniżej;

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

czy możesz spojrzeć na mój kod, myślę, że mój problem jest podobny do twojego [link] ( stackoverflow.com/questions/50213362/... )
Mateen Chaudhry

1

Po prostu usuń wszystkie widoki swojego menedżera układu przed powiadomieniem. lubić:

myLayoutmanager.removeAllViews();

To działa. Miałem problem z ładowaniem przewijania i zmianą tabulatora.
Warwicky

1

Korzystanie z ListAdapter (androidx.recyclerview.widget.ListAdapter)połączenia adapter.submitList(null)przed połączeniem adapter.submitList(list):

adapter.submitList(null)
adapter.submitList(someDataList)

0

Znalazłem to ustawienie mRecycler.setLayoutFrozen (true); w metodzie onRefresh swipeContainer.

rozwiązał problem dla mnie.

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

        }
    });

0

To dość paskudny błąd.

Aby obsłużyć kliknięcie elementu, użyłem implementacji RecyclerView.OnItemTouchListenerpodobnej do rozwiązania znalezionego w tym pytaniu .

Po wielokrotnym odświeżeniu RecyclerViewźródła danych i kliknięciu elementu IndexOutOfBoundsExceptionspowoduje to awarię mojej aplikacji. Po kliknięciu elementu RecyclerViewwewnętrzny szuka właściwego widoku podstawowego i przywraca mu pozycję. Sprawdzając kod źródłowy, zauważyłem, że było trochę TasksiThreads zaplanowane. Krótko mówiąc, jest to po prostu jakiś nielegalny stan, w którym dwa źródła danych są ze sobą połączone i nie są zsynchronizowane, a wszystko szaleje.

Na tej podstawie Zdjąłem wdrożenie systemu RecyclerView.OnItemTouchListeneri po prostu złapał kliknij na ViewHolderna Adaptersobie:

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

}

To może nie być najlepsze rozwiązanie, ale na razie bezproblemowe. Mam nadzieję, że pozwoli ci to zaoszczędzić trochę czasu :).


Tworzenie nowego obiektu za każdym razem, gdy zostanie wywołane onBind, spowoduje, że wiele obiektów zostanie wyrzuconych, a użytkownik może się zawiesić.
dephinera

0

Lint dał mi radę dotyczącą niespójności: napisałem (onBindViewHolder ()):

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

który musiał zostać zastąpiony przez:

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

Uruchom oba kody w swoim kodzie, a następnie uruchom Lint, aby uzyskać pełne wyjaśnienie !!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.