Recycler-Ansicht: Inkonsistenz festgestellt. Ungültiger Ansichtshalteradapter positionViewHolder

78

Inkonsistenz der Recycler-Ansicht Erkannter Fehler beim schnellen Scrollen oder beim Laden weiterer Elemente.

FATAL EXCEPTION: main
Process: com.pratap.endlessrecyclerview, PID: 21997
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{56a082c position=40 id=-1, oldPos=39, pLpos:39 scrap [attachedScrap] tmpDetached no parent}
at android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:4251)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4382)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4363)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1961)
at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1370)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1333)
at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:562)
at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:2864)
at android.support.v7.widget.RecyclerView.consumePendingUpdateOperations(RecyclerView.java:1445)
at android.support.v7.widget.RecyclerView.access$400(RecyclerView.java:144)
at android.support.v7.widget.RecyclerView$1.run(RecyclerView.java:282)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
at android.view.Choreographer.doCallbacks(Choreographer.java:670)
at android.view.Choreographer.doFrame(Choreographer.java:603)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
at android.os.Handler.handleCallback(Handler.java:746)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5443)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:728)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)

Adapter

public class DataAdapter extends RecyclerView.Adapter {
    private final int VIEW_ITEM = 1;
    private final int VIEW_PROG = 0;

    private List<Feed> mFeed;
    // The minimum amount of items to have below your current scroll position
    // before loading more.
    private int visibleThreshold = 5;
    private int lastVisibleItem, totalItemCount;
    private boolean loading;
    private OnLoadMoreListener onLoadMoreListener;

    public DataAdapter(List<Feed> feeds, RecyclerView recyclerView) {

        mFeed = feeds;

        if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {

            final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView
                .getLayoutManager();

            recyclerView
                .addOnScrollListener(new RecyclerView.OnScrollListener() {
                    @Override
                    public void onScrolled(RecyclerView recyclerView,
                        int dx, int dy) {
                        super.onScrolled(recyclerView, dx, dy);

                        totalItemCount = linearLayoutManager.getItemCount();
                        lastVisibleItem = linearLayoutManager
                            .findLastVisibleItemPosition();
                        if (!loading
                            && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                            // End has been reached
                            // Do something
                            if (onLoadMoreListener != null) {
                                onLoadMoreListener.onLoadMore();
                            }
                            loading = true;
                        }
                    }
                });
        }
    }

    @Override
    public int getItemViewType(int position) {
        return mFeed.get(position) == null ? VIEW_PROG : VIEW_ITEM;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,
        int viewType) {
        RecyclerView.ViewHolder vh;
        if (viewType == VIEW_ITEM) {
            View v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.list_row, parent, false);

            vh = new StudentViewHolder(v);
        }
        else {
            View v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.progress_item, parent, false);

            vh = new ProgressViewHolder(v);
        }
        return vh;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof StudentViewHolder) {

            Feed singleStudent= (Feed) mFeed.get(position);
            ((StudentViewHolder) holder).tvName.setText(singleStudent.getTitle());
            ((StudentViewHolder) holder).student= singleStudent;
        } else {
            ProgressViewHolder.PROGRESS_BAR.setIndeterminate(true);
        }
    }

    public void setLoaded() {
        loading = false;
    }

    public void  addFeed(Feed feed) {
        mFeed.add(feed);
        //mFeed.addAll(0, (Collection<? extends Feed>) feed);
        notifyItemInserted(mFeed.size());
        //notifyItemRangeInserted(0,mFeed.size());
        notifyDataSetChanged();
        //notifyItemInserted(mFeed.size());
        //setLoaded();
        //notifyItemInserted(mFeed.size());
    }

    public void removeAll(){
        mFeed.clear();
        notifyDataSetChanged();
    }

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

    public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
        this.onLoadMoreListener = onLoadMoreListener;
    }

    public static class StudentViewHolder extends RecyclerView.ViewHolder {
        public TextView tvName;

        public Feed student;
        public StudentViewHolder(View v) {
            super(v);
            tvName = (TextView) v.findViewById(R.id.tvName);

            //tvEmailId = (TextView) v.findViewById(R.id.tvEmailId);
        }
    }

    public static class ProgressViewHolder extends RecyclerView.ViewHolder {
        //public ProgressBar progressBar;
        public static ProgressBar PROGRESS_BAR;
        public ProgressViewHolder(View v) {
            super(v);
            PROGRESS_BAR = (ProgressBar) v.findViewById(R.id.progressBar1);
            //  progressBar = (ProgressBar) v.findViewById(R.id.progressBar1);
        }
    }
}

