Android ListView wird nach notifyDataSetChanged nicht aktualisiert

116

Mein ListFragment-Code

public class ItemFragment extends ListFragment {

    private DatabaseHandler dbHelper;
    private static final String TITLE = "Items";
    private static final String LOG_TAG = "debugger";
    private ItemAdapter adapter;
    private List<Item> items;


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.item_fragment_list, container, false);        
        return view;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.setHasOptionsMenu(true);
        super.onCreate(savedInstanceState);
        getActivity().setTitle(TITLE);
        dbHelper = new DatabaseHandler(getActivity());
        items = dbHelper.getItems(); 
        adapter = new ItemAdapter(getActivity().getApplicationContext(), items);
        this.setListAdapter(adapter);

    }



    @Override
    public void onResume() {
        super.onResume();
        items.clear();
        items = dbHelper.getItems(); //reload the items from database
        adapter.notifyDataSetChanged();
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);
        if(dbHelper != null) { //item is edited
            Item item = (Item) this.getListAdapter().getItem(position);
            Intent intent = new Intent(getActivity(), AddItemActivity.class);
            intent.putExtra(IntentConstants.ITEM, item);
            startActivity(intent);
        }
    }
}

Meine ListView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

Dies aktualisiert aber nicht die ListView. Auch nach dem Neustart der App werden die aktualisierten Elemente nicht angezeigt. Mein ItemAdaptererstreckt sichBaseAdapter

public class ItemAdapter extends BaseAdapter{

    private LayoutInflater inflater;
    private List<Item> items;
    private Context context;

    public ProjectListItemAdapter(Context context, List<Item> items) {
        super();
        inflater = LayoutInflater.from(context);
        this.context = context;
        this.items = items;

    }

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

    @Override
    public Object getItem(int position) {
        return items.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ItemViewHolder holder = null;
        if(convertView == null) {
            holder = new ItemViewHolder();
            convertView = inflater.inflate(R.layout.list_item, parent,false);
            holder.itemName = (TextView) convertView.findViewById(R.id.topText);
            holder.itemLocation = (TextView) convertView.findViewById(R.id.bottomText);
            convertView.setTag(holder);
        } else {
            holder = (ItemViewHolder) convertView.getTag();
        }
        holder.itemName.setText("Name: " + items.get(position).getName());
        holder.itemLocation.setText("Location: " + items.get(position).getLocation());
        if(position % 2 == 0) {                                                                                 
            convertView.setBackgroundColor(context.getResources().getColor(R.color.evenRowColor));
        } else {    
            convertView.setBackgroundColor(context.getResources().getColor(R.color.oddRowColor));
        }
        return convertView;
    }

    private static class ItemViewHolder {
        TextView itemName;
        TextView itemLocation;
    }
}

Kann mir bitte jemand helfen?

Codierer
quelle
2
Haben Sie getestet, ob der Datenbankvorgang ordnungsgemäß funktioniert? Wie sieht der Adapter aus? Wenn Sie ein On-Objekt für die adapterReferenz erstellen, warum testen Sie es dann eine Zeile weiter unten auf Null?
Luksprog
Code löst keine Ausnahme aus und ich habe dies mit Debug überprüft. Alle Methoden werden fehlerfrei ausgeführt. Ja, das ist ein dummer Fehler.
Coder

Antworten:

229

Schauen Sie sich Ihre onResumeMethode an in ItemFragment:

@Override
public void onResume() {
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); // reload the items from database
    adapter.notifyDataSetChanged();
}

Was Sie gerade vor dem Aufruf aktualisiert haben, notifyDataSetChanged()ist nicht das Feld des Adapters, private List<Item> items;sondern das identisch deklarierte Feld des Fragments. Der Adapter speichert weiterhin einen Verweis auf die Liste der Elemente, die Sie beim Erstellen des Adapters übergeben haben (z. B. in onCreate des Fragments). Die kürzeste (im Sinne der Anzahl der Änderungen), aber nicht elegante Möglichkeit, Ihren Code so zu verhalten, wie Sie es erwarten, besteht darin, einfach die Zeile zu ersetzen:

    items = dbHelper.getItems(); // reload the items from database

mit

    items.addAll(dbHelper.getItems()); // reload the items from database

Eine elegantere Lösung:

1) Elemente entfernen private List<Item> items;von ItemFragment- wir müssen nur im Adapter auf sie verweisen

2) Ändern Sie onCreate in:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    super.setHasOptionsMenu(true);
    getActivity().setTitle(TITLE);
    dbHelper = new DatabaseHandler(getActivity());
    adapter = new ItemAdapter(getActivity(), dbHelper.getItems());
    setListAdapter(adapter);
}

3) Methode in ItemAdapter hinzufügen:

public void swapItems(List<Item> items) {
    this.items = items;
    notifyDataSetChanged();
}

4) Ändern Sie Ihren onResume in:

