Android, ListView IllegalStateException: "Le contenu de l'adaptateur a changé mais ListView n'a pas reçu de notification"

189

Ce que je veux faire : exécuter un thread d'arrière-plan qui calcule le contenu de ListView et met à jour partiellement ListView, pendant que les résultats sont calculés.

Ce que je sais que je dois éviter : je ne peux pas jouer avec le contenu de ListAdapter du thread d'arrière-plan, j'ai donc hérité d'AsyncTask et publié le résultat (ajouter des entrées à l'adaptateur) de onProgressUpdate. Mon adaptateur utilise ArrayList des objets de résultat, toutes les opérations sur ces arraylists sont synchronisées.

Recherche d'autres personnes : il y a ici des données très précieuses . J'ai également souffert de plantages presque quotidiens pour un groupe d'environ 500 utilisateurs, et lorsque j'ai ajouté un list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)bloc dans onProgressUpdate, les plantages ont été réduits d'un facteur 10 mais n'ont pas disparu. (il a été suggéré en réponse )

Ce que j'ai parfois : remarquez, cela arrive très rarement (une fois par semaine pour l'un des 3,5k utilisateurs). Mais j'aimerais me débarrasser complètement de ce bug. Voici un stacktrace partiel:

`java.lang.IllegalStateException:` The content of the adapter has changed but ListView  did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131296334, class android.widget.ListView) with Adapter(class com.transportoid.Tracks.TrackListAdapter)]
at android.widget.ListView.layoutChildren(ListView.java:1432)
at android.widget.AbsListView.onTouchEvent(AbsListView.java:2062)
at android.widget.ListView.onTouchEvent(ListView.java:3234)
at android.view.View.dispatchTouchEvent(View.java:3709)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:852)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
[...]

Aidez-moi? Plus besoin, voir ci-dessous

RÉPONSE FINALE: Il s'est avéré que j'appelais notifyDataSetChangedtoutes les 5 insertions pour éviter le scintillement et les changements soudains de liste. Cela ne peut pas être fait de cette manière, avertissez toujours l'adaptateur lorsque la liste de base change. Ce bug est parti depuis longtemps pour moi maintenant.

Tomash
la source
3
Avez-vous appelé notifyDataSetChanged ()?
Denis Palnitsky
1
Bien sûr, dans onProgressUpdate, il y a séquence: list.setVisibility (GONE) - addObjectToList / opération synchronisée sur list / - notifyDataSetChanged () - list.setVisibility (VISIBLE) (et sans exception de modifications de visibilité se produit beaucoup plus souvent)
tomash
1
Modifiez-vous la ArrayList sous-jacente sur un thread différent? Toutes les modifications, même celles de la ArrayList référencée par l'adaptateur, doivent se produire sur le thread d'interface utilisateur.
Rich Schuler
2
@Qberticus - comme je l'ai clairement indiqué, je ne modifie pas ArrayList à partir d'un thread différent, mais à partir de la méthode onProgressUpdate d'AcyncTask - cela fonctionne dans le thread GUI.
tomash le
1
@tomash j'ai eu la même exception que j'utilise pull pour rafraîchir et après exécution, j'ai écrit adapter.notifyDataSetChanged (); lvAutherlist.completeRefreshing (); mais parfois obtenu cette erreur comment le résoudre
Khan

Réponses:

119

J'ai eu le même problème.

J'ajoutais des éléments à mon ArrayListfil externe à l'interface utilisateur.

Solution: j'ai fait les deux adding the itemset j'ai appelé notifyDataSetChanged()le fil d'interface utilisateur.