Aktivität

public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {

    private Toolbar toolbar;

    private TextView tvEmptyView;
    private RecyclerView mRecyclerView;
    private DataAdapter mAdapter;
    private LinearLayoutManager mLayoutManager;
    private RestManager mManager;
    private List<Feed> mFeed;
    SwipeRefreshLayout mSwipeRefreshLayout;
    protected Handler handler;
    private int currentPage=1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        toolbar = (Toolbar) findViewById(R.id.toolbar);
        tvEmptyView = (TextView) findViewById(R.id.empty_view);
        mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
        mSwipeRefreshLayout= (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
        mSwipeRefreshLayout.setOnRefreshListener(this);
        //studentList = new ArrayList<Student>();
        mFeed = new ArrayList<Feed>();
        handler = new Handler();
        if (toolbar != null) {
            setSupportActionBar(toolbar);
            getSupportActionBar().setTitle("Android Students");

        }
        mManager = new RestManager();

        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        mRecyclerView.setHasFixedSize(true);

        mLayoutManager = new LinearLayoutManager(this);

        // use a linear layout manager
        mRecyclerView.setLayoutManager(mLayoutManager);

        // create an Object for Adapter
        mAdapter = new DataAdapter(mFeed,mRecyclerView);

        // set the adapter object to the Recyclerview
        mRecyclerView.setAdapter(mAdapter);
        //   mAdapter.notifyDataSetChanged();

        loadData(false);

        //        if (mFeed.isEmpty()) {
        //            mRecyclerView.setVisibility(View.GONE);
        //            tvEmptyView.setVisibility(View.VISIBLE);
        //
        //        } else {
        //            mRecyclerView.setVisibility(View.VISIBLE);
        //            tvEmptyView.setVisibility(View.GONE);
        //        }

        mAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore() {
                //add null , so the adapter will check view_type and show progress bar at bottom
                mFeed.add(null);
                mAdapter.notifyItemInserted(mFeed.size() - 1);

                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //   remove progress item
                        mFeed.remove(mFeed.size() - 1);
                        // mAdapter.notifyItemRemoved(mFeed.size());
                        //add items one by one
                        int start = mFeed.size();
                        currentPage++;

                        Log.d("CurrentPage", String.valueOf(currentPage));
                        Call<Results> listCall = mManager.getFeedApi().getAllFeeds(1);

                        listCall.enqueue(new Callback<Results>() {

                            @Override
                            public void onResponse(Call<Results> call, Response<Results> response) {
                                mSwipeRefreshLayout.setRefreshing(false);
                                if (response.isSuccess()) {
                                    if (response.body() != null) {
                                        Results feedList = response.body();

                                        // List<Results> newUsers = response.body();

                                        Log.d("Retrofut", String.valueOf(feedList));

                                        for (int i = 0; i < feedList.results.size(); i++) {
                                            Feed feed = feedList.results.get(i);
                                            // mFeed.add(feed);
                                            mAdapter.addFeed(feed);
                                            //                                        mAdapter.notifyDataSetChanged();


                                            //mAdapter.notifyItemInserted(mFeed.size());

                                        }
                                        //    mAdapter.notifyDataSetChanged();
                                    }
                                }
                            }

                            @Override
                            public void onFailure(Call<Results> call, Throwable t) {
                                Log.d("Retrofut", "Error");
                                mFeed.remove(mFeed.size() - 1);
                                mAdapter.notifyItemRemoved(mFeed.size());

                                mAdapter.setLoaded();
                                mSwipeRefreshLayout.setRefreshing(false);
                            }
                        });

                        //        for (int i = 1; i <= 20; i++) {
                        //            studentList.add(new Student("Student " + i, "androidstudent" + i + "@gmail.com"));
                        //
                        //        }

                        mAdapter.setLoaded();
                        //or you can add all at once but do not forget to call mAdapter.notifyDataSetChanged();

                    }
                }, 2000);
            }
        });
    }

    // load initial data
    private void loadData(final boolean removePreData) {

        Call<Results> listCall = mManager.getFeedApi().getAllFeeds(1);

        listCall.enqueue(new Callback<Results>() {

                             @Override
                             public void onResponse(Call<Results> call, Response<Results> response) {

                                 if (response.isSuccess()) {
                                     if (response.body() != null) {
                                         //  if(removePreData) mAdapter.removeAll();
                                         Results feedList = response.body();
                                         Log.d("Retrofut", String.valueOf(feedList));

                                         for (int i = 0; i < feedList.results.size(); i++) {
                                             Feed feed = feedList.results.get(i);
                                             // mFeed.add(feed);
                                             //mAdapter.notifyDataSetChanged();
                                             mAdapter.addFeed(feed);
                                         }

                                         mSwipeRefreshLayout.setRefreshing(false);
                                     }
                                 }
                             }

                             @Override
                             public void onFailure(Call<Results> call, Throwable t) {
                                 Log.d("Retrofut", String.valueOf(t));
                                 mFeed.remove(mFeed.size() - 1);
                                 mAdapter.notifyItemRemoved(mFeed.size());
                                 mAdapter.setLoaded();
                                 mSwipeRefreshLayout.setRefreshing(false);
                             }
                         }
        );

        //        for (int i = 1; i <= 20; i++) {
        //            studentList.add(new Student("Student " + i, "androidstudent" + i + "@gmail.com"));
        //
        //        }

        mSwipeRefreshLayout.setRefreshing(true);
    }

    @Override
    public void onRefresh() {
        mFeed.clear();
        mAdapter.notifyDataSetChanged();
        loadData(true);
        currentPage=1;
    }
}
Prashanth
quelle
Ich habe den gleichen Fehler, der auftritt, weil ich einen endlosen Recycler verwendet habe, da meine Daten an die letzte Position gelangen. Sie erhalten nicht die Anzahl der Artikel. Wenn Sie sie so einstellen, erhalten Sie 27 Artikel in jeder Schriftrolle Holen Sie sich 27 Elemente, so dass es dann danach auftritt, wenn ich bekomme, was das Problem gerade erstellt Logik, dass, wenn ich (%) list.size () von Element modifiziere, ich erhalte, wenn es gleich 0 ist, verwenden Sie einfach notifyItemrangeInserted andernfalls notifyDataSetChanged ();
Shashank Verma
Gibt es eine Möglichkeit, dies zu reproduzieren? Ich bekomme diesen Absturz in meiner Produktions-App, kann ihn aber nicht lokal reproduzieren
Ezio
Wenn Sie lokal produzieren, verwenden Sie ein Low-End-Gerät mit 1 GB RAM
Hamza
Wenn Sie diesen Fehler erhalten, nachdem Sie eine Änderung an einer Room-Entität vorgenommen haben, die sich nur auf eine andere Aktivität auswirken soll, bei der dieser Fehler nicht auftritt, wurde wahrscheinlich beim Neuerstellen des Projekts ein Layoutcode-Cache verwendet, der sich nicht an diese Änderungen angepasst hat Sie müssen Daten aus der App aus den Android-Systemeinstellungen löschen
OzzyTheGiant