@Override
public void onResume() {
    super.onResume();
    adapter.swapItems(dbHelper.getItems());
}
Tomasz Gawel
quelle
Wäre es nicht sauberer, das ganze dbHelper-Ding in den Adapter zu verschieben? Sie würden also nur anrufen adapter.swapItems();und der Adapter würde das dbHelper.getItems()Zeug erledigen . Aber trotzdem danke für die Antwort :)
Ansgar
7
Warum sollten Sie die Elemente löschen () und erneut hinzufügen müssen? Ist das nicht genau der Zweck von notifyDataSetChanged()?
Phil Ryan
1
@tomsaz können Sie mir mit diesem stackoverflow.com/questions/28148618/…
1
Danke @tomsaz Gawel, deine SwapItems helfen mir sehr, ich weiß nicht, warum mein Adapter.notifydatasetchanged nicht funktioniert, da die "Liste", die ich übergebe, ebenfalls aktualisiert wird, auch wenn ich sie durch Drucken des Protokolls überprüft habe. Kannst du mir das bitte erklären? Konzept
Kimmi Dhingra
1
Diese Antwort ist richtig. Das Problem ist, dass die Array-Liste des ADAPTER-Elements nicht aktualisiert wurde. Dies bedeutet, dass Sie notifydatasetchanged aufrufen können, bis Ihr Gesicht ohne Wirkung blau ist. Der Adapter aktualisiert Ihr Dataset mit demselben Dataset, sodass KEINE Änderungen vorgenommen werden. Eine andere Alternative zu der in dieser Antwort veröffentlichten Lösung, die möglicherweise sauberer ist, ist: adapter.items = items; adapter.notifyDataSetChanged ();
Ray Li
23

Sie weisen globalen Variablenelementen in neu geladene Elemente zu onResume(), dies wird jedoch in der ItemAdapterKlasse nicht berücksichtigt , da es eine eigene Instanzvariable mit dem Namen "Elemente" gibt.

Fügen ListViewSie zum Aktualisieren in der ItemAdapterKlasse ein refresh () hinzu , das Listendaten, dh Elemente, akzeptiert

class ItemAdapter
{
    .....

    public void refresh(List<Item> items)
    {
        this.items = items;
        notifyDataSetChanged();
    } 
}

Update onResume()mit folgendem Code

@Override
public void onResume()
{
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); //reload the items from database
    **adapter.refresh(items);**
}
Santhosh
quelle
1
Das ist genau richtig. Der Konstruktor des Adapters erwartet, dass Elemente übergeben werden, aktualisiert jedoch immer nur das Feld der äußeren Klasse.
LuxuryMode
Hallo Santhosh. Können Sie sich ein ähnliches Problem ansehen
8

Ändern Sie in onResume () diese Zeile

items = dbHelper.getItems(); //reload the items from database

zu

items.addAll(dbHelper.getItems()); //reload the items from database

Das Problem ist, dass Sie Ihrem Adapter niemals von der Liste der neuen Elemente erzählen. Wenn Sie Ihrem Adapter keine neue Liste übergeben möchten (wie es scheint, tun Sie dies nicht), verwenden Sie sie einfach items.addAllnach Ihrem clear(). Dadurch wird sichergestellt, dass Sie dieselbe Liste ändern, auf die der Adapter verweist.

Justin Breitfeller
quelle
Es ist verwirrend, dass adapter.clear()der Adapter nicht zwingt zu erkennen, dass die Ansicht aktualisiert werden soll, aber adapter.add()oder adapter.addAll(). Danke für die Antwort!
w3bshark
Beachten Sie, dass ich items.addAll()adapter.addAll () verwendet habe und nicht. Das einzige, was den Adapter auf Änderungen reagieren lässt, ist das notifyDataSetChanged. Der Grund, warum der Adapter überhaupt Änderungen sieht, ist, dass die itemsListe dieselbe Liste ist, die der Adapter verwendet.
Justin Breitfeller
4

Wenn der Adapter bereits eingestellt ist, wird durch erneutes Einstellen die Listenansicht nicht aktualisiert. Überprüfen Sie stattdessen zuerst, ob die Listenansicht einen Adapter hat, und rufen Sie dann die entsprechende Methode auf.

Ich denke, es ist keine sehr gute Idee, beim Festlegen der Listenansicht eine neue Instanz des Adapters zu erstellen. Erstellen Sie stattdessen ein Objekt.

BuildingAdapter adapter = new BuildingAdapter(context);

    if(getListView().getAdapter() == null){ //Adapter not set yet.
     setListAdapter(adapter);
    }
    else{ //Already has an adapter
    adapter.notifyDataSetChanged();
    }

Sie können auch versuchen, die Aktualisierungsliste im UI-Thread auszuführen:

activity.runOnUiThread(new Runnable() {         
        public void run() {
              //do your modifications here

              // for example    
              adapter.add(new Object());
              adapter.notifyDataSetChanged()  
        }
});
AlexGo
quelle
Ich bin nicht sicher, wie ich UI-Thread implementieren soll. Meine Hauptaktivität besteht aus 3 Fragmenten (Registerkarten) und der Code in der Frage bezieht sich auf eines der Fragmente, die eine Listenansicht enthalten. Der Grund für die Übergabe von Elementen an ItemAdapterist, dass ich die Zeilen einfärben möchte und in der Listenansicht mehrere Datenelemente angezeigt werden. Ich habe den Code für den Adapter gepostet.
Coder
Sie müssen Ihren Code, der Ihre Liste auffüllt, mit "this" in meinen Beispielcode einfügen. statt "Aktivität"
AlexGo
In einigen Fällen wird es nicht aktualisiert, wenn Sie notifyDataSetChanged () in einem anderen Thread ausführen. Daher ist die obige Lösung in einigen Fällen richtig.
Ayman Al-Absi
4

