web-dev-qa-db-fra.com

Android RecyclerView: notifyDataSetChanged () IllegalStateException

J'essaie de mettre à jour les éléments d'un recycleview en utilisant notifyDataSetChanged ().

C'est ma méthode onBindViewHolder () dans l'adaptateur recycleview.

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

Ce que je veux faire, c'est mettre à jour les éléments de la liste, après avoir coché une case à cocher. Je reçois une exception illégale cependant: "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)

J'ai également utilisé notifyItemChanged (), même exception. Un moyen secret de mettre à jour pour informer l'adaptateur que quelque chose a changé?

119
Arthur

Vous devez déplacer la méthode 'setOnCheckedChangeListener ()' vers ViewHolder, qui est la classe interne de votre adaptateur.

onBindViewHolder() n’est pas une méthode d’initialisation ViewHolder. Cette méthode consiste à actualiser chaque élément du recycleur . Lorsque vous appelez notifyDataSetChanged(), onBindViewHolder() sera appelé en tant que nombre de fois.

Donc, si vous notifyDataSetChanged() mettez dans onCheckChanged() et initialisez checkBox dans onBindViewHolder(), vous obtiendrez IllegalStateException à cause de l'appel de la méthode circulaire.

cochez la case -> onCheckedChanged () -> notifyDataSetChanged () -> onBindViewHolder () -> cochez la case -> onChecked ...

Simplement, vous pouvez résoudre ce problème en mettant un indicateur dans Adapter.

essaye ça,

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

L'utilisation d'une Handler pour ajouter des éléments et appeler notify...() à partir de cette Handler a résolu le problème pour moi.

39
cybergen

Vous pouvez simplement réinitialiser l'écouteur précédent avant d'apporter des modifications et vous n'obtiendrez pas cette exception.

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

Je ne sais pas bien, mais j'ai aussi eu le même problème. J'ai résolu ce problème en utilisant onClickListner sur checkbox

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

Essayez ceci, cela peut aider!

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

Trouvé une solution simple -

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

Ici, nous créons un runnable à notifyItemChanged pour un poste lorsque recyclerview est prêt à le gérer.

6
Rohan Kandwal

Lorsque vous avez le message d'erreur:

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

Simple, faites ce qui cause l'exception dans: 

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

votre élément CheckBox est en cours de modification lorsque vous appelez notifyDataSetChanged(); afin que cette exception se produise . Essayez d’appeler notifyDataSetChanged(); après votre commentaire. Par exemple: 

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

Au début, je pensais que la réponse de Moonsoo (la réponse acceptée) ne fonctionnerait pas pour moi car je ne peux pas initialiser ma setOnCheckedChangeListener() dans le constructeur ViewHolder car je dois la lier à chaque fois pour obtenir une variable de position mise à jour. Mais il m'a fallu beaucoup de temps pour comprendre ce qu'il disait. 

Voici un exemple de "l'appel de méthode circulaire" dont il parle:

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.
}

Le seul problème avec cela est que, lorsque nous devons initialiser le commutateur pour qu'il soit activé ou désactivé (à partir de l'état de sauvegarde précédent, par exemple), il appelle le programme d'écoute qui peut appeler nofityItemRangeChanged qui appelle à nouveau onBindViewHolder. Vous ne pouvez pas appeler onBindViewHolder lorsque vous êtes déjà dans onBindViewHolder], car vous ne pouvez pas notifyItemRangeChanged si vous êtes déjà en train de notifier que la plage d'éléments a été modifiée. Mais je n'avais besoin que de mettre à jour l'interface utilisateur pour l'activer ou la désactiver, ne voulant rien déclencher.

Voici la solution que j'ai tirée de la réponse de JoniDS qui empêchera la boucle infinie. Tant que nous définissons l'auditeur sur "null" avant de l'activer sur Checked, l'interface utilisateur sera mise à jour sans déclencher l'écouteur, ce qui évite la boucle infinie. Ensuite, nous pouvons définir l'auditeur après. 

Code de JoniDS:

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

Solution complète à mon exemple:

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