Antworten:

41

Es sieht ähnlich aus mit bekannten Android- Bug

Es gibt ziemlich hässliche, aber funktionierende Ansätze

public class WrapContentLinearLayoutManager extends LinearLayoutManager {
    //... constructor
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
        } catch (IndexOutOfBoundsException e) {
            Log.e("Error", "IndexOutOfBoundsException in RecyclerView happens");
        }
    }
}


mRecyclerView.setLayoutManager(new WrapContentGridLayoutManager(getContext(), spanCount));

Bei mir funktioniert es ohne Nebeneffekt.

paynd
quelle
1
Die App stürzt nach dieser Verwendung nicht ab, aber der Fehler ist immer noch vorhanden. Was ist der genaue Grund für diesen Fehler?
Krishna Meena
10
Dies ist eine wirklich schlechte Idee, die Ausnahme zu kathodisieren und zum Schweigen zu bringen ... issuetracker.google.com/issues/37030377#comment9 @Angad Tiwari
marcos E.
1
@payyd was ist spanCount?
Vijay Rajput
3
Ich verwende die neueste Version von RecyclerView: 26.0.2 und dieser Fehler tritt manchmal immer noch auf, sodass sie vermutlich nicht behoben wurden.
Vovahost
4
Gibt es eine Möglichkeit, diesen Absturz zu reproduzieren, da wir diesen Absturz in der Produktion bekommen, ihn aber nicht lokal reproduzieren können
Ezio
34

