wake-up-neo.com

Android RecyclerView: notifyDataSetChanged () IllegalStateException

Ich versuche, die Elemente einer Recyclingansicht mit notifyDataSetChanged () zu aktualisieren.

Dies ist meine onBindViewHolder () -Methode im Recycleview-Adapter.

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {

     //checkbox view listener
    viewHolder.getCheckbox().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

            //update list items
            notifyDataSetChanged();
        }
    });
}

Ich möchte die Listenelemente aktualisieren, nachdem ich ein Kontrollkästchen aktiviert habe. Ich bekomme jedoch eine illegale Ausnahme: "Cannot call this method while RecyclerView is computing a layout or scrolling"

Java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
    at Android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.Java:1462)
    at Android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onChanged(RecyclerView.Java:2982)
    at Android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.Java:7493)
    at Android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.Java:4338)
    at com.app.myapp.screens.RecycleAdapter.onRowSelect(RecycleAdapter.Java:111)

Ich habe auch notifyItemChanged () verwendet, dieselbe Ausnahme. Gibt es eine geheime Methode, um den Adapter zu benachrichtigen, wenn sich etwas geändert hat?

119
Arthur

Sie sollten die Methode 'setOnCheckedChangeListener ()' in die ViewHolder-Klasse verschieben, die sich auf Ihrem Adapter befindet.

onBindViewHolder() ist keine Methode, die ViewHolder..__ initialisiert. Diese Methode ist ein Schritt zum Aktualisieren jedes Recycler-Elements Wenn Sie notifyDataSetChanged() aufrufen, wird onBindViewHolder() als Anzahl der Elemente jedes Mal aufgerufen.

Wenn Sie also notifyDataSetChanged() in onCheckChanged() einfügen und checkBox in onBindViewHolder() initialisieren, erhalten Sie aufgrund eines zirkulären Methodenaufrufs IllegalStateException.

klicken Sie auf die Checkbox -> onCheckedChanged () -> notifyDataSetChanged () -> onBindViewHolder () -> Checkbox setzen -> onChecked ...

Sie können dies einfach beheben, indem Sie ein Flag in den Adapter setzen.

versuche dies,

private boolean onBind;

public ViewHolder(View itemView) {
    super(itemView);
    mCheckBox = (CheckBox) itemView.findViewById(R.id.checkboxId);
    mCheckBox.setOnCheckChangeListener(this);
}

@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    if(!onBind) {
        // your process when checkBox changed
        // ...

        notifyDataSetChanged();
    }
}

...

@Override
public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) {
    // process other views 
    // ...

    onBind = true;
    viewHolder.mCheckBox.setChecked(trueOrFalse);
    onBind = false;
}
127
Moonsoo Jeong

Die Verwendung einer Handler zum Hinzufügen von Elementen und das Aufrufen von notify...() aus dieser Handler hat das Problem für mich behoben.

39
cybergen

Sie können den vorherigen Listener einfach zurücksetzen, bevor Sie Änderungen vornehmen. Diese Ausnahme wird nicht angezeigt.

private CompoundButton.OnCheckedChangeListener checkedListener = new CompoundButton.OnCheckedChangeListener() {                      
                        @Override
                        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                            //Do your stuff
                    });;

    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {
        holder.checkbox.setOnCheckedChangeListener(null);
        holder.checkbox.setChecked(condition);
        holder.checkbox.setOnCheckedChangeListener(checkedListener);
    }
39
JoniDS

Ich weiß es nicht gut, aber ich hatte auch das gleiche Problem. Ich habe das Problem gelöst, indem ich onClickListner für checkbox verwendete.

viewHolder.mCheckBox.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            if (model.isCheckboxBoolean()) {
                model.setCheckboxBoolean(false);
                viewHolder.mCheckBox.setChecked(false);
            } else {
                model.setCheckboxBoolean(true);
                viewHolder.mCheckBox.setChecked(true);
            }
            notifyDataSetChanged();
        }
    });

Versuchen Sie dies, das kann helfen!

