0

I have a listView whixh is refresh each time the client (my android app) receive a message from the server but the problem is when i click to many time on the refresh button the app crash and send me this error: 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. Make sure your adapter calls notifyDataSetChanged() when its content changes.

edit 1: the crash does not happen every time it onlu happen when i click about ten times quickly on the refresh button

activity code:

 class receiveimplements Runnable {
    @Override
    public void run() {
        try {

            if (socket != null && socket.isConnected()) {

                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                for (; ; ) {
                    while ((textRecu = reader.readLine()) != null) {
                        if (textRecu.length() < 6) {
                            bug = true;
                            break;
                        }

                        Log.d("textrecuapres", textRecu);
                        index++;
                        if (index == 1) {
                            listTitre.add(0, textRecu);
                            sendListeAndMakeAffichage(listSource,listTitre);
                        } else if (index == 2) {
                            listSource.add(0, textRecu);
                            index = 0;
                        }
                    }
                }
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

public void sendListeAndMakeAffichage(final List<String> sourceList,final List<String> sourceTitre) {

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            fragmentListe.setListTitreAndListSource(sourceTitre, sourceList);

        }
    });

}

Listfragment code:

public void setListTitreAndListSource(List<String> listTitre, List<String> listSource) {

    this.listTitre = listTitre;
    this.listSource = listSource;

    adapter.bind(listTitre);
    setListAdapter(adapter);
}

adapter code:

  public void bind(List<String> model) {
    notifyDataSetChanged();
    listeTitre = model;
    notifyDataSetChanged();
}
Dan Snow
  • 127
  • 1
  • 11

2 Answers2

4

When you add to listTitre, without calling notifyDataSetChanged()

  if (index == 1) {
     listTitre.add(0, textRecu);
     sendListeAndMakeAffichage(listSource,listTitre);

This is changing the contents of the adapter. You do schedule a call to notifyDataSetChanged() however it is not guaranteed to be scheduled before the ListView's render which will validate it's state. The adapter has to be manipulated within the same handled message as the notifyDataSetChanged() call in order to guarantee it all happens at once in the next render.

So right now you are doing:

Thread-1: Add to List

Main Thread : evaluatePendingMessages([validate ListView, runnable which calls notifyDataSetChanged()]

You have to modify the adapter within the same event as the part that updates the adapter (And they both have to root from the main thread)

So if this is valid with your logic you should do this.

  if (index == 1) {
     sendListeAndMakeAffichage(listSource,listTitre, textRecu);
  } ....

Then inside of that method:

public void sendListeAndMakeAffichage(final List<String> sourceList,final List<String> sourceTitre, final String newEntry) {

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            listTitre.add(newEntry); // Now we manipulate the list in this runnable
            // While the next call to the fragment will finish updating the adapter
            // and call notifyDataSetChanged()
            fragmentListe.setListTitreAndListSource(sourceTitre, sourceList);

        }
    });

}
Greg Giacovelli
  • 10,164
  • 2
  • 47
  • 64
  • so you advise me to add notifyDataSetChanged(); here ? `public void sendListeAndMakeAffichage(final List sourceList,final List sourceTitre) { runOnUiThread(new Runnable() { @Override public void run() { fragmentListe.setListTitreAndListSource(sourceTitre, sourceList); } });` – Dan Snow Jan 01 '14 at 23:12
  • No you already do it in the `fragment.Liste.setListTier....();` I am saying you have to perform the addition AND the notifyDataSetChanged inside of the same Runnable. Your textRecu should be added in the same runnable on the UIThread – Greg Giacovelli Jan 01 '14 at 23:14
  • thank you very very much for your help :) it works, I didn't know that i used to manipulate the list in the UI thread – Dan Snow Jan 01 '14 at 23:49
3

As the error message informs you, you cannot modify UI elements from a non-UI thread (and your Runnable uses a separate thread).

Use a Handler to update the UI thread. See this answer, for example: https://stackoverflow.com/a/12717105/240646

Community
  • 1
  • 1
Szymon
  • 42,577
  • 16
  • 96
  • 114
  • I got the same error as previously I forgot to say that the crash does not happen every time it only happen when i click about ten times quickly on the refresh button – Dan Snow Jan 01 '14 at 23:08