Aktualisierung ListView

  • Antworten:6
  • Bentwortet
Bärbel R.
  • Forum-Beiträge: 14

07.01.2016, 13:23:02 via Website

Hallo Leute,
ich bin neu in der Android-Programmierung und versuche seit Tagen vergeblich ein Problem zu lösen:
Meine MainActivity mit NavigationDrawer ruft nach Klick auf einen Drawer-Eintrag ein ListFragment auf, das eine ListView anzeigt.
Dazu werden Daten (MyData-Objekte) aus einer SQLite-DB gelesen. Damit das nicht im UI-Thread erfolgt, habe ich eine Handler-Klasse DbHandler, die Runnable implementiert und über die ich asynchron auf die DB zugreife (Übergabe ArrayList sqlInputData, Rückgabe ArrayList sqlOutputData):

public class MyDataFragment extends ListFragment …

// Objekt der Handlerklasse für Arbeitsthread initialisieren
private DbHandler mDbHandler = null;

private void updateListAdapterData() {
    // alle DS aus DB lesen
    // Eingabedaten für Arbeitsthread vorbereiten 
    // (hier keine notwendig, da alle DS gelesen werden)
    ArrayList sqlInputData = new ArrayList();
    // Methode aufrufen, die SQL-Aktion antriggert und Arbeitsthread erzeugt; 
    // setNewSqlAction liefert nur true, wenn kein Thread läuft
    if (mDbHandler.setNewSqlAction(DbHandler.CMD_GET_ALL_MYDATA, sqlInputData)) {
        new Thread(mDbHandler).start();
    }
}

Das liefert mir die Daten für den ListAdapter, der ein Objekt meiner Klasse MyListDataArrayAdapter, abgeleitet von ArrayAdapter, ist:

public void handleSqlResponse(int sqlCommand, ArrayList sqlOutputData) {
    // Switch Statement, um für jede Aktion eine geeignete Ergebnisbehandlung durchzuführen
    switch (sqlCommand) {
        case DbHandler.CMD_GET_ALL_MYDATA:
             // Liste für Anzeige in der ListView
            List<MyData> mMyDataList =  (List<MyData>;) sqlOutputData.get(0);
            MyListDataArrayAdapter myListDataArrayAdapter =  
                           new MyListDataArrayAdapter(getActivity(),mMyDataList);
            // Adapter setzen
            setListAdapter(myListDataArrayAdapter);
            // Mehrzeilige Auswahl für CAB erlauben
            getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
            break;

Die Methode updateListAdapterData rufe ich in onResume() und in Alert-Dialogen nach dem Neuerstellen oder Löschen von MyData-Objekten auf. Hier funktioniert alles und meine ListView wird aktualisiert angezeigt.

Nach Klick auf einen Listeneintrag starte ich eine 2. Activity, in der die Details dargestellt und geändert werden. Dort wird bei onStop() eine Speicherung der geänderten Daten in der DB asynchron über die DbHandler-Klasse gestartet.

Meine ListView bringt diese Änderung nicht, obwohl die Speicherung erfolgt. Der Test zeigt, das onResume() des ListFragments aufgerufen wird, bevor der Speichern-Thread zu ende ist. Damit ist die Speicherung noch nicht abgeschlossen, wenn der Adapter aktualisiert wird.

Kann mir bitte jemand helfen?

Antworten
Pascal P.
  • Admin
  • Forum-Beiträge: 11.286

07.01.2016, 15:48:12 via Website

Hallo Bärbel,
Herzlich wilkommen hier im Forum :)



Ist es so eine DatenMasse, dass du unbedingt einen eigenen SpeichernThread brauchst?
Alternativ, kannst du in der onStop solange waren, bis der Thread fertig ist, das ist zwar nicht optimal, aber besser als nichts.
Oder du implementierst den spechern Thread schon vor der onStop und reagierst dann auf den Back-Button oder speichern etc.
Dabei startest du dann deinen Thread mit callback. In der Zeit in der der Thread läd, könntest du einen ProgressDialog anzeigen lassen.
Sobald der Thread fertig ist und das Callback aufgerufen wird, verlässt/beendest du einfach die aktuelle Acitivity. Damit würd wieder onResume in der ListActivity aufgerufen und alles wird aktualisiert.

— geändert am 07.01.2016, 15:48:53

LG Pascal //It's not a bug, it's a feature. :) ;)

Antworten
Bärbel R.
  • Forum-Beiträge: 14

07.01.2016, 18:14:54 via Website

Hallo Pascal,

vielen Dank für Deine Hinweise.

Da ich verschiedene Datenobjekte in mehreren Tabellen verwalte, bin ich davon ausgegangen, dass ein eigener Thread notwendig ist. Die Datenmenge ist zwar nicht so groß, aber ich habe überall gelesen, dass man die DB-Zugriffe möglichst in einem eigenen Thread machen soll. Ich fange gerade erst mit der Android-Programmierung an und habe keine Erfahrung, ob es wirklich nötig ist. Das Speichern funktioniert aber.

Jetzt habe ich versucht, zu warten, bis der Thread fertig ist:

protected void onStop() {
    super.onStop();
    // Speichern der Daten im asynchronen Thread
    updateData();
    // Läuft noch Speichern-Thread?
    Boolean isWaiting = true;
    // Warten, bis Speichern-Thread aus Detail-Anzeige beendet ist: isBusy = false
    while (isWaiting) {
        Log.d(LOG, "onStop isBusy " + mDbHandlerTab.isBusy());
        if (!mDbHandlerTab.isBusy()) {
            // Thread ist beendet
            isWaiting = false;
            Log.d(LOG, "onStop isBusy " + mDbHandlerTab.isBusy());
        }
    }
}

Leider hat das nichts geändert. Im locat steht mehrfach ,
...D/ TabActivity: onStop isBusy true
dann
...D/ TabActivity : onStop isBusy false
und die ListView ist nicht aktuell.

Was meinst Du mit:

Dabei startest du dann deinen Thread mit callback.

Sobald der Thread fertig ist und das Callback aufgerufen wird, verlässt/beendest du einfach die aktuelle Acitivity.

? Wie starte ich einen Thread "mit callback"? Aus dem Thread kommt er ja in die Methode handleSqlResponse zurück. Dann ist der Thread auch fertig. Sollte ich dort die Activity mit finish() beenden?

Antworten
Pascal P.
  • Admin
  • Forum-Beiträge: 11.286

07.01.2016, 18:33:43 via App

So ganz passen tut das noch nicht. Mach es lieber ausserhalb der OnStop und mit dnem einem Thread callback.
Wie sieht überhaupt dein speichern Thread aus?

Edit ich glaube ich kann es dir besser erklären wenn ich Code habe :)

— geändert am 07.01.2016, 18:34:34

LG Pascal //It's not a bug, it's a feature. :) ;)

Antworten
Bärbel R.
  • Forum-Beiträge: 14

08.01.2016, 13:34:13 via Website

Hallo Pascal,
gern möchte ich Deinen Hinweis umsetzen, es außerhalb von onStop zu machen. Ich weis aber nicht wo und wie.
Die 2. Activity ist eine Tab-Activity mit 3 Pages (Fragments), um verschiedene Daten, teilweise mit expliziten Intents, einzugeben. Der Nutzer kann selbst jederzeit durch Klick auf ein Symbol der Toolbar ein Speichern veranlassen (mit Alert-Dialog; funktioniert) und vor Ausführung der expliziten Intents erfolgt das Speichern automatisch.
Alle vorhandenen Daten werden zunächst beim Start eingelesen und stehen den Fragments als public static Variable der Activity zur Verfügung. Änderungen werden über die Listener der Steuerelemente in diesen Variablen zwischengespeichert.
Mein Ziel ist es, beim Verlassen des Programms einen Alert-Dialog aufzurufen, wenn es noch ungespeicherte Änderungen gibt (entsprechendes Flag gesetzt ist), und nur zu Speichern, wenn der User das auswählt. Damit will ich dem User die Möglichkeit geben, Änderungen zu verwerfen.
Den Alert-Dialog bei onStop aufzurufen (, der beim Klick auf Speichern funktioniert), ist mir bisher allerdings auch nicht gelungen (Er wird ohne weitere Fehlermeldung übergangen.), so dass ich erst einmal nur die Speichern-Methode aufrufe, die funktioniert.
Dir Code zu zeigen ist nicht ganz einfach, weil ich viele verschiedene Klassen nutze. Ich versuche mal das Wichtigste der Klasse DbHandler und des Speichern-Moduls der 2. Activity MyDataDetailTabActivity zusammenzufassen:

Ich nutze zum Speichern die gleiche Handler-Klasse DbHandler, wie in der 1. Activity:

// Objekt der Handlerklasse für Arbeitsthread initialisieren
private DbHandler mDbHandlerTab = null;

und in onCreate:

mDbHandlerTab = new DbHandler(mMyDataDataSource, this);

wodurch in der DbHandler-Klasse der entsprechende Konstruktor aufgerufen wird:

public class DbHandler implements Runnable {
…
    // Um nicht zwei Aktionen gleichzeitig auszuführen, wird ein Busy-Flag auf true gesetzt ,
    // solange die aktuelle Aktion noch nicht abgeschlossen ist.
    private boolean isBusy = false;
    // Da verschiedene Aktionen ausgeführt werden, wird die angeforderte Aktion
    // als Zahlencode mit einer eigenen Integer gemerkt, die hier gespeichert wird.
    private int currentSqlCommand = -1;
    // Flag, welches Fragment/Activity den Thread startet
    private int myInstance = -1;
    // lnstanzvariable für DB, welche später im Konstruktor übergeben wird
    private MyDataDataSource mMyDataDataSource = null;
…
    public static final int OBJ_MYDATADETAILTABACTIVITY = 3;
...
    // Referenz auf den Context, um die Ergebnisse der Abfragen an den UI Thread
    // weiterleiten zu können
    private MyDataDetailTabActivity myDataDetailTabActivity = null;
…
    //  Eingangs- und Ausgangsdaten
    private ArrayList sqlInputData = null;
    private ArrayList sqlOutputData = null;
…
    public static final int CMD_UPDATE_DATA = 9;
...
    // Konstruktor für MyDataDetailTabActivity
    public DbHandler(MyDataDataSource mMyDataDataSource, MyDataDetailTabActivity myDataDetailTabActivity) {
        this.mMyDataDataSource = mMyDataDataSource;
        this.myDataDetailTabActivity = myDataDetailTabActivity;
        this.myInstance = OBJ_MYDATADETAILTABACTIVITY;
    }
…
    public synchronized boolean setNewSqlAction(int newSqlComand, ArrayList sqlData) {
        // Läuft eine Aktion?
        if (this.isBusy) {
            // Ja - weitere Aktion kann nicht gestartet werden
            return false;
        }
        // Aktion kann gestartet werden - Flag setzen, weil ab jetzt die Aktion läuft
        this.isBusy = true;
        // Parameter übernehmen
        this.currentSqlCommand = newSqlComand;
        this.sqlInputData = sqlData;
        // true zurückgegeben als Indikation, dass der Befehl erfolgreich registriert wurde
        return true;
    }
…
    @Override
    public void run() {
        // Zur Reduzierung von "Ressourcen-Konkurenz" mit dem UI-Thread
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
        // Bereitstellung der Ergebnisse
        setResult();
        // Rückgabe der Ergebnisse an den UI-Thread
        restoreResult();
        // Busy-Flag zurücksetzen auf false, damit weitere Befehle abgearbeitet werden können
        this.isBusy = false;
    }
…
    // Getter zum einfachen Zugriff auf isBusy
    public boolean isBusy() {
        return isBusy;
    }

Die 2. Activity hat folgende Speichermethode:

 private void updateData() {
    // Eingabedaten für Arbeitsthread vorbereiten
    ArrayList sqllnputData = new ArrayList();
    sqllnputData.add(mMyData);
    sqllnputData.add(mWerteRankingList);
    sqllnputData.add(mNotiz);
    sqllnputData.add(mPartner);
    // Methode aufrufen, die SQL-Aktion antriggert und Arbeitsthread erzeugt
    // nur wenn Methode true liefert, kann Thread gestartet werden
    if (mDbHandlerTab.setNewSqlAction(DbHandler.CMD_UPDATE_DATA, sqllnputData)) {
        // Erzeugen und Starten eines neuen Thread
        new Thread(mDbHandlerTab).start();
    } else {
        Log.d(LOG, "Thread läuft noch");
        // anderer Thread läuft noch - Hinweis an den User, die Aktion zu wiederholen
        Toast.makeText(getApplicationContext(),
                getText(R.string.toast_mydata_busy),
                Toast.LENGTH_LONG).show();
    }
 }

Ich hoffe, dass das Dir die entscheidenden Codestellen zeigt.

Kannst Du mir bitte erläutern, was Du mit "außerhalb onStop()" und "Thread mit callback" meinst?

Antworten
Pascal P.
  • Admin
  • Forum-Beiträge: 11.286

08.01.2016, 18:37:04 via Website

Ausserhlb der onStop meint:

einmal reagierst du auf den Home/Back Button in der Tollbar.
Dafür musst du in der onOptionsItemSelected der Activity auf die Integer android.R.id.home reagieren. (Das machst du vielleicht schon, dann musst du an dieser stelle nur Code ergänzen.

Zum andern musst du den speichern Code auch ausführen, wenn der Nutzer auf zurück drückt.
Dafür kannst du die onBackPressed() Methode der Activity überschreiben und darin den speichern code aufrufen.

Da du jetzt an 2 stellen speichern willst, wäre es von Vorteil du definierst eine Methode save()
in welcher du einen AlertDialog aufrufst ud den Nutzer wählen lässt ob speichern ja oder nein.
Ist das Ergebnis des AlertDialoges Ja, dann speicherst du deine Werte.

Zum Callback:
Ein Callback ist ein "Rückruf" wenn der Thread fertig ist.
http://stackoverflow.com/questions/18054720/what-is-callback-in-android

Erstmal definierst du ein Interface:

interface MySaveInterface
{
   void onSaveFinished();
}

Dann musst du eine Methode zum setzen des Callbacks in deinen Thread/ Runnables einfügen:

MySaveInterface callback; //Klassenvariable definieren
public void setCallback(MySaveInterface call)
{
this.callback = call; //in die Klassenvar übernehmen
}

Als vorletzes, kann das Callback dann in der run() aufgerufen werden.

    public void run()
    {
    /// hier der speichern Code...
    if(callback!=null)
    {
    callback.onSaveFinished();//Das hier vlt. noch kominieren mit Context#runInUiThread();
    }
    }


Und dann um zu speichern (evntl in der Speichern Methode):

 if (mDbHandlerTab.setNewSqlAction(DbHandler.CMD_UPDATE_DATA, sqllnputData)) {
        // Erzeugen und Starten eines neuen Thread
     mDbHandlerTab.setCallback(new MySaveInterface()
   {
    @Override
   public void onSaveFinished()
   {
   //hier der Code wenn der Thread fertig ist, wie z.b. finisher() zum Activity beenden und ProgressDialog entfernen
    }

}
        new Thread(mDbHandlerTab).start();
    } else {
        Log.d(LOG, "Thread läuft noch");
        // anderer Thread läuft noch - Hinweis an den User, die Aktion zu wiederholen
        Toast.makeText(getApplicationContext(),
                getText(R.string.toast_mydata_busy),
                Toast.LENGTH_LONG).show();
    }

LG Pascal //It's not a bug, it's a feature. :) ;)

Bärbel R.

Antworten
Bärbel R.
  • Forum-Beiträge: 14

11.01.2016, 14:36:41 via Website

Hallo Pascal,
vielen, vielen Dank für Deine Hilfe! Jetzt klappt alles und meine ListView wird aktualisiert.

Pascal P.

Antworten