22
jigar
protected void postAndNotifyAdapter(final Handler handler, final RecyclerView recyclerView, final RecyclerView.Adapter adapter) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (!recyclerView.isComputingLayout()) {
                    adapter.notifyDataSetChanged();
                } else {
                    postAndNotifyAdapter(handler, recyclerView, adapter);
                }
            }
        });
    }
11
bruce

Eine einfache Lösung gefunden -

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

    private RecyclerView mRecyclerView; 

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        mRecyclerView = recyclerView;
    }

    private CompoundButton.OnCheckedChangeListener checkedChangeListener 
    = (compoundButton, b) -> {
        final int position = (int) compoundButton.getTag();
        // This class is used to make changes to child view
        final Event event = mDataset.get(position);
        // Update state of checkbox or some other computation which you require
        event.state = b;
        // we create a runnable and then notify item changed at position, this fix crash
        mRecyclerView.post(new Runnable() {
            @Override public void run() {
                notifyItemChanged(position));
            }
        });
    }
}

Hier erstellen wir ein lauffähiges Element, um notifyItemChanged für eine Position zu benachrichtigen, wenn das Recyclerview bereit ist, damit umzugehen.

6
Rohan Kandwal

Wenn Sie den Nachrichtenfehler haben:

Cannot call this method while RecyclerView is computing a layout or scrolling

Einfach, tun Sie einfach, was die Ausnahme verursacht in: 

RecyclerView.post(new Runnable() {
    @Override
    public void run() {
        /** 
        ** Put Your Code here, exemple:
        **/
        notifyItemChanged(position);
    }
});
6
Antoine Draune

ihr CheckBox-Element kann geändert werden, wenn Sie notifyDataSetChanged(); aufrufen. Diese Ausnahme würde auftreten Versuchen Sie, notifyDataSetChanged(); im Post Ihrer Ansicht aufzurufen. Zum Beispiel: 

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

Zuerst dachte ich, Moonsoos Antwort (die akzeptierte Antwort) würde für mich nicht funktionieren, da ich meine setOnCheckedChangeListener() nicht im ViewHolder-Konstruktor initialisieren kann, da ich sie jedes Mal binden muss, damit eine aktualisierte Positionsvariable angezeigt wird. Aber ich habe lange gebraucht, um zu realisieren, was er sagte. 

Hier ist ein Beispiel für den "kreisförmigen Methodenaufruf", über den er spricht:

public void onBindViewHolder(final ViewHolder holder, final int position) {
    SwitchCompat mySwitch = (SwitchCompat) view.findViewById(R.id.switch);
    mySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                       if (isChecked) {
                           data.delete(position);
                           notifyItemRemoved(position);
                           //This will call onBindViewHolder, but we can't do that when we are already in onBindViewHolder!
                           notifyItemRangeChanged(position, data.size());
                       }
                   }
            });
    //Set the switch to how it previously was.
    mySwitch.setChecked(savedSwitchState); //If the saved state was "true", then this will trigger the infinite loop.
}

Das einzige Problem dabei ist, dass, wenn der Schalter ein- oder ausgeschaltet werden muss (z. B. aus dem gespeicherten Zustand), der Listener aufgerufen wird, der möglicherweise nofityItemRangeChanged aufruft, wodurch erneut onBindViewHolder aufgerufen wird. Sie können onBindViewHolder nicht aufrufen, wenn Sie sich bereits in onBindViewHolder befinden, da Sie notifyItemRangeChanged nicht auswählen können, wenn Sie gerade benachrichtigen, dass sich der Artikelbereich geändert hat. Aber ich musste nur die Benutzeroberfläche aktualisieren, um sie ein- oder auszuschalten, und wollte eigentlich nichts auslösen.

Hier ist die Lösung, die ich aus JoniDSs Antwort gelernt habe, die die Endlosschleife verhindert. Solange wir den Listener auf "null" setzen, bevor wir Checked setzen, wird die Benutzeroberfläche aktualisiert, ohne dass der Listener ausgelöst wird. Dadurch wird die Endlosschleife vermieden. Dann können wir den Listener nach einstellen. 