Mullins
la source
4
J'ai à la fois ajouté les éléments et appelé notifyDataSetChanged () dans le fil de l'interface utilisateur et j'ai résolu ce problème.
Mullins
23
Commentaire génial, vraiment.
dentex
2
Il s'agit d'un problème de multithreading et l'utilisation de blocs correctement synchronisés peut être évitée. Sans mettre d'éléments supplémentaires sur UI Thread et entraîner une perte de réactivité de l'application, consultez ma réponse ci-dessous.
Javanator
2
Pour les futurs lecteurs, faire des calculs dans le thread d'arrière-plan est une bonne chose, mais vous devez transmettre les résultats au thread d'interface utilisateur et ajouter des éléments à la collection de base de l'adaptateur et notifier l'adaptateur dans le même bloc de code.
tomash
4
@Mullins Pouvez-vous s'il vous plaît montrer un exemple pour votre solution? Je suis coincé au même problème
Jas
27

J'ai eu le même problème, mais je l'ai résolu en utilisant la méthode

requestLayout();

de la classe ListView

gian1200
la source
35
quand devrions-nous appeler cette méthode?
JehandadK
3
@DeBuGGeR après avoir ajouté des éléments à ListView ou changé l'adaptateur de celui-ci.
Ahmet Noyan Kızıltan
et si j'ajoute des éléments dans asynctask
Dr. aNdRO
2
Pour mémoire, @ Dr.aNdRO, vous collectez vos résultats dans AsyncTask. doInBackground () et enregistrez vos résultats pour mettre à jour la liste dans AsyncTask.onPostExecute (), qui s'exécutera dans le thread d'interface utilisateur. Si vous avez besoin de mettre à jour au fur et à mesure, utilisez AsyncTask.publishProgress () et AsyncTask.onProgressUpdate () qui s'exécute également dans le thread d'interface utilisateur.
Nicole Borrelli
21

Il s'agit d'un problème de multi - threading et l'utilisation de blocs correctement synchronisés peut être évitée. Sans mettre d'éléments supplémentaires sur UI Thread et entraîner une perte de réactivité de l'application.

J'ai également fait face à la même chose. Et comme la réponse la plus acceptée le suggère, apporter des modifications aux données de l'adaptateur à partir de UI Thread peut résoudre le problème. Cela fonctionnera, mais c'est une solution rapide et facile mais pas la meilleure.

Comme vous pouvez le voir pour un cas normal. La mise à jour de l'adaptateur de données à partir du thread d'arrière-plan et l'appel de notifyDataSetChanged dans le thread d'interface utilisateur fonctionnent.

Cette illégaleStateException survient lorsqu'un thread d'interface utilisateur met à jour la vue et qu'un autre thread d'arrière-plan modifie à nouveau les données. Ce moment provoque ce problème.

Donc, si vous synchronisez tout le code qui modifie les données de l'adaptateur et effectue un appel notifydatasetchange. Ce problème devrait disparaître. C'est parti pour moi et je suis toujours en train de mettre à jour les données du fil d'arrière-plan.

Voici mon code spécifique à mon cas pour que les autres puissent se référer.

Mon chargeur sur l'écran principal charge les contacts du répertoire téléphonique dans mes sources de données en arrière-plan.

    @Override
    public Void loadInBackground() {
        Log.v(TAG, "Init loadings contacts");
        synchronized (SingleTonProvider.getInstance()) {
            PhoneBookManager.preparePhoneBookContacts(getContext());
        }
    }

Ce PhoneBookManager.getPhoneBookContacts lit les contacts du répertoire et les remplit dans les hashmaps. Ce qui est directement utilisable par les adaptateurs de liste pour dessiner une liste.

Il y a un bouton sur mon écran. Cela ouvre une activité où ces numéros de téléphone sont répertoriés. Si je place directement l'adaptateur sur la liste avant que le thread précédent ne termine son travail, le cas de navigation rapide se produit moins souvent. Il fait apparaître l'exception. Quel est le titre de cette question SO. Je dois donc faire quelque chose comme ça dans la deuxième activité.

Mon chargeur dans la deuxième activité attend la fin du premier thread. Jusqu'à ce qu'il affiche une barre de progression. Vérifiez le loadInBackground des deux chargeurs.

Ensuite, il crée l'adaptateur et le livre à l'activité où, sur le thread d'interface utilisateur, j'appelle setAdapter.

Cela a résolu mon problème.