Wenn Sie Ihre Listenansicht aktualisieren möchten spielt keine Rolle , wenn Sie wollen , dass auf onResume(), onCreate()oder in einer anderen Funktion, erste , was Sie zu erkennen ist , dass Sie nicht brauchen eine neue Instanz des Adapters, nur bevölkern erstellen die Arrays mit Ihren Daten wieder. Die Idee ist ähnlich:

private ArrayList<String> titles;
private MyListAdapter adapter;
private ListView myListView;

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);

    myListView = (ListView) findViewById(R.id.my_list);

    titles = new ArrayList<String>()

    for(int i =0; i<20;i++){
        titles.add("Title "+i);
    }

    adapter = new MyListAdapter(this, titles);
    myListView.setAdapter(adapter);
}


@Override
public void onResume(){
    super.onResume();
    // first clear the items and populate the new items
    titles.clear();
    for(int i =0; i<20;i++){
        titles.add("New Title "+i);
    }
    adapter.notifySetDataChanged();
}

Abhängig von dieser Antwort sollten Sie dasselbe List<Item>in Ihrem verwenden Fragment. Bei Ihrer ersten Adapterinitialisierung füllen Sie Ihre Liste mit den Elementen und setzen den Adapter auf Ihre Listenansicht. Danach müssen Sie bei jeder Änderung Ihrer Artikel die Werte aus der Hauptposition löschen List<Item> itemsund sie dann erneut mit Ihren neuen Artikeln füllen und aufrufen notifySetDataChanged();.

So funktioniert das : ).

h4rd4r7c0r3
quelle
Danke für die Antwort. Ich habe die Änderungen vorgenommen, wie Sie erwähnt haben. Ich habe meinen Code gepostet. Es funktioniert immer noch nicht. Jetzt wird nicht einmal die Listenansicht angezeigt, wenn neue Elemente hinzugefügt werden.
Coder
Ich habe den Code geändert. Das Seltsame ist, dass das Element in DB
Coder am
Dieser Thread ist für die Datenbank stackoverflow.com/questions/14555332/…
Coder
3

Eine Antwort von AlexGo hat den Trick für mich getan:

getActivity().runOnUiThread(new Runnable() {
        @Override
        public void run() {
         messages.add(m);
         adapter.notifyDataSetChanged();
         getListView().setSelection(messages.size()-1);
        }
});

Das Listen-Update hat bei mir zuvor funktioniert, als das Update von einem GUI-Ereignis ausgelöst wurde und sich somit im UI-Thread befand.

Wenn ich jedoch die Liste von einem anderen Ereignis / Thread aktualisiere, dh von einem Aufruf von außerhalb der App, befindet sich das Update nicht im UI-Thread und ignoriert den Aufruf von getListView. Das Aufrufen des Updates mit runOnUiThread wie oben hat den Trick für mich getan. Vielen Dank!!

user2996950
quelle
3

Versuche dies

@Override
public void onResume() {
super.onResume();
items.clear();
items = dbHelper.getItems(); //reload the items from database
adapter = new ItemAdapter(getActivity(), items);//reload the items from database
adapter.notifyDataSetChanged();
}
Gautami
quelle
3
adpter.notifyDataSetInvalidated();

Versuchen Sie dies in der onPause()Methode der Aktivitätsklasse.

So M
quelle
1
adapter.setNotifyDataChanged()

sollte den Trick machen.

Hitman
quelle
3
Wo ist die Frage hier zu setzen?
SwiftBoy
1

Wenn Ihre Liste im Adapter selbst enthalten ist, sollte auch der Aufruf der Funktion zum Aktualisieren der Liste aufgerufen werden notifyDataSetChanged().

Das Ausführen dieser Funktion über den UI-Thread hat den Trick für mich getan:

Die refresh()Funktion im Adapter

public void refresh(){
    //manipulate list
    notifyDataSetChanged();
}

Führen Sie diese Funktion dann wiederum über den UI-Thread aus

getActivity().runOnUiThread(new Runnable() { 
    @Override
    public void run() {
          adapter.refresh()  
    }
});
Dévan Coetzee
quelle
Dies machte in der Tat einen Unterschied für mich, da das Update über einen anderen Thread über das Netzwerk kam.
Chuck
0

Versuchen Sie es so:

this.notifyDataSetChanged();

anstatt:

adapter.notifyDataSetChanged();

Sie müssen notifyDataSetChanged()auf die ListViewnicht mit dem Adapter - Klasse.

Jachu
quelle
Natürlich nicht, die einzige Chance, wenn die Aktivität um eine Listenansicht erweitert wird
cmario