Dieses Problem ist ein bekannter Fehler von RecyclerView. Die beste Lösung ist, die Liste jedes Mal zu löschen, bevor RecyclerView aktualisiert wird.

Um dieses Problem zu beheben, rufen Sie einfach notifyDataSetChanged () mit leerer Liste auf, bevor Sie die Recyclingansicht aktualisieren.

Zum Beispiel

//Method for refresh recycle view

if (!yourList.isEmpty())

yourList.clear(); //The list for update recycle view

adapter.notifyDataSetChanged(); 
EKN
quelle
Ich falle dieses Problem recyclerview LoadMore Artikel
Ahamadullah Saikat
@AhamadullahSaikat hast du eine Antwort darauf bekommen?
Anshul Tyagi
Ich arbeite mit einem Recycling-Überblick, Adaptern und Fragmenten und das funktioniert für mich. Ich füge meinem Adapter einfach eine Methode hinzu, um anhand des Fragments zu überprüfen, ob meine Liste leer ist. boolean isEmpty(){return mylist.isEmpty;}und dann in meinem Fragment Anrufmyadapter.isEmpty()
Quimbo
1
Nun, das ist seltsam. Das Löschen der Liste beim Aktualisieren hat das Problem für mich verursacht. Ich habe jedoch notifyDataSetChanged nicht aufgerufen. Wenn jemand diese Lösung verwenden wird, stellen Sie auf jeden Fall sicher, dass Sie aktualisieren.
Riot Goes Woof
1
Es klappt! Du hast meinen Tag nach einer Stunde nutzloser Nachforschungen gerettet!
Mattia Ruggiero
14

Setzen Sie diese Zeile zusammen mit der Einstellung von recyclerView. Das Problem wurde behoben, indem ItemAnimator für RecyclerView auf null gesetzt wurde.

in Kotlin

    recyclerView.layoutManager = LinearLayoutManager(this)
    recyclerView.itemAnimator = null

in Java

    recyclerView.setItemAnimator(null);
Abhay Pratap
quelle
8

Verwenden Sie diese Option, um eine RecyclerView zu aktualisieren

items.clear(); //here items is an ArrayList populating the RecyclerView
adapter.notifyDataSetChanged();
items.addAll(list);// add new data 
adapter.notifyItemRangeInserted(0, items.size);// notify adapter of new data