Tandis que l'élément est lié par le gestionnaire de disposition, il est très probable que vous définissiez l'état coché de votre case à cocher, ce qui déclenche le rappel. 

Bien entendu, il s'agit d'une hypothèse, car vous n'avez pas publié la trace complète de la pile.

Vous ne pouvez pas modifier le contenu de l'adaptateur pendant que RV recalcule la mise en page. Vous pouvez l'éviter en n'appelant pas notifyDataSetChanged si l'état coché de l'élément est égal à la valeur envoyée dans le rappel (ce qui sera le cas si l'appel checkbox.setChecked déclenche le rappel).

2
yigit

Utilisez onClickListener sur la case à cocher au lieu de OnCheckedChangeListener, cela résoudra le problème

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

Pourquoi ne pas vérifier l'état RecyclerView.isComputingLayout() comme suit? 

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

Avant notifyDataSetChanged(), vérifiez simplement avec cette méthode: recyclerView.IsComputingLayout()

1

Utilisation simple Post:

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

J'ai rencontré ce problème! Après que la réponse de Moonsoo n'ait pas vraiment fait flotter mon bateau, j'ai un peu déconné et trouvé une solution qui a fonctionné pour moi. 

Tout d'abord, voici une partie de mon 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());
            }
        }
    });

Vous remarquerez que je notifie spécifiquement l'adaptateur pour le poste que je change, au lieu de l'ensemble de données comme vous le faites. Cela étant dit, même si je ne peux pas garantir que cela fonctionnera pour vous, j'ai résolu le problème en encapsulant mon appel notifyItemChanged() dans un bloc try/catch. Cela a simplement attiré l'exception, mais a néanmoins permis à mon adaptateur d'enregistrer le changement d'état et de mettre à jour l'affichage!

J'espère que cela aide quelqu'un!

EDIT: Je dois admettre que ce n’est probablement pas la manière appropriée/mature de traiter le problème, mais comme cela ne semble pas poser de problèmes en laissant l’exception non gérée, j’ai pensé partager cela au cas où ce serait bien assez pour quelqu'un d'autre. 

0
Andrew

Pour moi, le problème est survenu lorsque je suis sorti de EditText par Terminé, Retour, ou une entrée extérieure tactile. Ceci a pour effet de mettre à jour le modèle avec le texte d'entrée, puis d'actualiser la vue du recycleur via l'observation en direct des données. 

Le problème était que le curseur/focus reste dans EditText. 

Quand j'ai supprimé le focus en utilisant: 

editText.clearFocus() 

Notifier que les données ont changé. La méthode d'affichage du recycleur a effectivement cessé de générer cette erreur. 

Je pense que c'est l'une des raisons possibles/solutions à ce problème. Il est possible que cette exception puisse être corrigée d’une autre manière, car elle peut être provoquée par une raison totalement différente.

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 {

                    }

                }
            };
        }
0

Pour moi, j’écoutais le changement de classement d’une barre d’évaluation, mais après une pression longue de plusieurs clics, une application s’est effondrée à cause du problème, puis une solution claire a été trouvée si je voulais notifydatasetchange (); dans bindviewholder manipulé avec handler donné par:

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

J'espère que cela résoudra le problème.

0
Ali Nawaz

J'ai eu le même problème en utilisant la case à cocher et le RadioButton. Remplacer notifyDataSetChanged() par notifyItemChanged(position) a fonctionné. J'ai ajouté un champ booléen isChecked au modèle de données. Ensuite, j'ai mis à jour la valeur booléenne et dans onCheckedChangedListener, j'ai appelé notifyItemChanged(adapterPosition). Ce n'est peut-être pas la meilleure façon, mais a fonctionné pour moi. La valeur booléenne est utilisée pour vérifier si l'élément est coché.

0
Vishak A Kamath

utilisez simplement la méthode isPressed() de CompoundButton dans onCheckedChanged(CompoundButton compoundButton, boolean isChecked)
par exemple

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

Cela se produit car vous définissez probablement le "écouteur" avant de configurer la valeur de cette ligne, ce qui oblige l'écouteur à se déclencher lorsque vous "configurez la valeur" pour la case à cocher.

Ce que vous devez faire c'est:

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