Ce code n'est qu'un extrait de code. Vous devez le changer pour bien compiler pour vous.

@Override
public Loader<PhoneBookContactAdapter> onCreateLoader(int arg0, Bundle arg1) {
    return new PhoneBookContactLoader(this);
}

@Override
public void onLoadFinished(Loader<PhoneBookContactAdapter> arg0, PhoneBookContactAdapter arg1) {
    contactList.setAdapter(adapter = arg1);
}

/*
 * AsyncLoader to load phonebook and notify the list once done.
 */
private static class PhoneBookContactLoader extends AsyncTaskLoader<PhoneBookContactAdapter> {

    private PhoneBookContactAdapter adapter;

    public PhoneBookContactLoader(Context context) {
        super(context);
    }

    @Override
    public PhoneBookContactAdapter loadInBackground() {
        synchronized (SingleTonProvider.getInstance()) {
            return adapter = new PhoneBookContactAdapter(getContext());    
        }
    }

}

J'espère que cela t'aides

Javanateur
la source
Désolé comment as-tu fait ça? Comment avez-vous synchronisé le code du thread d'arrière-plan et l'appel notifydatasetchange? J'utilise un doInBackground lors du filtrage des données sur un AutocompleteTextView et je suis confronté au même problème ... Pouvez-vous me conseiller quelque chose pour résoudre?
tonix
@ user3019105 - Synchronisation de deux threads d'arrière-plan. un qui met à jour les données en arrière-plan et un autre qui notifie au thread d'interface utilisateur setAdadpter ou notifydatasetchanged à l'aide des méthodes handler.post ou runOnUiThread disponibles. Pour mon cas particulier, j'ai synchronisé deux chargeurs avec leur doInBackground synchronisé sur un objet singleton. On commence à préparer les données sur l'écran de démarrage lui-même pour être rapidement disponibles pour l'écran du tableau de bord. C'était mon besoin.
Javanator
Pourriez-vous s'il vous plaît publier un extrait de code de cette implémentation? Dans mon cas, j'ai résolu d'appeler clear () sur l'adaptateur, de créer une ArrayList <T> temporaire d'objets filtrés, puis de faire un appel à addAll (tmpArrayList) et enfin à notifyDataSetChanged (). Je fais tous ces appels dans la méthode de filtre publishResult (), exécutée sur le thread principal. Pensez-vous que c'est une solution fiable?
tonix
@ user3019105 vérifiez la réponse modifiée. J'espère que cela vous sera utile maintenant
Javanator
cette réponse nécessite beaucoup de lecture sur pourquoi cela fonctionne, tutorials.jenkov.com/java-concurrency/synchronized.html est un bon point de départ
abdu
15

J'ai résolu ce problème en ayant 2 listes. Une liste que j'utilise uniquement pour l'adaptateur, et je fais toutes les modifications / mises à jour de données sur l'autre liste. Cela me permet de faire des mises à jour sur une liste dans un thread d'arrière-plan, puis de mettre à jour la liste "adaptateur" dans le thread principal / UI:

List<> data = new ArrayList<>();
List<> adapterData = new ArrayList();

...
adapter = new Adapter(adapterData);
listView.setAdapter(adapter);

// Whenever data needs to be updated, it can be done in a separate thread
void updateDataAsync()
{
    new Thread(new Runnable()
    {
        @Override
        public void run()
        {
            // Make updates the "data" list.
            ...

            // Update your adapter.
            refreshList();
        }
    }).start();
}

void refreshList()
{
    runOnUiThread(new Runnable()
    {
        @Override
        public void run()
        {
            adapterData.clear();
            adapterData.addAll(data);
            adapter.notifyDataSetChanged();
            listView.invalidateViews();
        }
    });
}
triade
la source
7

J'ai écrit ce code et l'ai fait fonctionner dans une image d'émulateur 2.1 pendant ~ 12 heures et je n'ai pas obtenu l'exception IllegalStateException. Je vais donner au cadre Android le bénéfice du doute sur celui-ci et dire qu'il s'agit probablement d'une erreur dans votre code. J'espère que ça aide. Vous pouvez peut-être l'adapter à votre liste et à vos données.