JoniDS-Code:

holder.checkbox.setOnCheckedChangeListener(null);
holder.checkbox.setChecked(condition);
holder.checkbox.setOnCheckedChangeListener(checkedListener);

Volle Lösung zu meinem Beispiel:

public void onBindViewHolder(final ViewHolder holder, final int position) {
    SwitchCompat mySwitch = (SwitchCompat) view.findViewById(R.id.switch);

    //Set it to null to erase an existing listener from a recycled view.
    mySwitch.setOnCheckedChangeListener(null);

    //Set the switch to how it previously was without triggering the listener.
    mySwitch.setChecked(savedSwitchState); //If the saved state was "true", then this will trigger the infinite loop.

    //Set the listener now.
    mySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (isChecked) {
                data.delete(position);
                notifyItemRemoved(position);
                //This will call onBindViewHolder, but we can't do that when we are already in onBindViewHolder!
                notifyItemRangeChanged(position, data.size());
            }
        }
    });
}
4
Rock Lee

Während das Element vom Layout-Manager gebunden wird, setzen Sie wahrscheinlich den aktivierten Status Ihres Kontrollkästchens, das den Rückruf auslöst. 

Dies ist natürlich eine Vermutung, da Sie nicht die vollständige Stapelverfolgung veröffentlicht haben.

Sie können den Adapterinhalt nicht ändern, während RV das Layout neu berechnet. Sie können dies vermeiden, indem Sie notifyDataSetChanged nicht aufrufen, wenn der geprüfte Status des Elements dem im Callback gesendeten Wert entspricht (was der Fall ist, wenn der Aufruf von checkbox.setChecked den Callback auslöst).

2
yigit

Verwenden Sie onClickListener im Kontrollkästchen anstelle von OnCheckedChangeListener. Dadurch wird das Problem gelöst

viewHolder.myCheckBox.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            if (viewHolder.myCheckBox.isChecked()) {
                // Do something when checkbox is checked
            } else {
                // Do something when checkbox is unchecked                
            }
            notifyDataSetChanged();
        }
    });

Warum nicht den RecyclerView.isComputingLayout()-Status wie folgt überprüfen? 

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

    private RecyclerView mRecyclerView; 

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        mRecyclerView = recyclerView;
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {

        viewHolder.getCheckbox().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (mRecyclerView != null && !mRecyclerView.isComputingLayout()) {
                    notifyDataSetChanged();
                }
            }
        });
    }
}
1
NcJie

Bevor Sie notifyDataSetChanged() überprüfen, überprüfen Sie dies einfach mit dieser Methode: recyclerView.IsComputingLayout()

Einfacher Gebrauch Post:

new Handler().post(new Runnable() {
        @Override
        public void run() {
                mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1);
            }
        }
    });
1
Kai Wang

Ich bin genau auf dieses Thema gestoßen! Nachdem Moonsoos Antwort mein Boot nicht wirklich getrieben hatte, machte ich ein bisschen Unfug und fand eine Lösung, die für mich funktionierte. 

Zunächst ein paar von meinem Code: 

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {

    final Event event = mDataset.get(position);

    //
    //  .......
    //

    holder.mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            event.setActive(isChecked);
            try {
                notifyItemChanged(position);
            } catch (Exception e) {
                Log.e("onCheckChanged", e.getMessage());
            }
        }
    });

Sie werden feststellen, dass ich den Adapter speziell für die Position, die ich ändere, benachrichtige, anstatt wie bei der gesamten Datenmenge. Obwohl ich nicht garantieren kann, dass dies für Sie funktionieren wird, habe ich das Problem gelöst, indem Sie meinen Aufruf von notifyItemChanged() in einen try/catch-Block wickeln. Dies hat einfach die Ausnahme erfasst, aber mein Adapter konnte immer noch die Statusänderung registrieren und die Anzeige aktualisieren!

Hoffe das hilft jemandem!