`

Nikhil Bansal
quelle
1
danke bro diese Antwort ist 100% Arbeit für mich. wenn sich mein recyclerview innerhalb des Fragments befindet. Vielen Dank .
Kunal Nikam
7

Ich hatte ein ähnliches Problem, und auch diese Lösung hat mir geholfen, nachdem ich meinem Wohnmobil einen neuen Artikel hinzugefügt habe:

recyclerView.getRecycledViewPool().clear();
adapter.notifyDataSetChanged();
K.Os
quelle
3

In meinem Fall habe ich es so gemacht, wie notifyItemInserted(position); es mir dieses Problem verursacht hat, dann habe ich es benutzt und es hat perfekt funktioniert.notifyItemRangeInserted(startIndex,endIndex);

Ali Akram
quelle
3

Vielleicht können Sie dies versuchen, bevor Sie den Adapter aktualisieren:

dataList.clear(); 
patrolListAdapter.notifyDataSetChanged();
BLiYing
quelle
2

Das Problem liegt in dieser Codezeile:

 mFeed = feeds;

Sie weisen mFeedder Instanz des Anrufers zu. feedsWenn der Anrufer seine Variable ändert (möglicherweise Elemente hinzufügen, löschen oder entfernen), mFeedändert sich Ihre lokale Instanz

versuche zu wechseln

mFeed.addAll(feeds);

Vergessen Sie nicht mFeed, eine Liste zu erstellen, die Ihren Anforderungen entsprichtmFeed = new ArrayList<>();

Jorge Arimany
quelle
1

Ich hatte dieses Problem beim schnellen Scrollen durch mein Endlos / Paging RecyclerView. Die Wurzel meines Problems lag in der Tatsache, dass ich am Anfang der Liste ein "Header" -Element hatte. Dieses Header-Element war kein Teil der Datenquelle, sondern wurde nur am Anfang der adapterListe eingefügt . Wenn Sie also schnell scrollen und neue Seiten mit Elementen hinzufügen, RecyclerView Adapterbenachrichtigen Sie dieadapter dass neue Daten eingefügt wurden, habe ich das zusätzliche Header-Element nicht berücksichtigt, wodurch die Größe der Adapterliste falsch war ... und diese Ausnahme verursacht wurde. ..

Kurz gesagt, wenn Sie eine Kopf- / Fußzeile in unserem RecyclerViewAdapter verwenden, stellen Sie sicher, dass Sie dies bei der Aktualisierung der Adapterdaten berücksichtigen.

Beispiel:

public void addNewPageToList(List<MyData> list)
{   //
    // Make sure you account for any header/footer in your list!
    //
    // Add one to the currentSize to account for the header item.
    //
    int currentSize = this.adapterList.size() + 1;
    this.adapterList.addAll(list);
    notifyItemRangeInserted(currentSize, this.adapterList.size());
}

Bearbeiten: Ich denke, Sie könnten immer nur die Adaptermethode verwenden getItemCount(), um die Größe zu ermitteln, anstatt die Größe aus der „Datenliste“ abzurufen und zu ergänzen. Ihre getItemCount()Methode sollte bereits alle zusätzlichen Kopf- / Fußzeilen / usw. berücksichtigen, die Sie in Ihrer Liste haben.

Sakiboy
quelle
1

Setzen Sie diese Zeile zusammen mit der Einstellung von recyclerView. Das Problem wurde behoben, indem ItemAnimator für RecyclerView auf null gesetzt wurde.

in kotlin
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.itemAnimator = null
Rahul Raj
quelle
0

Ich hatte ein ähnliches Problem. Das Entfernen aller Ansichten aus RecyclerView hat mir geholfen:

RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
layoutManager.removeAllViews();
Andrew Churilo
quelle
0

Für mich war das Problem, dass ich notifyDatasetChanged nicht gepostet habe, als sich der Datensatz geändert hat, als ich die inkrementelle Suche implementiert habe.

Ich hatte eine Liste, die basierend auf der Suche des Benutzers im Such-Widget gefiltert wurde. Für jedes Element in der Liste habe ich eine Remoteanforderung gestellt, und als ich das Ergebnis zurückerhielt, habe ich diese bestimmte Zelle aktualisiert.

Ich musste beide Benachrichtigungen ausführen, damit die Recycler-Ansicht funktioniert

Filtern Sie den Originaldatensatz und veröffentlichen Sie die Datensatzänderung

this.searchResultTable?.post {
    this.searchResultTable?.adapter?.notifyDataSetChanged()
}

Nachdem Sie eine Antwort erhalten haben, senden Sie erneut Benachrichtigungen

this.searchResultTable?.post {
    this.searchResultTable?.adapter?.notifyItemChanged(index, updateDataHashMap)
}

Sie müssen Aktualisierungen veröffentlichen, anstatt Benachrichtigungsnachrichten direkt zu senden, um zu verhindern, dass die Recycler-Ansicht abstürzt, wenn die Aktualisierung eingeht, bevor die Ansicht angelegt wird.

Ein weiterer wichtiger Punkt ist, dass Sie beim Veröffentlichen der einzelnen Updates nach der Remote-Antwort sicherstellen müssen, dass die Liste, die der Benutzer derzeit sieht, die Liste ist, die zum Zeitpunkt des Sendens der Anforderungen vorhanden war.

ykonda
quelle