public class ListViewStressTest extends ListActivity {
    ArrayAdapter<String> adapter;
    ListView list;
    AsyncTask<Void, String, Void> task;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        this.adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
        this.list = this.getListView();

        this.list.setAdapter(this.adapter);

        this.task = new AsyncTask<Void, String, Void>() {
            Random r = new Random();
            int[] delete;
            volatile boolean scroll = false;

            @Override
            protected void onProgressUpdate(String... values) {
                if(scroll) {
                    scroll = false;
                    doScroll();
                    return;
                }

                if(values == null) {
                    doDelete();
                    return;
                }

                doUpdate(values);

                if(ListViewStressTest.this.adapter.getCount() > 5000) {
                    ListViewStressTest.this.adapter.clear();
                }
            }

            private void doScroll() {
                if(ListViewStressTest.this.adapter.getCount() == 0) {
                    return;
                }

                int n = r.nextInt(ListViewStressTest.this.adapter.getCount());
                ListViewStressTest.this.list.setSelection(n);
            }

            private void doDelete() {
                int[] d;
                synchronized(this) {
                    d = this.delete;
                }
                if(d == null) {
                    return;
                }
                for(int i = 0 ; i < d.length ; i++) {
                    int index = d[i];
                    if(index >= 0 && index < ListViewStressTest.this.adapter.getCount()) {
                        ListViewStressTest.this.adapter.remove(ListViewStressTest.this.adapter.getItem(index));
                    }
                }
            }

            private void doUpdate(String... values) {
                for(int i = 0 ; i < values.length ; i++) {
                    ListViewStressTest.this.adapter.add(values[i]);
                }
            }

            private void updateList() {
                int number = r.nextInt(30) + 1;
                String[] strings = new String[number];

                for(int i = 0 ; i < number ; i++) {
                    strings[i] = Long.toString(r.nextLong());
                }

                this.publishProgress(strings);
            }

            private void deleteFromList() {
                int number = r.nextInt(20) + 1;
                int[] toDelete = new int[number];

                for(int i = 0 ; i < number ; i++) {
                    int num = ListViewStressTest.this.adapter.getCount();
                    if(num < 2) {
                        break;
                    }
                    toDelete[i] = r.nextInt(num);
                }

                synchronized(this) {
                    this.delete = toDelete;
                }

                this.publishProgress(null);
            }

            private void scrollSomewhere() {
                this.scroll = true;
                this.publishProgress(null);
            }

            @Override
            protected Void doInBackground(Void... params) {
                while(true) {
                    int what = r.nextInt(3);

                    switch(what) {
                        case 0:
                            updateList();
                            break;
                        case 1:
                            deleteFromList();
                            break;
                        case 2:
                            scrollSomewhere();
                            break;
                    }

                    try {
                        Thread.sleep(0);
                    } catch(InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }

        };

        this.task.execute(null);
    }
}
Rich Schuler
la source
Merci pour votre travail! J'ai remarqué que pour la méthode ArrayAdapter hasStableIds () = false; mon implémentation a renvoyé true, ce qui n'était pas tout à fait correct, car les lignes étaient triées avant chaque notifyDataSetChanged (). Je vais essayer - si les accidents survivent, je continuerai l'enquête. Meilleures salutations!
tomash
4

Il y a plusieurs jours j'ai rencontré le même problème et cause plusieurs milliers de plantages par jour, environ 0,1% des utilisateurs rencontrent cette situation. J'ai essayé setVisibility(GONE/VISIBLE)et requestLayout(), mais le nombre de crashs ne diminue qu'un peu.

Et je l'ai finalement résolu. Rien avec setVisibility(GONE/VISIBLE). Rien avec requestLayout().

Enfin, j'ai trouvé que la raison était que j'utilisais un Handlerappel notifyDataSetChanged()après la mise à jour des données, ce qui peut conduire à une sorte de:

  1. Met à jour les données vers un objet de modèle (je l'appelle un DataSource)
  2. L'utilisateur touche la vue de liste (qui peut appeler checkForTap()/ onTouchEvent()et enfin appeler layoutChildren())
  3. L'adaptateur récupère les données de l'objet modèle et appelle notifyDataSetChanged()et met à jour les vues

Et j'ai fait une autre erreur que dans getCount(), getItem()et getView(), j'utilise directement des champs dans DataSource, plutôt que de les copier sur l'adaptateur. Alors finalement ça plante quand:

  1. L'adaptateur met à jour les données que la dernière réponse donne
  2. Lors du retour de la prochaine réponse, DataSource met à jour les données, ce qui entraîne une modification du nombre d'éléments
  3. L'utilisateur touche la vue de liste, qui peut être un tap, un mouvement ou un retournement
  4. getCount()et getView()est appelé, et listview trouve que les données ne sont pas cohérentes et lève des exceptions telles que java.lang.IllegalStateException: The content of the adapter has changed but.... Une autre exception courante est une IndexOutOfBoundExceptionutilisation de l'en-tête / pied de page dans ListView.

La solution est donc simple, je copie simplement les données vers l'adaptateur à partir de ma source de données lorsque mon gestionnaire déclenche l'adaptateur pour obtenir des données et des appels notifyDataSetChanged(). Le crash ne se reproduit plus jamais.

HJWAJ
la source
1
C'était mon problème. Dans mon cas, getCount () renvoyait le nouveau nombre disponible avant que notifyDataSetChanged ne soit appelé. Étant donné que ma liste était «virtuelle», je capture maintenant le décompte mis à jour dans ma méthode notifyDataSetChanged, puis je retourne ce décompte mis en cache lorsqu'on me demande le décompte via getCount ().
Glenn
3

Si cela se produisait par intermittence, il s'avère que je n'ai eu ce problème que lorsque la liste a été défilée après un dernier élément `` charger plus '' a été cliqué. Si la liste n'était pas défilée, tout fonctionnait bien.

Après BEAUCOUP de débogage, c'était un bug de ma part, mais une incohérence dans le code Android également.

Lorsque la validation se produit, ce code est exécuté dans ListView

        } else if (mItemCount != mAdapter.getCount()) {
            throw new IllegalStateException("The content of the adapter has changed but "
                    + "ListView did not receive a notification. Make sure the content of "

Mais quand onChange se produit, il déclenche ce code dans AdapterView (parent de ListView)

    @Override
    public void onChanged() {
        mDataChanged = true;
        mOldItemCount = mItemCount;
        mItemCount = getAdapter().getCount();

Notez que l'adaptateur n'est PAS garanti d'être le même!

Dans mon cas, puisqu'il s'agissait d'un 'LoadMoreAdapter', je retournais le WrappedAdapter dans l'appel getAdapter (pour accéder aux objets sous-jacents). Cela a entraîné des décomptes différents en raison de l'élément supplémentaire `` Charger plus '' et de l'exception lancée.

Je n'ai fait cela que parce que les documents donnent l'impression que c'est correct

ListView.getAdapter javadoc

Renvoie l'adaptateur actuellement utilisé dans ce ListView. L'adaptateur retourné peut ne pas être le même que celui transmis à setAdapter (ListAdapter), mais peut être un WrapperListAdapter.

Aaronvargas
la source
J'ai un problème similaire . Pouvez-vous s'il vous plaît suggérer comment y remédier.
May13ank
Si vous regardez les 2 exemples de code ci-dessus, vous verrez mAdapter dans ListView et getAdapter () dans un parent de ListView (AdapterView). Si vous substituez getAdapter (), le résultat de getAdapter () peut ne pas être le mAdapter. Si les nombres des 2 adaptateurs sont différents, vous obtenez l'erreur.
aaronvargas
3

Mon problème était lié à l'utilisation d'un filtre avec ListView.

Lors de la configuration ou de la mise à jour du modèle de données sous-jacent de ListView, je faisais quelque chose comme ceci:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    getFilter().filter(filter);
}

L'appel filter()de la dernière ligne entraînera (et doit) notifyDataSetChanged()être appelé dans la publishResults()méthode Filter . Cela peut parfois fonctionner correctement, spécialement dans mon Nexus 5 rapide. Mais en réalité, cela cache un bogue que vous remarquerez avec des appareils plus lents ou dans des conditions gourmandes en ressources.

Le problème est que le filtrage est effectué de manière asynchrone, et donc entre la fin de l' filter()instruction et l'appel à publishResults(), tous deux dans le thread d'interface utilisateur, un autre code de thread d'interface utilisateur peut s'exécuter et modifier le contenu de l'adaptateur.

Le correctif réel est simple, il suffit d'appeler notifyDataSetChanged()également avant de demander le filtrage à effectuer:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    notifyDataSetChanged(); // Fix
    getFilter().filter(filter);
}
cprcrack
la source
3

J'ai une liste des objets Feed. Il est ajouté et tronqué à partir d'un thread sans interface utilisateur. Cela fonctionne bien avec l'adaptateur ci-dessous. J'appelle FeedAdapter.notifyDataSetChangedde toute façon le fil d'interface utilisateur, mais un peu plus tard. J'aime cela car mes objets Feed restent en mémoire dans le service local même lorsque l'interface utilisateur est morte.

public class FeedAdapter extends BaseAdapter {
    private int size = 0;
    private final List<Feed> objects;

    public FeedAdapter(Activity context, List<Feed> objects) {
        this.context = context;
        this.objects = objects;
        size = objects.size();
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        ...
    }

    @Override
    public void notifyDataSetChanged() {
        size = objects.size();

        super.notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return size;
    }

    @Override
    public Object getItem(int position) {
        try {
            return objects.get(position);
        } catch (Error e) {
            return Feed.emptyFeed;
        }
    }

    @Override
    public long getItemId(int position) {
        return position;
    }
}
il y a
la source
2

J'étais confronté au même problème avec exactement le même journal d'erreurs. Dans mon cas, onProgress()AsyncTask ajoute les valeurs à l'adaptateur en utilisant mAdapter.add(newEntry). Pour éviter que l'interface utilisateur ne devienne moins réactive, je configure mAdapter.setNotifyOnChange(false)et appelle mAdapter.notifyDataSetChanged()4 fois la seconde. Une fois par seconde, le tableau est trié.

Cela fonctionne bien et semble très addictif, mais malheureusement, il est possible de le planter en touchant assez souvent les éléments de la liste affichés.

Mais il semble que j'ai trouvé une solution de contournement acceptable. Je suppose que même si vous travaillez simplement sur le thread d'interface utilisateur, l'adaptateur n'accepte pas beaucoup de modifications de ses données sans appeler notifyDataSetChanged(), à cause de cela, j'ai créé une file d'attente qui stocke tous les nouveaux éléments jusqu'à ce que les 300 ms mentionnés soient terminés. Si ce moment est atteint, j'ajoute tous les éléments stockés en un seul coup et j'appelle notifyDataSetChanged(). Jusqu'à présent, je ne pouvais plus écraser la liste .

Lars K.
la source
2

Même si j'ai rencontré le même problème dans mon application de notification XMPP, le message des destinataires doit être ajouté à la vue de liste (implémenté avec ArrayList). Lorsque j'ai essayé d'ajouter le contenu du récepteur via MessageListener(thread séparé), l'application se ferme avec l'erreur ci-dessus. J'ai résolu ce problème en ajoutant le contenu à ma méthode arraylist& setListviewadapaterthrough runOnUiThreadqui fait partie de la classe Activity. Cela a résolu mon problème.

Balaji
la source
1

J'ai fait face à un problème similaire, voici comment j'ai résolu mon cas. Je vérifie si c'est taskdéjà le cas RUNNINGou FINISHEDparce qu'une tâche ne peut s'exécuter qu'une seule fois. Ci-dessous, vous verrez un code partiel et adapté de ma solution.

public class MyActivity... {
    private MyTask task;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       // your code
       task = new MyTask();
       setList();
    }

    private void setList() {
    if (task != null)
        if (task.getStatus().equals(AsyncTask.Status.RUNNING)){
            task.cancel(true);
            task = new MyTask();
            task.execute();         
        } else if (task.getStatus().equals(AsyncTask.Status.FINISHED)) {
            task = new MyTask();
            task.execute();
        } else 
            task.execute();
    }

    class MyTask extends AsyncTask<Void, Item, Void>{
       List<Item> Itens;

       @Override
       protected void onPreExecute() {

        //your code

        list.setVisibility(View.GONE);
        adapterItem= new MyListAdapter(MyActivity.this, R.layout.item, new ArrayList<Item>());
        list.setAdapter(adapterItem);

        adapterItem.notifyDataSetChanged();
    }

    @Override
    protected Void doInBackground(Void... params) {

        Itens = getItens();
        for (Item item : Itens) {
            publishProgress(item );
        }

        return null;
    }

    @Override
    protected void onProgressUpdate(Item ... item ) {           
        adapterItem.add(item[0]);
    }

    @Override
    protected void onPostExecute(Void result) {
        //your code
        adapterItem.notifyDataSetChanged();     
        list.setVisibility(View.VISIBLE);
    }

}

}
Leonardo Costa
la source
1

J'ai eu le même problème et je l'ai résolu. Mon problème était que j'utilisais un listview, avec un adaptateur de tableau et avec un filtre. Sur la méthode, performFilteringje jouais avec le tableau contenant les données et c'était le problème car cette méthode ne fonctionne pas sur le thread de l'interface utilisateur et, ÉVENTUELLEMENT, cela pose des problèmes.

Gusthema
la source
1

L'une des causes de ce plantage est que l' ArrayListobjet ne peut pas changer complètement. Donc, lorsque je supprime un élément, je dois faire ceci:

mList.clear();
mList.addAll(newDataList);

Cela a corrigé le crash pour moi.

andude
la source
1

Dans mon cas, j'ai appelé la méthode GetFilter()sur un adaptateur à partir de la TextWatcher()méthode sur l'activité principale, et j'ai ajouté les données avec une boucle For GetFilter(). La solution était de changer la boucle For en AfterTextChanged()sous-méthode sur l'activité principale et de supprimer l'appel àGetFilter()

sek13300
la source
1
Veuillez clarifier votre solution. Je suis aussi dans une situation qui ressemble à u /
nAkhmedov
1
adapter.notifyDataSetChanged()
nithin joseph
la source
2
C'est une bonne pratique sur Stack Overflow d'ajouter une explication sur les raisons pour lesquelles votre solution devrait fonctionner ou est meilleure que les solutions existantes. Pour plus d'informations, lisez Comment répondre .
Samuel Liew
0

J'obtenais également exactement la même erreur et j'utilisais AsyncTask:

`java.lang.IllegalStateException:` The content of the adapter has changed but ListView  did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131296334, class android.widget.ListView) with Adapter... etc