EDIT: Ich gebe zu, dies ist wahrscheinlich nicht die richtige/ausgereifte Art, mit dem Problem umzugehen, aber da es offensichtlich keine Probleme bereitet, wenn die Ausnahme nicht behandelt wird, dachte ich, ich würde es teilen, falls es gut wäre genug für jemand anderen. 

0
Andrew

Bei mir trat ein Problem auf, wenn ich EditText durch Fertig, Zurück oder Eingabe außerhalb der Eingabe verlassen habe. Dies führt dazu, dass das Modell mit dem eingegebenen Text aktualisiert wird und die Ansicht des Recyclers mithilfe der Live-Datenüberwachung aktualisiert wird. 

Das Problem war, dass der Cursor/Fokus in EditText bleibt. 

Wenn ich den Fokus gelöscht habe mit: 

editText.clearFocus() 

Benachrichtigen Sie die geänderte Methode der Recycler-Ansicht. Dieser Fehler wurde nicht mehr angezeigt. 

Ich denke, das ist einer der möglichen Gründe/Lösungen für dieses Problem. Es ist möglich, dass diese Ausnahme auf andere Weise behoben werden kann, da sie aus einem ganz anderen Grund verursacht werden kann.

0
Michał Ziobro
        @Override
        public void onBindViewHolder(final MyViewHolder holder, final int position) {
            holder.textStudentName.setText(getStudentList.get(position).getName());
            holder.rbSelect.setChecked(getStudentList.get(position).isSelected());
            holder.rbSelect.setTag(position); // This line is important.
            holder.rbSelect.setOnClickListener(onStateChangedListener(holder.rbSelect, position));

        }

        @Override
        public int getItemCount() {
            return getStudentList.size();
        }
        private View.OnClickListener onStateChangedListener(final RadioButton checkBox, final int position) {
            return new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (checkBox.isChecked()) {
                        for (int i = 0; i < getStudentList.size(); i++) {

                            getStudentList.get(i).setSelected(false);

                        }
                        getStudentList.get(position).setSelected(checkBox.isChecked());

                        notifyDataSetChanged();
                    } else {

                    }

                }
            };
        }

Für mich hörte ich die Bewertung einer Bewertungsleiste ändern, aber nach langem Drücken mehrerer Klicks auf einmal stürzte die App aufgrund des Problems ab und fand eine klare Lösung, wenn ich notifydatasetchange () benachrichtigen wollte; in bindviewholder handhabte es mit einem Handler, der gegeben wurde von:

//inside bindViewHolder                 
new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        notifyDataSetChanged();
                    }
                });

Ich hoffe, es wird das Problem lösen.

0
Ali Nawaz

Ich hatte das gleiche Problem mit der Checkbox und dem RadioButton. Das Ersetzen von notifyDataSetChanged() durch notifyItemChanged(position) hat funktioniert. Ich habe dem Datenmodell ein Boolesches Feld isChecked hinzugefügt. Dann habe ich den Booleschen Wert aktualisiert und in onCheckedChangedListenernotifyItemChanged(adapterPosition) aufgerufen. Dies ist vielleicht nicht der beste Weg, hat aber für mich funktioniert. Der Boolesche Wert wird verwendet, um zu überprüfen, ob das Element überprüft wurde.

0
Vishak A Kamath

verwenden Sie einfach die isPressed()-Methode von CompoundButton in onCheckedChanged(CompoundButton compoundButton, boolean isChecked)
z.B

public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {   
                      ... //your functionality    
                            if(compoundButton.isPressed()){
                                notifyDataSetChanged();
                            }
                        }  });
0
Asad

Dies geschieht, weil Sie wahrscheinlich den 'Listener' festgelegt haben, bevor Sie den Wert für diese Zeile konfigurieren. Dadurch wird der Listener ausgelöst, wenn Sie 'den Wert' für das Kontrollkästchen konfigurieren.

Was Sie tun müssen, ist:

@Override
public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) {
   viewHolder.mCheckBox.setOnCheckedChangeListener(null);
   viewHolder.mCheckBox.setChecked(trueOrFalse);
   viewHolder.setOnCheckedChangeListener(yourCheckedChangeListener);
}
0