Je l'ai résolu en mettant adapter.notifyDataSetChanged();au bas de mon fil d'interface utilisateur, c'est ma méthode AsyncTask onPostExecute. Comme ça :

 protected void onPostExecute(Void aVoid) {

 all my other stuff etc...
    all my other stuff etc...

           adapter.notifyDataSetChanged();

                }

            });
        }

Maintenant, mon application fonctionne.

EDIT: En fait, mon application s'est toujours plantée environ une fois sur 10, donnant la même erreur.

Finalement, je suis tombé runOnUiThreadsur un article précédent, qui, à mon avis, pourrait être utile. Alors je l'ai mis dans ma méthode doInBackground, comme ceci:

@Override
protected Void doInBackground(Void... voids) {

    runOnUiThread(new Runnable() {
                      public void run() { etc... etc...

Et j'ai supprimé la adapter.notifyDataSetChanged();méthode. Maintenant, mon application ne plante jamais.

CHarris
la source
0

Veuillez essayer l'une de ces solutions:

  1. Parfois, si vous ajoutez un nouvel objet à la liste de données dans un thread (ou une doInBackgroundméthode), cette erreur se produit. La solution est: créer une liste temporaire et ajouter des données à cette liste dans le thread (ou doInBackground), puis copier toutes les données de la liste temporaire vers la liste des adaptateurs dans le thread d'interface utilisateur (ou onPostExcute)

  2. Assurez-vous que toutes les mises à jour de l'interface utilisateur sont appelées dans le fil de discussion de l'interface utilisateur.

Phuc Tran
la source
0

J'ai eu le même problème lors de l'ajout de nouvelles données dans le chargeur d'image paresseux que je viens de mettre

         adapter.notifyDataSetChanged();

dans

       protected void onPostExecute(Void args) {
        adapter.notifyDataSetChanged();
        // Close the progressdialog
        mProgressDialog.dismiss();
         }

j'espère que ça vous aide

Hobii Sgonf
la source
0

Comme @Mullins a dit: "
J'ai à la fois ajouté les éléments et appelé notifyDataSetChanged()dans le fil de l'interface utilisateur et j'ai résolu cela. - Mullins".

Dans mon cas, j'ai asynctasket j'ai appelé notifyDataSetChanged()la doInBackground()méthode et le problème est résolu, lorsque j'ai appelé de onPostExecute()j'ai reçu l'exception.

Mine Ciro
la source
0

J'avais une coutume ListAdapteret j'appelais super.notifyDataSetChanged()au début et non à la fin de la méthode

@Override
public void notifyDataSetChanged() {
    recalculate();
    super.notifyDataSetChanged();
}
Pascalius
la source
0

J'avais la même situation, j'avais de nombreux groupes de boutons insérés dans mon élément sur listview et je changeais certaines valeurs booléennes à l'intérieur de mon élément comme holder.rbVar.setOnclik ...

mon problème est survenu parce que j'appelais une méthode dans getView (); et enregistrait un objet dans sharepreference, j'ai donc eu la même erreur ci-dessus

Comment je l'ai résolu; J'ai supprimé ma méthode dans getView () pour notifyDataSetInvalidated () et le problème a disparu

   @Override
    public void notifyDataSetChanged() {
        saveCurrentTalebeOnShare(currentTalebe);
        super.notifyDataSetChanged();
    }
Sam
la source
0

J'ai eu le même problème. enfin j'ai la solution

avant de mettre à jour la liste, si le clavier virtuel est présent, fermez-le d'abord. après cela, définissez la source de données et appelez notifydatasetchanged ().

lors de la fermeture du clavier en interne, listview mettra à jour son interface utilisateur. il continue d'appeler jusqu'à la fermeture du clavier. cette fois, si la source de données change, cela lèvera cette exception. si les données sont mises à jour dans onActivityResult, il y a une chance pour la même erreur.

 InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(v.getWindowToken(), 0);

        view.postDelayed(new Runnable() {
            @Override
            public void run() {
                refreshList();
            }
        },100L);
sreejith
la source
0

Ma solution:

1) Créez un fichier temp ArrayList.

2) faites vos gros travaux (sqlite row fetch, ...) dans la doInBackgroundméthode et ajoutez des éléments à la liste des temp.

3) ajoutez tous les éléments de temp araylist à l'arraylist de votre listview dans onPostExecutemethod.

note:vous voudrez peut-être supprimer certains éléments de la vue de liste et également supprimer de la base de données sqlite et peut-être supprimer certains fichiers liés aux éléments de sdcard, supprimer simplement les éléments de la base de données et supprimer leurs fichiers associés et les ajouter à temp arraylist dans background thread. puis dans UI threadsupprimer les éléments existants dans la liste des éléments temporaires de la liste des listes.

J'espère que cela t'aides.

Personne8
la source