android sqlite introT8_wowomnom_Fotolia_85154667

Android SQLite Datenbank Tutorial – Teil 8: SQLite Datenbank Upgrade in Android


Um dies alles zu realisieren, müssen wir alle Klassen unseres Android Projekts überarbeiten. Wir werden an den Klassen folgende Änderungen vornehmen:

  1. ShoppingMemo – Diese Klasse ist unser Datenmodell und repräsentiert einen Datensatz der SQLite Datenbank. Wir fügen in sie ein weiteres Attribut ein, mit dem wir den Status der jeweiligen ShoppingMemo als checked oder not checked speichern.
  2. ShoppingMemoDbHelper – Sie ist eine Hilfsklasse mit deren Hilfe wir die SQLite Datenbank erstellen lassen. Sie enthält weiterhin wichtige Konstanten, die wir für die Arbeit mit der Datenbank benötigen, wie den Tabellennamen, die Datenbankversion oder die Namen der Spalten. Wir fügen ihr eine weitere Spalte hinzu und implementieren ihre onUpgrade() Methode, um die SQLite Datenbank neu zu erstellen.
  3. ShoppingMemoDataSource – Diese Klasse ist unser Data Access Object und für das Verwalten der Daten verantwortlich. Sie unterhält die Datenbankverbindung und ist für das Hinzufügen, Auslesen und Löschen von Datensätzen zuständig. Wir nehmen in dieser Klasse Änderungen an den Methoden updateShoppingMemo() und cursorToShoppingMemo() vor.
  4. MainActivity – Von dieser Klasse aus, steuern wir unsere SQLite App. Wir fügen ihrem ListView einen OnItemClickListener zu, um damit auf kurze Klicks zu reagieren.

Nun wünschen wir euch viel Spaß beim achten Teil unseres Android SQLite Datenbank Tutorials. Los geht’s!

1. SQLite Datenbank Upgrade in Android durchführen

Der SQLite Datenbank Upgrade ist recht unkompliziert. Damit wir unsere ShoppingMemoDbHelper-Klasse von der SQLiteOpenHelper-Klasse ableiten konnten, mussten wir die beiden Methoden onCreate() und onUpgrade() überschreiben.

Die onCreate() Methode wird nur aufgerufen, falls die SQLite Datenbank noch nicht existiert.

Die onUpgrade() Methode wird aufgerufen, sobald die neue Versionsnummer höher als die alte Versionsnummer ist und somit ein Datenbank-Upgrade notwendig wird.

Wir müssen daher die onUpgrade() Methode implementieren und in ihr den SQLite Datenbank Upgrade ausführen. Dafür gibt es verschiedene Ansätze. Man kann die bereits vorhandene Tabelle verändern mit dem SQL-Befehl alter und dadurch sicherstellen, dass keine Daten verloren gehen.

Da wir bisher aber nur Testdaten in unserer SQLite Datenbank gespeichert haben, werden wir einen anderen Ansatz wählen. Wir werden die bisherige Tabelle löschen und anschließend eine neue Tabelle mit vier anstelle der bisherigen drei Spalten erzeugen.

Dazu werden wir den folgenden Quellcode verwenden:

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    Log.d(LOG_TAG, "Die Tabelle mit Versionsnummer " + oldVersion + " wird entfernt.");
    db.execSQL("DROP TABLE IF EXISTS " + TABLE_SHOPPING_LIST);
    onCreate(db);
}

Mit den oberen Zeilen wird das SQLite Datenbank Upgrade durchgeführt, sobald die Versionsnummer der SQLite Datenbank erhöht wurde. Mit der Anweisung in Zeile 4 lassen wir die alte Tabelle aus der SQLite Datenbank entfernen.

Anschließend rufen wir in Zeile 5 die onCreate() Methode auf und lassen dadurch eine neue Tabelle erzeugen. Damit auch wirklich eine neue Tabelle in der SQLite Datenbank erzeugt wird, müssen vorher noch einige Konstanten der ShoppingMemoDbHelper-Klasse verändert werden. Welche das sind werden wir im dritten Abschnitt ausführlich besprechen.

2. Ein neues Attribut in der ShoppingMemo-Klasse anlegen

Um den Status der ShoppingMemo, also ob der jeweilige Einkaufslisten-Eintrag abgehakt ist oder nicht, speichern zu können, müssen wir ein zusätzliches Attribut in der ShoppingMemo-Klasse anlegen.

Die neue Instanzvariable soll den Namen checked tragen und vom Datentyp boolean sein. Um die Membervariable zu initialisieren, müssen wir auch den bisherigen Konstruktor anpassen.

Außerdem müssen wir die Get– und Set-Methoden der Membervariable definieren, damit wir den Wert des Attributs auslesen und festlegen können.

Die eben aufgezählten Änderungen werden wir nun an der ShoppingMemo-Klasse vornehmen. Dazu öffnen wir die Klassendatei ShoppingMemo.java im Editorfenster von Android Studio über einen Klick auf den entsprechenden Eintrag in der linken Projektleiste.

Anschließend fügen wir den gelb markierten Quellcode in die ShoppingMemo-Klasse ein:

ShoppingMemo.java

package de.programmierenlernenhq.shoppinglisthq;


public class ShoppingMemo {

    private String product;
    private int quantity;
    private long id;
    private boolean checked;


    public ShoppingMemo(String product, int quantity, long id, boolean checked) {
        this.product = product;
        this.quantity = quantity;
        this.id = id;
        this.checked = checked;
    }


    public String getProduct() {
        return product;
    }

    public void setProduct(String product) {
        this.product = product;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public boolean isChecked() {
        return checked;
    }

    public void setChecked (boolean checked) {
        this.checked = checked;
    }


    @Override
    public String toString() {
        String output = quantity + " x " + product;

        return output;
    }
}

Der eingefügte Quellcode ist selbsterklärend. Die neue Instanzvariable checked wird den Status der jeweiligen ShoppingMemo speichern. Der Wert true wird für abgehakt stehen, also dass das entsprechende Produkt gekauft wurde.

In Android Studio sollte die Klasse ShoppingMemo nun wie folgt aussehen:

android sqlite shopping memo

Die überarbeitete Klasse ShoppingMemo mit Markierungen

Da unser Datenmodell nun über eine neue Eigenschaft verfügt, repräsentiert es nicht mehr die Datensätze unserer bisherigen SQLite Datenbank. Dieses Problem müssen wir als Nächstes beheben und die Vorbereitungen für das SQLite Datenbank Upgrade treffen. Dazu nehmen wir Anpassungen an unserer Hilfsklasse ShoppingMemoDbHelper vor.

3. Die ShoppingMemoDbHelper-Klasse für den SQLite Datenbank Upgrade vorbereiten

An unserer Hilfsklasse ShoppingMemoDbHelper müssen wir nun einige Änderungen vornehmen, da sich das zugrunde liegende Datenmodell geändert hat.

Unsere ShoppingMemo besitzt nun vier Attribute, ein Datensatz unserer Tabelle aber nur drei Spalten.

Wir müssen daher die alte Tabelle aus der SQLite Datenbank entfernen und eine neue Tabelle, die vier Spalten besitzt, erstellen lassen.

Um dies zu realisieren, werden wir die folgenden Änderungen vornehmen:

  1. Erhöhen der Datenbank Versionsnummer – Konstante DB_VERSION von 1 auf 2 erhöhen.
  2. Definieren einer neuen Spalte – Neue Konstante COLUMN_CHECKED als vierte Spalte anlegen.
  3. Erweitern des Create-Befehls – Die Konstante SQL_CREATE erweitern, so dass durch sie nun eine Tabelle mit vier Spalten erzeugt wird.
  4. Definieren eines Drop-Befehls – Anlegen der neuen Konstante SQL_DROP, mit deren Hilfe die alte Tabelle aus der SQLite Datenbank gelöscht wird.
  5. Implementieren der onUpgrade() Methode – In der onUpgrade() Methode lassen wir die alte Tabelle entfernen und eine neue Tabelle mit vier Spalten erstellen.

In dem unten angegebenen Quellcode sind die oben aufgezählten Änderungen bereits vorgenommen worden. Die in der ShoppingMemoDbHelper-Klasse durchgeführten Änderungen sind gelb markiert.

ShoppingMemoDbHelper.java

package de.programmierenlernenhq.shoppinglisthq;


import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class ShoppingMemoDbHelper extends SQLiteOpenHelper{

    private static final String LOG_TAG = ShoppingMemoDbHelper.class.getSimpleName();

    public static final String DB_NAME = "shopping_list.db";
    public static final int DB_VERSION = 2;

    public static final String TABLE_SHOPPING_LIST = "shopping_list";

    public static final String COLUMN_ID = "_id";
    public static final String COLUMN_PRODUCT = "product";
    public static final String COLUMN_QUANTITY = "quantity";
    public static final String COLUMN_CHECKED = "checked";

    public static final String SQL_CREATE =
            "CREATE TABLE " + TABLE_SHOPPING_LIST +
                    "(" + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                    COLUMN_PRODUCT + " TEXT NOT NULL, " +
                    COLUMN_QUANTITY + " INTEGER NOT NULL, " +
                    COLUMN_CHECKED + " BOOLEAN NOT NULL DEFAULT 0);";

    public static final String SQL_DROP = "DROP TABLE IF EXISTS " + TABLE_SHOPPING_LIST;


    public ShoppingMemoDbHelper(Context context) {
        //super(context, "PLATZHALTER_DATENBANKNAME", null, 1);
        super(context, DB_NAME, null, DB_VERSION);
        Log.d(LOG_TAG, "DbHelper hat die Datenbank: " + getDatabaseName() + " erzeugt.");
    }

    // Die onCreate-Methode wird nur aufgerufen, falls die Datenbank noch nicht existiert
    @Override
    public void onCreate(SQLiteDatabase db) {
        try {
            Log.d(LOG_TAG, "Die Tabelle wird mit SQL-Befehl: " + SQL_CREATE + " angelegt.");
            db.execSQL(SQL_CREATE);
        }
        catch (Exception ex) {
            Log.e(LOG_TAG, "Fehler beim Anlegen der Tabelle: " + ex.getMessage());
        }
    }

    // Die onUpgrade-Methode wird aufgerufen, sobald die neue Versionsnummer höher
    // als die alte Versionsnummer ist und somit ein Upgrade notwendig wird
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.d(LOG_TAG, "Die Tabelle mit Versionsnummer " + oldVersion + " wird entfernt.");
        db.execSQL(SQL_DROP);
        onCreate(db);
    }
}

In Android Studio sollte die Klasse ShoppingMemoDbHelper nun wie folgt aussehen:

android sqlite db helper column

Die überarbeitete Klasse ShoppingMemodBHelper mit Markierungen

In der oberen Abbildung sind die fünf Änderungen mit blauen Linien bzw. Rahmen markiert. Die Nummerierung entspricht der Aufzählung der Änderungen zu Beginn dieses Abschnitts.

Mit den nun vorgenommenen Änderungen an der DbHelper-Klasse haben wir unsere SQLite App für den Datenbank Upgrade vorbereitet. Würden wir nun unsere Anwendung ausführen, dann würde die SQLiteOpenHelper-Klasse anhand der erhöhten Versionsnummer erkennen, dass ein Datenbank Upgrade notwendig ist und die onUpgrade() Methode aufrufen.

Bevor wir jedoch unsere Android App starten können, müssen wir noch Änderungen an den restlichen beiden Klassen unseres Projekts vornehmen. Als Nächstes nehmen wir uns die Klasse ShoppingMemoDataSource vor und im Anschluss daran die MainActivity-Klasse.

4. Die ShoppingMemoDataSource-Klasse anpassen

Unsere Datenquelle müssen wir nun auch an die neue Tabellenstruktur anpassen. Betroffen sind die beiden Methoden updateShoppingMemo() und cursorToShoppingMemo(), sowie die Variable columns.

Wir öffnen die Klassendatei ShoppingMemoDataSource.java im Editorfenster von Android Studio über einen Klick auf den entsprechenden Eintrag in der linken Projektleiste.

Anschließend fügen wir den gelb markierten Quellcode in die ShoppingMemoDataSource-Klasse ein:

ShoppingMemoDataSource.java

package de.programmierenlernenhq.shoppinglisthq;


import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import android.content.ContentValues;
import android.database.Cursor;
import java.util.ArrayList;
import java.util.List;


public class ShoppingMemoDataSource {

    private static final String LOG_TAG = ShoppingMemoDataSource.class.getSimpleName();

    private SQLiteDatabase database;
    private ShoppingMemoDbHelper dbHelper;

    private String[] columns = {
            ShoppingMemoDbHelper.COLUMN_ID,
            ShoppingMemoDbHelper.COLUMN_PRODUCT,
            ShoppingMemoDbHelper.COLUMN_QUANTITY,
            ShoppingMemoDbHelper.COLUMN_CHECKED
    };

    public ShoppingMemoDataSource(Context context) {
        Log.d(LOG_TAG, "Unsere DataSource erzeugt jetzt den dbHelper.");
        dbHelper = new ShoppingMemoDbHelper(context);
    }

    public void open() {
        Log.d(LOG_TAG, "Eine Referenz auf die Datenbank wird jetzt angefragt.");
        database = dbHelper.getWritableDatabase();
        Log.d(LOG_TAG, "Datenbank-Referenz erhalten. Pfad zur Datenbank: " + database.getPath());
    }

    public void close() {
        dbHelper.close();
        Log.d(LOG_TAG, "Datenbank mit Hilfe des DbHelpers geschlossen.");
    }

    public ShoppingMemo createShoppingMemo(String product, int quantity) {
        ContentValues values = new ContentValues();
        values.put(ShoppingMemoDbHelper.COLUMN_PRODUCT, product);
        values.put(ShoppingMemoDbHelper.COLUMN_QUANTITY, quantity);

        long insertId = database.insert(ShoppingMemoDbHelper.TABLE_SHOPPING_LIST, null, values);

        Cursor cursor = database.query(ShoppingMemoDbHelper.TABLE_SHOPPING_LIST,
                columns, ShoppingMemoDbHelper.COLUMN_ID + "=" + insertId,
                null, null, null, null);

        cursor.moveToFirst();
        ShoppingMemo shoppingMemo = cursorToShoppingMemo(cursor);
        cursor.close();

        return shoppingMemo;
    }

    public void deleteShoppingMemo(ShoppingMemo shoppingMemo) {
        long id = shoppingMemo.getId();

        database.delete(ShoppingMemoDbHelper.TABLE_SHOPPING_LIST,
                        ShoppingMemoDbHelper.COLUMN_ID + "=" + id,
                        null);

        Log.d(LOG_TAG, "Eintrag gelöscht! ID: " + id + " Inhalt: " + shoppingMemo.toString());
    }

    public ShoppingMemo updateShoppingMemo(long id, String newProduct, int newQuantity, boolean newChecked) {
        int intValueChecked = (newChecked)? 1 : 0;

        ContentValues values = new ContentValues();
        values.put(ShoppingMemoDbHelper.COLUMN_PRODUCT, newProduct);
        values.put(ShoppingMemoDbHelper.COLUMN_QUANTITY, newQuantity);
        values.put(ShoppingMemoDbHelper.COLUMN_CHECKED, intValueChecked);

        database.update(ShoppingMemoDbHelper.TABLE_SHOPPING_LIST,
                values,
                ShoppingMemoDbHelper.COLUMN_ID + "=" + id,
                null);

        Cursor cursor = database.query(ShoppingMemoDbHelper.TABLE_SHOPPING_LIST,
                columns, ShoppingMemoDbHelper.COLUMN_ID + "=" + id,
                null, null, null, null);

        cursor.moveToFirst();
        ShoppingMemo shoppingMemo = cursorToShoppingMemo(cursor);
        cursor.close();

        return shoppingMemo;
    }

    private ShoppingMemo cursorToShoppingMemo(Cursor cursor) {
        int idIndex = cursor.getColumnIndex(ShoppingMemoDbHelper.COLUMN_ID);
        int idProduct = cursor.getColumnIndex(ShoppingMemoDbHelper.COLUMN_PRODUCT);
        int idQuantity = cursor.getColumnIndex(ShoppingMemoDbHelper.COLUMN_QUANTITY);
        int idChecked = cursor.getColumnIndex(ShoppingMemoDbHelper.COLUMN_CHECKED);

        String product = cursor.getString(idProduct);
        int quantity = cursor.getInt(idQuantity);
        long id = cursor.getLong(idIndex);
        int intValueChecked = cursor.getInt(idChecked);

        boolean isChecked = (intValueChecked != 0);

        ShoppingMemo shoppingMemo = new ShoppingMemo(product, quantity, id, isChecked);

        return shoppingMemo;
    }

    public List<ShoppingMemo> getAllShoppingMemos() {
        List<ShoppingMemo> shoppingMemoList = new ArrayList<>();

        Cursor cursor = database.query(ShoppingMemoDbHelper.TABLE_SHOPPING_LIST,
                columns, null, null, null, null, null);

        cursor.moveToFirst();
        ShoppingMemo shoppingMemo;

        while(!cursor.isAfterLast()) {
            shoppingMemo = cursorToShoppingMemo(cursor);
            shoppingMemoList.add(shoppingMemo);
            Log.d(LOG_TAG, "ID: " + shoppingMemo.getId() + ", Inhalt: " + shoppingMemo.toString());
            cursor.moveToNext();
        }

        cursor.close();

        return shoppingMemoList;
    }
}

Mit der ersten Änderung im oberen Quellcode fügen wir dem String-Array columns ein weiteres Element, die neue Spalte, hinzu. Die neue Spalte fügen wir in Zeile 25 dem Array hinzu.

Anschließend überarbeiten wir die updateShoppingMemo() Methode. Wir erweitern ihre Parameterliste um einen vierten Parameter vom Datentyp boolean. Da die SQLite Datenbank diesen Datentyp nicht kennt, wandeln wir ihn mit der Anweisung in Zeile 73 in einen int-Wert um.

Diesen int-Wert fügen wir in Zeile 78 der ContentValues-Variable hinzu und legen ihn darin mit dem neuen Spaltennamen als Schlüssel ab. Somit werden ab jetzt drei Werte plus die ID in die Tabelle der SQLite Datenbank geschrieben.

Weitere Änderungen nehmen wir nicht an der updateShoppingMemo() Methode vor.

Als letzte Änderung im Quellcode passen wir die Methode cursorToShoppingMemo() an. In Zeile 100 fragen wir den Spaltenindex der neuen Spalte beim Cursor-Objekt an. Anschließend lesen wir in Zeile 105 den Wert der vierten Spalte im übergebenen Datensatz aus.

Da dieser Wert aber vom Datentyp int ist, wandeln wir ihn mit der Anweisung in Zeile 107 in einen boolean-Wert um. Jetzt können wir mit den ausgelesenen Datenbankwerte ein ShoppingMemo-Objekt erzeugen lassen. Wir müssen dazu aber noch Zeile 109 ändern und den Konstruktor der Klasse ShoppingMemo mit vier Argumenten aufrufen.

In Android Studio sollte die Klasse ShoppingMemoDataSource nun folgendermaßen aussehen:

android sqlite shopping memo data source

Die überarbeitete Klasse ShoppingMemoDataSource mit Markierungen

Jetzt haben wir alle notwendigen Änderungen an der Klasse ShoppingMemoDataSource durchgeführt. Unsere Datenquelle beachtet nun die neue Tabellenstruktur.

Als Nächstes nehmen wir umfassende Erweiterungen an unserer MainActivity-Klasse vor. Dies werden auch die letzten Änderungen an den Dateien unseres SQLite Projekts sein.

5. Erweitern der MainActivity-Klasse

Nun kommen wir zu den größten Änderungen bzw. Erweiterungen des Quellcodes. Wir werden in der Klasse MainActivity für unseren ListView einen OnItemClickListener registrieren. Mit dem Listener werden wir auf kurze Klicks auf die Listeneinträge reagieren und diese dann durchstreichen.

Um dies zu realisieren, nehmen wir die folgenden Änderungen am Quellcode der Klasse MainActivity vor:

  1. Einfügen der Import-Anweisungen – Zuerst importieren wir die benötigten Klassen.
  2. Deklarieren des ListViews als Membervariable – Anschließend werden wir die Membervariable mShoppingMemosListView deklarieren, so dass wir in den Methoden darauf zugreifen können.
  3. Überarbeiten der onCreate() Methode – Wir fügen in diese Methode eine Anweisung ein und zwar den Aufruf der initializeShoppingMemosListView() Methode, die wir im nächsten Arbeitsschritt definieren.
  4. Definieren der initializeShoppingMemosListView() Methode – Wir definieren eine neue Methode, die den ListView initialisiert und für ihn einen OnItemClickListener registriert.
  5. Überarbeiten der showAllListEntries() Methode – Bisher haben wir in dieser Methode den ArrayAdapter immer wieder neu erstellt, um neue Daten anzuzeigen. Dies verbessern wir nun, indem wir nicht mehr den ganzen Adapter löschen, sondern nur seinen Inhalt und ihm anschließend die neuen Daten zuweisen.
  6. Kleine Anpassung der createEditShoppingMemoDialog() Methode – Als letzten Arbeitsschritt passen wir den Methodenaufruf updateShoppingMemo() an. Wir übergeben der Methode nun vier anstelle der bisherigen drei Argumente.

Am Ende dieses Abschnitts haben wir nochmals den kompletten Quellcode der MainActivity-Klasse aufgeführt. Falls ihr bei den folgenden Schritten durcheinander kommt, könnt ihr ihn zur Kontrolle bzw. Orientierung nutzen.

Nun beginnen wir mit dem ersten Arbeitsschritt.

5.1 Einfügen der Import-Anweisungen

Wir öffnen als Erstes die Klassendatei MainActivity.java im Editorfenster von Android Studio über einen Klick auf den entsprechenden Eintrag in der linken Projektleiste.

Anschließend fügen wir den folgenden Quellcode unter den bereits vorhandenen Import-Anweisungen ein:

MainActivity.java

import android.graphics.Color;
import android.graphics.Paint;
import android.widget.TextView;
import android.view.ViewGroup;
import android.widget.AdapterView;

5.2 Deklarieren des ListViews als Membervariable

Nun legen wir die Membervariable mShoppingMemosListView im Variablenbereich der MainActivity-Klasse an. In der Variable speichern wir eine Referenz die auf unser ListView-Objekt verweist.

Wir fügen nun folgende Zeile in die Klasse MainActivity ein:

MainActivity.java

private ListView mShoppingMemosListView;

5.3 Überarbeiten der onCreate() Methode

Nun fügen wir in die onCreate() Methode der MainActivity den Methodenaufruf initializeShoppingMemosListView() ein. Die aufzurufende Methode werden wir im nächsten Arbeitsschritt definieren, sie wird das ListView-Objekt initialisieren.

Wir fügen nun die gelb markierte Zeile in die Methode onCreate() der Klasse MainActivity ein:

MainActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Log.d(LOG_TAG, "Das Datenquellen-Objekt wird angelegt.");
    dataSource = new ShoppingMemoDataSource(this);

    initializeShoppingMemosListView();

    activateAddButton();
    initializeContextualActionBar();
}

5.4 Definieren der initializeShoppingMemosListView() Methode

Im vierten Arbeitsschritt definieren wir die Methode initializeShoppingMemosListView(), die für uns den ListView initialisieren wird. Die neue Methode erstellt einen ArrayAdapter, weist diesen dem ListView zu und registriert einen OnItemClickListener für den ListView.

Dazu fügen wir den folgenden Quellcode in den Methodenbereich der MainActivity-Klasse ein.

MainActivity.java

private void initializeShoppingMemosListView() {
  List<ShoppingMemo> emptyListForInitialization = new ArrayList<>();

  mShoppingMemosListView = (ListView) findViewById(R.id.listview_shopping_memos);

  // Erstellen des ArrayAdapters für unseren ListView
  ArrayAdapter<ShoppingMemo> shoppingMemoArrayAdapter = new ArrayAdapter<ShoppingMemo> (
          this,
          android.R.layout.simple_list_item_multiple_choice,
          emptyListForInitialization) {

    // Wird immer dann aufgerufen, wenn der übergeordnete ListView die Zeile neu zeichnen muss
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

      View view =  super.getView(position, convertView, parent);
      TextView textView = (TextView) view;

      ShoppingMemo memo = (ShoppingMemo) mShoppingMemosListView.getItemAtPosition(position);

      // Hier prüfen, ob Eintrag abgehakt ist. Falls ja, Text durchstreichen
      if (memo.isChecked()) {
        textView.setPaintFlags(textView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
        textView.setTextColor(Color.rgb(175,175,175));
      }
      else {
        textView.setPaintFlags( textView.getPaintFlags() & (~ Paint.STRIKE_THRU_TEXT_FLAG));
        textView.setTextColor(Color.DKGRAY);
      }

      return view;
    }
  };

  mShoppingMemosListView.setAdapter(shoppingMemoArrayAdapter);

  mShoppingMemosListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
      ShoppingMemo memo = (ShoppingMemo) adapterView.getItemAtPosition(position);

      // Hier den checked-Wert des Memo-Objekts umkehren, bspw. von true auf false
      // Dann ListView neu zeichnen mit showAllListEntries()
      ShoppingMemo updatedShoppingMemo = dataSource.updateShoppingMemo(memo.getId(), memo.getProduct(), memo.getQuantity(), (!memo.isChecked()));
      Log.d(LOG_TAG, "Checked-Status von Eintrag: " + updatedShoppingMemo.toString() + " ist: " + updatedShoppingMemo.isChecked());
      showAllListEntries();
    }
  });

}

Mit dem oberen Quellcode realisieren wir drei Dinge:

  1. Erstellen eines ArrayAdapters – Mit den Zeilen 7 bis 33 legen wir den ArrayAdapter an und überschreiben seine getView() Methode. Die getView() Methode wird immer dann aufgerufen, wenn ein Listeneintrag gezeichnet werden muss. In dieser Methode verändern wir die Eigenschaften des TextView-Elements, mit welchem wir die jeweiligen SQLite Datenbankeinträge anzeigen. Wir prüfen, ob der entsprechende Datenbankeintrag abgehakt ist, also ob das Attribut checked den Wert true besitzt. Ist dies der Fall, streichen wir den Text durch und ändern die Schriftfarbe in ein helles Grau. Ist der Wert false, dann geben wir den Text normal aus.
  2. ArrayAdapter an den ListView binden – Anschließend binden wir den erzeugten ArrayAdapter mit der Anweisung in Zeile 35 an unseren ListView.
  3. Registrieren eines OnItemClickListener für den ListView – Damit wir auf kurze Klicks auf Listeneinträge reagieren können, registrieren wir mit den Zeilen 37 bis 48 einen OnItemClickListener für unseren ListView. In dem Listeners kehren wir den Status des angeklickten Listeneintrags um. Wir fragen dazu das angeklickte ShoppingMemo-Objekt an und setzen anschließend den checked-Wert neu. Den alten Wert kehren wir dafür um. Anschließend lassen wir den ListView neu zeichnen.

5.5 Überarbeiten der showAllListEntries() Methode

Als nächste Änderung werden wir die Methode showAllListEntries() überarbeiten. In ihr fragen wir alle Einträge aus der SQLite Datenbank an und übergeben die ausgelesenen Daten an den ArrayAdapter. Anschließend weisen wir über den ArrayAdapter unseren ListView an, sich neu zu zeichnen.

Wir löschen dazu unsere bisherige showAllListEntries() Methode im Methodenbereich der MainActivity-Klasse und ersetzen sie mit folgendem Quellcode:

MainActivity.java

private void showAllListEntries () {
    List<ShoppingMemo> shoppingMemoList = dataSource.getAllShoppingMemos();

    ArrayAdapter<ShoppingMemo> adapter = (ArrayAdapter<ShoppingMemo>) mShoppingMemosListView.getAdapter();

    adapter.clear();
    adapter.addAll(shoppingMemoList);
    adapter.notifyDataSetChanged();
}

5.6 Kleine Anpassung der createEditShoppingMemoDialog() Methode

Als letzte Änderung nehmen wir eine Anpassung am Aufruf der updateShoppingMemo() Methode in der Methode createEditShoppingMemoDialog() vor. Dies müssen wir tun, da die Methode updateShoppingMemo() jetzt 4 Argumente erwartet, anstelle der bisherigen drei.

Wir passen nun die gelb markierte Zeile in der Methode createEditShoppingMemoDialog() der Klasse MainActivity an:

MainActivity.java

private AlertDialog createEditShoppingMemoDialog(final ShoppingMemo shoppingMemo) {

  AlertDialog.Builder builder = new AlertDialog.Builder(this);
  LayoutInflater inflater = getLayoutInflater();

  View dialogsView = inflater.inflate(R.layout.dialog_edit_shopping_memo, null);

  final EditText editTextNewQuantity = (EditText) dialogsView.findViewById(R.id.editText_new_quantity);
  editTextNewQuantity.setText(String.valueOf(shoppingMemo.getQuantity()));

  final EditText editTextNewProduct = (EditText) dialogsView.findViewById(R.id.editText_new_product);
  editTextNewProduct.setText(shoppingMemo.getProduct());

  builder.setView(dialogsView)
      .setTitle(R.string.dialog_title)
      .setPositiveButton(R.string.dialog_button_positive, new DialogInterface.OnClickListener() {
          @Override
          public void onClick(DialogInterface dialog, int id) {
            String quantityString = editTextNewQuantity.getText().toString();
            String product = editTextNewProduct.getText().toString();

            if ((TextUtils.isEmpty(quantityString)) || (TextUtils.isEmpty(product))) {
              Log.d(LOG_TAG, "Ein Eintrag enthielt keinen Text. Daher Abbruch der Änderung.");
              return;
            }

            int quantity = Integer.parseInt(quantityString);

            // An dieser Stelle schreiben wir die geänderten Daten in die SQLite Datenbank
            ShoppingMemo updatedShoppingMemo = dataSource.updateShoppingMemo(shoppingMemo.getId(), product, quantity, shoppingMemo.isChecked());

            Log.d(LOG_TAG, "Alter Eintrag - ID: " + shoppingMemo.getId() + " Inhalt: " + shoppingMemo.toString());
            Log.d(LOG_TAG, "Neuer Eintrag - ID: " + updatedShoppingMemo.getId() + " Inhalt: " + updatedShoppingMemo.toString());

            showAllListEntries();
            dialog.dismiss();
          }
      })
      .setNegativeButton(R.string.dialog_button_negative, new DialogInterface.OnClickListener() {
          public void onClick(DialogInterface dialog, int id) {
            dialog.cancel();
          }
      });

  return builder.create();
}

5.7 Der komplette Quellcode der MainActivity-Klasse

Nun haben wir alle Änderungen an der MainActivity-Klasse vorgenommen.

Mit dem eingefügtem Quellcode reagieren wir auf kurze Klicks des Benutzers und ändern das Aussehen des angeklickten Listeneintrags.

In dem unten angegebenen Quellcode ist die gesamte MainActivity-Klasse zur Kontrolle für euch aufgeführt. Die neu eingefügten Zeilen sind gelb markiert.

Der vollständiger Quelltext der Klasse MainActivity:

MainActivity.java

package de.programmierenlernenhq.shoppinglisthq;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;

import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import android.util.SparseBooleanArray;
import android.view.ActionMode;
import android.widget.AbsListView;

import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;

import android.graphics.Color;
import android.graphics.Paint;
import android.widget.TextView;
import android.view.ViewGroup;
import android.widget.AdapterView;


public class MainActivity extends AppCompatActivity {

    public static final String LOG_TAG = MainActivity.class.getSimpleName();

    private ShoppingMemoDataSource dataSource;

    private ListView mShoppingMemosListView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(LOG_TAG, "Das Datenquellen-Objekt wird angelegt.");
        dataSource = new ShoppingMemoDataSource(this);

        initializeShoppingMemosListView();

        activateAddButton();
        initializeContextualActionBar();
    }

    private void initializeShoppingMemosListView() {
        List<ShoppingMemo> emptyListForInitialization = new ArrayList<>();

        mShoppingMemosListView = (ListView) findViewById(R.id.listview_shopping_memos);

        // Erstellen des ArrayAdapters für unseren ListView
        ArrayAdapter<ShoppingMemo> shoppingMemoArrayAdapter = new ArrayAdapter<ShoppingMemo> (
                this,
                android.R.layout.simple_list_item_multiple_choice,
                emptyListForInitialization) {

            // Wird immer dann aufgerufen, wenn der übergeordnete ListView die Zeile neu zeichnen muss
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {

                View view =  super.getView(position, convertView, parent);
                TextView textView = (TextView) view;

                ShoppingMemo memo = (ShoppingMemo) mShoppingMemosListView.getItemAtPosition(position);

                // Hier prüfen, ob Eintrag abgehakt ist. Falls ja, Text durchstreichen
                if (memo.isChecked()) {
                    textView.setPaintFlags(textView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
                    textView.setTextColor(Color.rgb(175,175,175));
                }
                else {
                    textView.setPaintFlags( textView.getPaintFlags() & (~ Paint.STRIKE_THRU_TEXT_FLAG));
                    textView.setTextColor(Color.DKGRAY);
                }

                return view;
            }
        };

        mShoppingMemosListView.setAdapter(shoppingMemoArrayAdapter);

        mShoppingMemosListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
                ShoppingMemo memo = (ShoppingMemo) adapterView.getItemAtPosition(position);

                // Hier den checked-Wert des Memo-Objekts umkehren, bspw. von true auf false
                // Dann ListView neu zeichnen mit showAllListEntries()
                ShoppingMemo updatedShoppingMemo = dataSource.updateShoppingMemo(memo.getId(), memo.getProduct(), memo.getQuantity(), (!memo.isChecked()));
                Log.d(LOG_TAG, "Checked-Status von Eintrag: " + updatedShoppingMemo.toString() + " ist: " + updatedShoppingMemo.isChecked());
                showAllListEntries();
            }
        });

    }

    private void showAllListEntries () {
        List<ShoppingMemo> shoppingMemoList = dataSource.getAllShoppingMemos();

        ArrayAdapter<ShoppingMemo> adapter = (ArrayAdapter<ShoppingMemo>) mShoppingMemosListView.getAdapter();

        adapter.clear();
        adapter.addAll(shoppingMemoList);
        adapter.notifyDataSetChanged();
    }

    @Override
    protected void onResume() {
        super.onResume();

        Log.d(LOG_TAG, "Die Datenquelle wird geöffnet.");
        dataSource.open();

        Log.d(LOG_TAG, "Folgende Einträge sind in der Datenbank vorhanden:");
        showAllListEntries();
    }

    @Override
    protected void onPause() {
        super.onPause();

        Log.d(LOG_TAG, "Die Datenquelle wird geschlossen.");
        dataSource.close();
    }

    private void activateAddButton() {
        Button buttonAddProduct = (Button) findViewById(R.id.button_add_product);
        final EditText editTextQuantity = (EditText) findViewById(R.id.editText_quantity);
        final EditText editTextProduct = (EditText) findViewById(R.id.editText_product);

        buttonAddProduct.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String quantityString = editTextQuantity.getText().toString();
                String product = editTextProduct.getText().toString();

                if (TextUtils.isEmpty(quantityString)) {
                    editTextQuantity.setError(getString(R.string.editText_errorMessage));
                    return;
                }
                if (TextUtils.isEmpty(product)) {
                    editTextProduct.setError(getString(R.string.editText_errorMessage));
                    return;
                }

                int quantity = Integer.parseInt(quantityString);
                editTextQuantity.setText("");
                editTextProduct.setText("");

                dataSource.createShoppingMemo(product, quantity);

                InputMethodManager inputMethodManager;
                inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
                if (getCurrentFocus() != null) {
                    inputMethodManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
                }

                showAllListEntries();
            }
        });

    }

    private void initializeContextualActionBar() {

        final ListView shoppingMemosListView = (ListView) findViewById(R.id.listview_shopping_memos);
        shoppingMemosListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);

        shoppingMemosListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {

            int selCount = 0;

            // In dieser Callback-Methode zählen wir die ausgewählen Listeneinträge mit
            // und fordern ein Aktualisieren der Contextual Action Bar mit invalidate() an
            @Override
            public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
                if (checked) {
                    selCount++;
                } else {
                    selCount--;
                }
                String cabTitle = selCount + " " + getString(R.string.cab_checked_string);
                mode.setTitle(cabTitle);
                mode.invalidate();
            }

            // In dieser Callback-Methode legen wir die CAB-Menüeinträge an
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                getMenuInflater().inflate(R.menu.menu_contextual_action_bar, menu);
                return true;
            }

            // In dieser Callback-Methode reagieren wir auf den invalidate() Aufruf
            // Wir lassen das Edit-Symbol verschwinden, wenn mehr als 1 Eintrag ausgewählt ist
            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                MenuItem item = menu.findItem(R.id.cab_change);
                if (selCount == 1) {
                    item.setVisible(true);
                } else {
                    item.setVisible(false);
                }

                return true;
            }

            // In dieser Callback-Methode reagieren wir auf Action Item-Klicks
            // Je nachdem ob das Löschen- oder Ändern-Symbol angeklickt wurde
            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                boolean returnValue = true;
                SparseBooleanArray touchedShoppingMemosPositions = shoppingMemosListView.getCheckedItemPositions();

                switch (item.getItemId()) {
                    case R.id.cab_delete:
                        for (int i = 0; i < touchedShoppingMemosPositions.size(); i++) {
                            boolean isChecked = touchedShoppingMemosPositions.valueAt(i);
                            if (isChecked) {
                                int postitionInListView = touchedShoppingMemosPositions.keyAt(i);
                                ShoppingMemo shoppingMemo = (ShoppingMemo) shoppingMemosListView.getItemAtPosition(postitionInListView);
                                Log.d(LOG_TAG, "Position im ListView: " + postitionInListView + " Inhalt: " + shoppingMemo.toString());
                                dataSource.deleteShoppingMemo(shoppingMemo);
                            }
                        }
                        showAllListEntries();
                        mode.finish();
                        break;

                    case R.id.cab_change:
                        Log.d(LOG_TAG, "Eintrag ändern");
                        for (int i = 0; i < touchedShoppingMemosPositions.size(); i++) {
                            boolean isChecked = touchedShoppingMemosPositions.valueAt(i);
                            if (isChecked) {
                                int postitionInListView = touchedShoppingMemosPositions.keyAt(i);
                                ShoppingMemo shoppingMemo = (ShoppingMemo) shoppingMemosListView.getItemAtPosition(postitionInListView);
                                Log.d(LOG_TAG, "Position im ListView: " + postitionInListView + " Inhalt: " + shoppingMemo.toString());

                                AlertDialog editShoppingMemoDialog = createEditShoppingMemoDialog(shoppingMemo);
                                editShoppingMemoDialog.show();
                            }
                        }

                        mode.finish();
                        break;

                    default:
                        returnValue = false;
                        break;
                }
                return returnValue;
            }

            // In dieser Callback-Methode reagieren wir auf das Schließen der CAB
            // Wir setzen den Zähler auf 0 zurück
            @Override
            public void onDestroyActionMode(ActionMode mode) {
                selCount = 0;
            }
        });
    }

    private AlertDialog createEditShoppingMemoDialog(final ShoppingMemo shoppingMemo) {

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        LayoutInflater inflater = getLayoutInflater();

        View dialogsView = inflater.inflate(R.layout.dialog_edit_shopping_memo, null);

        final EditText editTextNewQuantity = (EditText) dialogsView.findViewById(R.id.editText_new_quantity);
        editTextNewQuantity.setText(String.valueOf(shoppingMemo.getQuantity()));

        final EditText editTextNewProduct = (EditText) dialogsView.findViewById(R.id.editText_new_product);
        editTextNewProduct.setText(shoppingMemo.getProduct());

        builder.setView(dialogsView)
                .setTitle(R.string.dialog_title)
                .setPositiveButton(R.string.dialog_button_positive, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int id) {
                        String quantityString = editTextNewQuantity.getText().toString();
                        String product = editTextNewProduct.getText().toString();

                        if ((TextUtils.isEmpty(quantityString)) || (TextUtils.isEmpty(product))) {
                            Log.d(LOG_TAG, "Ein Eintrag enthielt keinen Text. Daher Abbruch der Änderung.");
                            return;
                        }

                        int quantity = Integer.parseInt(quantityString);

                        // An dieser Stelle schreiben wir die geänderten Daten in die SQLite Datenbank
                        ShoppingMemo updatedShoppingMemo = dataSource.updateShoppingMemo(shoppingMemo.getId(), product, quantity, shoppingMemo.isChecked());

                        Log.d(LOG_TAG, "Alter Eintrag - ID: " + shoppingMemo.getId() + " Inhalt: " + shoppingMemo.toString());
                        Log.d(LOG_TAG, "Neuer Eintrag - ID: " + updatedShoppingMemo.getId() + " Inhalt: " + updatedShoppingMemo.toString());

                        showAllListEntries();
                        dialog.dismiss();
                    }
                })
                .setNegativeButton(R.string.dialog_button_negative, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.cancel();
                    }
                });

        return builder.create();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Mit den beschriebenen Änderungen sollte die Klasse MainActivity nun wie folgt in Android Studio aussehen:

android sqlite mainactivity column

Die überarbeitete Klasse MainActivity mit Markierungen

In der oberen Abbildung sind die in den sechs Arbeitsschritten vorgenommenen Änderungen mit einem blauen Rahmen markiert. Neben den Rahmen ist jeweils ein nummeriertes Kästchen angeordnet. Die Nummerierung entspricht der Nummerierung der oben aufgezählten Arbeitsschritte.

Im nächsten Abschnitt werden wird unsere SQLite App testen und Einträge der Einkaufsliste und somit auch der hinterlegten SQLite Datenbank durchstreichen.

6. Starten und Testen unserer SQLite App

In diesem Abschnitt werden wir unsere Android App ausführen und überprüfen, ob der SQLite Datenbank Upgrade korrekt ausgeführt wird.

Dazu werden wir zunächst die Anwendung auf einem Android Gerät ausführen und anschließend die zurückgelieferten Log-Meldungen in Android Studio analysieren. Zusätzlich prüfen wir auch, ob die Einträge korrekt in dem ListView der MainActivity abgehakt werden können.

6.1 Ausführen der Android SQLite-App

Nun wollen wir unsere App ausführen und testen. Dazu installieren wir die Anwendung auf unser angeschlossenes Android Gerät (Smartphone oder Tablet) oder in einer AVD des Android Emulators.

Wie eine Android App installiert wird, könnt ihr in den folgenden beiden Teilen unseres großen Android App Programmieren Tutorials nachlesen:

Hier noch einmal in Kürze die wichtigsten Arbeitsschritte.

Zuerst schließen wir unser Android Gerät an den PC an und stellen eine Verbindung über die ADB (Android Debug Bridge) her. Danach klicken wir auf das Run 'app'-Symbol. Unser Android Projekt wird dadurch neu erstellt und auf dem angeschlossenen Gerät ausgeführt.

Das Run 'app'-Symbol befindet sich in der oberen Menüleiste, siehe folgende Abbildung:

android sqlite app starten

Android App über das Run App-Symbol starten

Nach einigen Momenten öffnet sich der Choose Device-Dialog. In ihm nehmen wir die folgenden Einstellungen vor:

  1. Radio Button Choose a running device aktivieren
  2. Das angeschlossene Android Gerät auswählen
  3. Mit einem Klick auf den OK-Button die Installation unserer App auf das Gerät starten
android choose device

Auswählen des angeschlossenen Android Geräts zum Aufspielen unserer App

Der Dialog schließt sich und unsere Android App wird auf das angeschlossene Gerät übertragen und installiert. Die Installation dauert nur einen kurzen Augenblick und verläuft fast unbemerkt im Hintergrund. Danach wird unsere App automatisch gestartet.

6.2 Überprüfen der Log-Meldungen in Android Studio

Unsere SQLite-App sollte jetzt auf dem Android Gerät gestartet worden sein.

Um die Log-Meldungen zu überprüfen, öffnen wir in Android Studio die Android-Ansicht bei angeschlossenem Android Gerät.

In der unteren Abbildung sind die Log-Meldungen direkt nach dem Starten unserer Android App dargestellt. Der in Lektion 2 angelegte Log-Filter ist aktiviert (rote Markierung X). Unsere Meldungen werden im logcat-Textbereich angezeigt.

android sqlite log new table

Die Log-Meldungen in Android Studio mit aktivem Log-Filter – Das SQLite Datenbank Upgrade wurde durchgeführt

Uns werden die folgenden Log-Meldungen ausgegeben:

01: MainActivity﹕ Das Datenquellen-Objekt wird angelegt.
02: ShoppingMemoDataSource﹕ Unsere DataSource erzeugt jetzt den dbHelper.
03: ShoppingMemoDbHelper﹕ DbHelper hat die Datenbank: shopping_list.db erzeugt.
04: MainActivity﹕ Die Datenquelle wird geöffnet.
05: ShoppingMemoDataSource﹕ Eine Referenz auf die Datenbank wird jetzt angefragt.
06: ShoppingMemoDbHelper﹕ Die Tabelle mit Versionsnummer 1 wird entfernt.
07: ShoppingMemoDbHelper﹕ Die Tabelle wird mit SQL-Befehl: CREATE TABLE shopping_list(_id INTEGER PRIMARY KEY AUTOINCREMENT, product TEXT NOT NULL, quantity INTEGER NOT NULL, checked BOOLEAN NOT NULL DEFAULT 0); angelegt.
08: ShoppingMemoDataSource﹕ Datenbank-Referenz erhalten. Pfad zur Datenbank: /data/data/de.programmierenlernenhq.shoppinglisthq/databases/shopping_list.db
09: MainActivity﹕ Folgende Einträge sind in der Datenbank vorhanden:

Die meisten Log-Meldungen sind bereits bekannt. Neu sind die beiden Meldungen 06 und 07, die über den durchgeführten SQLite Datenbank Upgrade informieren.

Die alte Tabelle wir aus der SQLite Datenbank entfernt und eine neue Tabelle mit dem oben aufgeführten SQL-Befehl erstellt. Wir können auch erkennen, dass die vierte Spalte vom Datentyp boolean sein soll und den Standardwert 0 für jeden neuen Eintrag besitzt.

Der Standardwert ist nicht false, da die SQLite Datenbank keinen echten boolean Datentyp beherrscht, sondern dafür die Integer-Zahlen 0 und 1 verwendet.

In der unteren Abbildung ist das Vorgehen für das Durchstreichen eines ListView-Eintrags in unserer App dargestellt:

android sqlite column

Einträge aus der SQLite Datenbank im ListView durchstreichen (abhaken)

Ein Video veranschaulicht das Abhaken von Listeneinträgen aber viel besser als ein Bild. Daher haben wir eine kleine Funktionspräsentation unserer SQLite App in Form eines Videos erstellt.

6.3 Video – Funktionspräsentation unserer SQLite App

In dem unteren Video haben wir einen Eintrag aus dem ListView abgehakt und dadurch durchgestrichen. In der SQLite Datenbank wird dabei der Wert in der vierten Spalte entsprechend geändert. Im Video ist zu sehen, wie das Abhaken der Listeneinträge funktioniert.

Mit dieser weiteren Funktion ist die Einkaufslisten-App nun abgeschlossen und kann im Alltag verwendet werden. Es können natürlich noch zusätzliche Funktionen eingebaut werden, wie bspw. das Anlegen von verschiedenen Einkaufslisten.

Zusammenfassung

In diesem letzten Teil unseres Android SQLite Tutorials haben wir noch einmal Veränderungen an allen Klassen unseres Projekts vorgenommen. Dies war notwendig, da wir die Datenstruktur erweitert haben.

Wir haben folgende Änderungen durchgeführt:

  1. Erweitern der ShoppingMemo-Klasse um ein zusätzliches Attribut.
  2. Vorbereiten der ShoppingMemoDbHelper-Klasse für den SQLite Datenbank Upgrade.
  3. Anpassen der ShoppingMemoDataSource-Klasse an die neue Datenstruktur der SQLite Datenbank und des Datenmodells.
  4. Erweitern der MainActivity-Klasse um einen OnItemClickListener, mit dessen Hilfe wir Einträge des ListViews abhaken (durchstreichen) können.

Nun sind wir am Ende des SQLite Tutorials angelangt. Wir hoffen es hat bei euch alles so wie beschrieben funktioniert und ihr hattet Spaß bei der Entwicklung einer Android SQLite App.

Vielen Dank für euer Durchhaltevermögen und viel Erfolg bei euren zukünftigen Android Projekten.



Comments 56

  1. Hallo,
    ich habe mit dem Code

    private ShoppingMemo cursorToShoppingMemo(Cursor cursor) {
            int idIndex = cursor.getColumnIndex(ShoppingMemoDbHelper.COLUMN_ID);
            int idProduct = cursor.getColumnIndex(ShoppingMemoDbHelper.COLUMN_PRODUCT);
            int idQuantity = cursor.getColumnIndex(ShoppingMemoDbHelper.COLUMN_QUANTITY);
            int idChecked = cursor.getColumnIndex(ShoppingMemoDbHelper.COLUMN_CHECKED);
    
            String product = cursor.getString(idProduct);
            int quantity = cursor.getInt(idQuantity);
            long id = cursor.getLong(idIndex);
            int intValueChecked = cursor.getInt(idChecked);
    
            Boolean isChecked = (intValueChecked != 0);
    
            ShoppingMemo shoppingMemo = new ShoppingMemo(product, quantity, id, isChecked);
    

    Folgenden Fehler:

    Error:(110, 37) error: constructor ShoppingMemo in class ShoppingMemo cannot be applied to given types;
    required: String,int,long
    found: String,int,long,Boolean
    reason: actual and formal argument lists differ in length

    Irgendeine Idee ?
    Das Tut ist nebenbei echt sehr gut erzählt, vielen Dank für die Mühe und Arbeit!

    MfG Kevin

    1. Post
      Author

      Hallo Kevin,

      Deine Fehlermeldung weist auf ein Problem mit dem Konstruktor der ShoppingMemo-Klasse hin. In Deinem Quellcode ist ein entsprechender Konstruktor mit 3 Parametern vorhanden, es wird aber ein Konstruktor mit 4 Parametern benötigt.

      In Schritt 2 von Teil 8 haben wir den Konstruktor der Klasse ShoppingMemo.java um einen vierten Parameter erweitert, von:

      public ShoppingMemo(String product, int quantity, long id) { ... }
      

      hin zu:

      public ShoppingMemo(String product, int quantity, long id, boolean checked) { ... }
      

      Vielleicht hast Du diesen Schritt übersprungen oder es hat sich ein kleiner Tippfehler in Deinem Quellcode eingeschlichen.

      Außerdem sollte die Variable isChecked vom primitiven Typ boolean sein. In Deinem Quellcode ist sie vom Objekt-Typ Boolean (java.lang.Boolean).

      Viele Grüße, Chris

    2. Hallo Chris,

      Vielen Dank für die schnelle Antwort, ein Semikolon hatte gefehlt, beim erneuten hinschauen viel es mir dann auf, vielen dank.

      Ist es möglich eine Datenbank online zu speichern und abzurufen ?

      Vielen Dank für die Hilfe
      MfG Kevin

    3. Post
      Author

      Hallo Kevin,

      schön wenn es jetzt funktioniert.

      Ja es ist möglich auf eine online Datenbank zuzugreifen. Man benötigt dafür einen Server auf dem die Datenbank ausgeführt wird und eine Schnittstelle über die man vom Android Gerät auf die Datenbank zugreifen kann. Als Anfrage-Ergebnisse kann man sich XML oder JSON Daten liefern lassen und diese in der App dann weiterverarbeiten.

      Viele Grüße, Chris

  2. Hallo Chris, was du auf deiner Seite anbietest ist aussergewöhnlich. Für einen Android- Anfänger wie mich (73) mit einiger Erfahrung in PASCAL ist das Projekt ziemlich anspruchsvoll, aber ich habe mich durchgekämpft.Was ich nicht geschafft habe: ich will die erzeugte SQLITE-Datei von außen mit einem DB-Drowser manipulieren. Leider gelingt es mir nicht , diese Datei im Projekt zu finden, ich finde schon den Pfad nicht. Wenn du mir da helfen könntest, fiele mir die beabsichtigte Spende noch leichter !
    Nochmals danke ! Norbert

    1. Post
      Author

      Hallo Norbert,

      danke für Deine lobenden Worte!

      Es gibt mehrere Wege, um an die Datenbank einer Android App zu gelangen.

      Wenn man die App auf dem Emulator in einem AVD ausführt, kann man mit Hilfe des Android Device Monitors zur Datenbankdatei navigieren und die Datei dann mit „Pull a file from the device“ vom AVD herunterladen und auf der Festplatte speichern.

      Um dies zu tun, führt man zuerst die App auf dem AVD aus und startet anschließend den Android Device Manager (ADM) in Android Studio über Tools>Android>Android Device Manager. Im ADM wählt man als Device den Emulator aus und klickt danach auf den Reiter File Explorer. Dort kann man frei in dem Dateisystem des Android Virtual Devices navigieren. Die Datenbank liegt in dem Ordner data/data/de.programmierenlernenhq.shoppinglisthq/databases und trägt den Namen shopping_list.db.

      Die Datei wird nun ausgewählt und kann dann mit Hilfe des Buttons „Pull a file from the device“ auf die Festplatte kopiert werden. Der Button ist rechts oben im ADM-Fenster angebracht und sieht aus wie eine Diskette mit einem lila Pfeil (der nach links zeigt).

      Die überarbeitete Datenbankdatei kann auf diesem Wege auch wieder aus das AVD kopiert werden. Dazu muss der Button „Push a file onto the device“ rechts daneben verwendet werden.

      Möchte man die Datenbankdatei von einem physischen Android Gerät laden, geht der eben beschriebene Weg leider nicht, da man keine Leserechte auf die „data“-Verzeichnisse besitzt. Um dennoch auf die Datei zugreifen zu können, kann man sich die Datei auf eine SD Karte kopieren lassen. Bei StackOverflow.com gibt es dazu einige erfolgversprechende Anleitungen zu finden, bspw. hier: location of sqlite database on the device

      Man kann scheinbar die Datenbank auch direkt auf der SD-Karte erzeugen lassen. Ich habe dies aber noch nicht selbst ausprobiert. Der Quellcode könnte wie folgt aussehen:

      public ShoppingMemoDbHelper(Context context) {
              super(context, "/mnt/sdcard/database_name.db", null, DB_VERSION);
              Log.d(LOG_TAG, "DbHelper hat die Datenbank: " + getDatabaseName() + " erzeugt.");
      }
      

      Ich hoffe meine Ausführungen helfen Dir etwas weiter und wünsche noch viel Freude bei der Android Programmierung 🙂

      Viele Grüße, Chris

    1. Post
      Author
  3. Hallo lieber Chris,

    Dankeschön für so einen tollen Beitrag, das hat mir wirklich mit Begeisterung geholfen. Bleibe gesund!

    Gruß aus Berlin

    Amir

    1. Post
      Author

      Hallo Amir,

      Danke für Dein Lob! Es freut mich sehr, wenn der Beitrag hilfreich und zugleich interessant war.

      Viele Grüße, Chris

  4. Hi, sehr gutes Tutorial!
    Hat mir sehr weiter geholfen 🙂

    Ich möchte nun eine weitere Tabelle anlegen, die die gleiche Struktur wie die erste besitzt. Wie kann ich das am einfachsten machen, ohne DataSource, Helper und Entry neu anlegen zu müssen?

    Viele Grüße und weiter so!

    1. Post
      Author

      Hallo Rob,

      in der Helper-Klasse einfach eine neue Tabelle definieren (Tabellenname als Konstante, SQL-Create Anweisung, in onCreate() die Tabelle erstellen lassen) und nicht vergessen die DB-Version um 1 zu erhöhen.

      In der DataSource-Klasse dann die benötigten zusätzlichen Methoden, die mit der neu angelegten Tabelle (mittels Tabellennamen) arbeiten, implementieren und von der Activity wie gehabt auf die DataSource-Methoden zugreifen.

      Viele Grüße, Chris

    2. @Rob:
      Wenn du eine weitere Tabelle mit der gleichen Struktur anlegen willst, ist es vermutlich sehr viel zweckmäßiger, dass du die bereits existierende Tabelle um eine Spalte erweiterst, die eine Art Kategorie abbildet. Solche Kategorien könnten bspw. sein: Supermarkt, Lebensmittel, Baumarkt, Internetbestellung, Kleidung, …
      Auch Anlässe wären als Kategorien geeignet.

      @Chris:
      Sehr gut zusammengestelltes Tutorial. Ich habe es sehr ambitioniert und gerne durchgearbeitet. Einzig anfangs aufzuführende Lernvoraussetzungen seitens der Lernenden fehlen. Für Anfänger ist es nicht geeignet, weil recht viele Inhalte aus anderen Bereichen zusammenkommen. Insbesondere die SQL-Anweisungen dürften bei Nicht-Datenbank-Vorkenntnissen schwer zu verstehen sein.
      Noch ein Hinweis: Die Grafik in der anfänglichen Übersicht zu Lektion 2 (MainActivity … DataObjekt) finde ich sehr aufschlussreich. Ähnliche Grafiken in späteren Lektionen, die die Zusammenhänge der Erweiterungen zeigen, könnten die Schritte vermutlich noch besser verdeutlichen.

  5. Hallo Chris,

    ich bin begeistert. Mir geht es ähnlich, wie deinem Fan Norbert Schomburg. Ich habe die 66 auch schon überschritten und mich an die Android-Programmierung gewagt. Dank deines ausgezeichneten Tutorials habe ich die App sogar auf meinem SAMSUNG Galaxy S5 zum Laufen gebracht. Jetzt würde ich nur noch wissen wollen, wie man die Checkbox mit einem Häkchen füllen bzw. wieder leeren kann.

    Hast du da einen Tipp?

    1. Post
      Author

      Hallo Wilfried,

      danke für Dein Lob!

      Im SQLite-Tutorial nehmen wir für die Checkboxen ein Standardlayout (simple_list_item_multiple_choice). Damit ist es schwierig zu erklären, wie eine Checkbox in Android funktioniert.

      Ich habe mich mal im Web umgesehen und ein sehr anschauliches Tutorial gefunden, das Checkboxen im Detail behandelt. Auch der Beitrag auf der Android Developer Seite ist zu empfehlen.

      https://www.mkyong.com/android/android-checkbox-example/
      https://developer.android.com/reference/android/widget/CheckBox.html

      Ich hoffe Dir helfen die Links weiter.

      Viele Grüße, Chris

  6. Hallo zusammen,
    ich habe ein neues Problem: Die App läuft, wenn ich die Methode „public void activateAddButton“ und den entsprechenden Methodenaufruf auskommentiere. Aber mit der Methode wird mir an genau diesen Stellen ein Fehler gemeldet mit einer NullPointerException: java.lang.NullPointerException: Attempt to invoke virtual method ‚void android.widget.Button.setOnClickListener(android.view.View$OnClickListener)‘ on a null object reference
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3319)

    Ich weiß nicht, wo ich einen Fehler gemacht habe.

    Ich hoffe es kann mir jemand schnell helfen.

    Vielen Dank, Katja

    1. Post
      Author

      Hallo Katja,

      wie es scheint, wird versucht auf einem leeren Objekt eine Methode aufzurufen. Die Ursache des Fehlers findet man am besten mit dem Debugger. Wenn du möchtest, kannst Du mir deine Projektdateien (den ganzen Android Studio Projektordner, aber OHNE die build-Unterordner) als ZIP per E-Mail zusenden. Die E-Mail Adresse kannst Du im Impressum finden. Ich schaue dann mal, ob ich etwas herausfinden kann.

      Viele Grüße, Chris

  7. Super Tutorial, wirklich eins der besten was es im web gibt.
    Sehr ausführlich, übersichtlich markiert und immer wieder der ganze Quellcode damit nichts schief gehen kann. Hat mir sehr geholfen, Danke!

    1. Post
      Author

      Hallo André,

      danke für’s Lob! Ja das Tutorial sollte so verständlich wie möglich sein, da beim Programmieren schon winzige Kleinigkeiten zu großen Problemen führen können. Diese frustrierenden Momente sollte unseren Lesern erspart bleiben.

      Viele Grüße, Chris

  8. Hallo,

    das Tutorial ist sehr gut. Das Programm funktioniert sowohl auf einem Emulator als auch auf einem angeschlossenen Samsung Galaxy S4 Mini. Leider werden im Android Device Monitor keine Dateien und Unterverzeichnisse angezeigt. Ich sehe auf der linken Seite nur die oberste Verzeichnisebene beim Emulatorsystem. Wie kann ich das ändern?

    Viele Grüße,
    Hans-Jürgen

    1. Post
      Author
  9. Hallo
    zuerst, es ist ein super Tutorial, ich bin wirklich ein Anfänger.

    hab aber ein Problem,
    laut Log ist alles einwandfrei siehe unten, aber auf dem Handy stürzt die APP ab mit dem Fehler
    “ ShoppingListHQ angehalten“
    was mache ich Falsch?
    Viele Grüsse
    Vito

    01-27 14:02:36.349 29607-29607/de.programmierenlernenhq.shoppinglisthq D/MainActivity: Das Datenquellen-Objekt wird angelegt.
    01-27 14:02:36.349 29607-29607/de.programmierenlernenhq.shoppinglisthq D/ShoppingMemoDataSource: Unsere DataSource erzeugt jetzt den dbHelper.
    01-27 14:02:36.349 29607-29607/de.programmierenlernenhq.shoppinglisthq D/ShoppingMemoDbHelper: DbHelper hat die Datenbank: shopping_list.db erzeugt.
    01-27 14:02:36.359 29607-29607/de.programmierenlernenhq.shoppinglisthq D/MainActivity: Die Datenquelle wird geöffnet.
    01-27 14:02:36.359 29607-29607/de.programmierenlernenhq.shoppinglisthq D/ShoppingMemoDataSource: Eine Referenz auf die Datenbank wird jetzt angefragt.
    01-27 14:02:36.369 29607-29607/de.programmierenlernenhq.shoppinglisthq D/ShoppingMemoDbHelper: Die Tabelle wird mit SQL-Befehl: CREATE TABLE shopping_list(_id INTEGER PRIMARY KEY AUTOINCREMENT, product TEXT NOT NULL, quantity INTEGER NOT NULL, checkedBOOLEAN NOT NULL DEFAULT 0); angelegt.
    01-27 14:02:36.369 29607-29607/de.programmierenlernenhq.shoppinglisthq D/ShoppingMemoDataSource: Datenbank-Referenz erhalten. Pfad zur Datenbank: /data/user/0/de.programmierenlernenhq.shoppinglisthq/databases/shopping_list.db
    01-27 14:02:36.369 29607-29607/de.programmierenlernenhq.shoppinglisthq D/MainActivity: Folgende Einträge sind in der Datenbank vorhanden:

    1. Post
      Author

      Hallo Vito,

      momentan kann ich mich aus Zeitgründen leider kaum den Kommentaren widmen, daher kommt meine Antwort so spät 🙁

      Wenn du möchtest, kannst Du mir deine Projektdateien (den ganzen Android Studio Projektordner, aber OHNE die build-Unterordner) als ZIP per E-Mail zusenden. Die E-Mail Adresse kannst Du im Impressum finden. Ich schaue dann mal, ob ich etwas herausfinden kann.

      Viele Grüße, Chris

    2. @Vito
      Wenn das Protokoll am Ende deines Postings endet, dann sieht es auf den ersten Blick nach einem Problem in showAllLIstEntries() aus. Vermutlich liegt das Problem in ShoppingMemoDataSource.getAllShoppingMemos(), weil dort die Daten zusammengestellt werden.
      Wenn du an diesen Stellen jeweils einen Breakpoint setzt und die App im Debug Modus startest, kannst du genauer betrachten, welche Laufzeitwerte vorliegen und so (hoffentlich) den Fehler finden.
      Da mein Beitrag jetzt erst kommt, hast du vermutlich das Problem bereits beseitigt, aber wer weiß … 😉

  10. Hi Chris.

    Bei mir ist folgendes Problem aufgetaucht. Deine Anleitung ist super und funktioniert ohne Probleme! Dafür erstmal danke und Top geschrieben.

    Mein Problem fängt aber da an, in dem ich versuche einen Weiteren Wert (String) hinzuzufügen. Im Grunde habe ich immer mich an den Strings product orientiert, verdoppelt und in „nachname“ umgewandelt.

    Ich habe mal mein Log eingefügt. Ich weiß jetzt nicht woran es liegen könnte.
    Danke schonmal für die Hilfe!

    03-24 18:56:38.587 32579-32579/com.example.fabian.teamapp D/Team: Das Datenquellen-Objekt wird angelegt.
    03-24 18:56:38.587 32579-32579/com.example.fabian.teamapp D/TeamDatenDataSource: Unsere DataSource erzeugt jetzt den dbHelper.
    03-24 18:56:38.587 32579-32579/com.example.fabian.teamapp D/TeamDatenDbHelper: DbHelper hat die Datenbank: shopping_list.db erzeugt.
    03-24 18:56:38.597 32579-32579/com.example.fabian.teamapp D/Team: Die Datenquelle wird geöffnet.
    03-24 18:56:38.597 32579-32579/com.example.fabian.teamapp D/TeamDatenDataSource: Eine Referenz auf die Datenbank wird jetzt angefragt.
    03-24 18:56:38.607 32579-32579/com.example.fabian.teamapp D/TeamDatenDataSource: Datenbank-Referenz erhalten. Pfad zur Datenbank: /data/user/0/com.example.fabian.teamapp/databases/shopping_list.db
    03-24 18:56:38.607 32579-32579/com.example.fabian.teamapp D/Team: Folgende Einträge sind in der Datenbank vorhanden:

  11. Vielen Dank für dein Tutorial, wie vielen anderen hat es auch mir sehr geholfen 🙂

    Zum Schluss würde ich noch gerne wissen, wie ich meine Quellcodes so schön anschaulich exportieren kann ;D

    Wenn ich meinen Quellcode über Android Studio drucke sieht es ziemlich besch***** aus.

    1. Post
      Author

      Hallo HamGo,

      ich habe oft mehrere Screenshots vom Quellcode gemacht. Also das Android Studio Fenster mehrmals per Screenshot abfotografiert. Diese Screenshots habe ich dann manuell wieder zusammengesetzt. Sehr aufwendig, aber auch sehr hilfreich für die Leser.

      Viele Grüße, Chris

  12. Ein Super Tutorial. Funktionert sogar auf meinem Chinesenhandy mit Android 7 tadellos.
    Die ActionBar habe ich um einige Funktionen erweitert – läuft.
    Auch die Anzahl der Felder habe ich auf 8 erweitert. Läuft ebenfalls.
    Allerdings versuche ich seit Tagen, einen Multi-Column Listview hinzubekommen und schaffe es nicht.
    Irgendeine Idee, wie ich mit den bestehenden Klassen sowas erzeugen kann?

    Es sieht nicht schön aus, wenn alle Felder einfach nur durch Leerzeichen getrennt hintereinander gepresst stehen.
    Ein Layout dafür habe ich schon…..es müssen nur die Felder dort hinein – und genau das geht irgendwie nicht.

    Danke,
    Oliver

    1. Post
      Author

      Hallo Oliver,

      danke für dein Lob!

      Man müsste einen Custom-Adapter implementieren, der den ListView mit der Datenquelle verbindet. Vielleicht zeige ich in einem neuen Tutorial einmal wie so etwas geht.

      Viele Grüße, Chris

  13. Hey,

    ich habe eine Frage, kann ich es irgendwie hinbekommen das sich die APP mit anderen Handys Synct? Am besten mithilfe eines Raspberry Pi 3b!

    Bis dahin aber schonmal danke für das Tutorial…

    Liebe Grüße

  14. Hallo,
    erstmal vorweg, ein super Arbeit. Habe nach Deinem Beispiel mein erstes App soweit entwickelt, das es läuft und komme nun bei den Feinheiten nicht weiter. Wie kann ich die Daten der Datenbank in csv oder pdf wandeln um sie per mail zu verschicken. Für ein wenig Hilfe wäre ich dankbar. Habe einges im Netz gefunden, kann es aber nicht lauffähig einbinden.

    Gruß
    Josh

  15. Hallo Chris
    Danke für die tollen Kurse. Wirklich super gemacht.
    Hier noch ein Verbesserungsvorschlag: Wenn du ein UI-Element beschreibst (z.B. ein neues Menu) fände ich es sinnvoll, gleich am Anfang einen Screenshot davon zu zeigen. Dann wissen die Leser schon mal, um was es geht und können sich auf den Inhalt konzentrieren, anstatt sich erstmal vorstellen zu müssen, wie das denn genau aussehen wird.

    Gruss
    Klaus

    1. Post
      Author

      Hallo Klaus,

      danke für Dein Lob und den sehr hilfreichen Verbesserungsvorschlag! Den werde ich für zukünftige Tutorials berücksichtigen.

      Viele Grüße, Chris

  16. Hallo Chris
    Auch ich möchte mich dem Lob der Vorgänger anschließen.
    App läuft:
    1. Wie kann ich den Hintergrund der contextual-activity-bar ändern
    2. Wo kann ich die apk Datei finden android Studio
    Gruß Willi

  17. Hallo Chris
    Ich möchte mich dem Lob der Vorgänger anschließen.
    Nun habe ich diese App über BuildVariant als release eingestellt und über Build/Build APK(s) erzeugen lassen. Diese Datei habe ich zu Testzwecken auf das Smartphone übertragen und wollte diese dort installieren. Obwohl in der Einstellungen unbekannte Quellen aktiviert wurde, kommt nachdem ich den Installationsprozess ausgelöst habe (App wurde nicht installiert)
    Auch über das Internet habe ich keine funktionierte Lösung gefunden.
    Sollte es die Wertvolle Zeit zulassen, dann währe ich über jeden Hinweis dankbar.
    Gruß Willi

  18. Hallo Chris,
    erstmal ein großes Dankeschön für das Tutorial, ganz tolle Arbeit. Dank deiner guten Beschreibung war es ein Kinderspiel, allerdings für dich, so
    glaube ich eine aufwendige Angelegenheit. Die LogCat Methoden kannte ich noch nicht. Da gibt es ja verschiedene:
    Log.v(); // Verbose
    Log.d(); // Debug
    Log.i(); // Info
    Log.w(); // Warning
    Log.e(); // Error
    Habe in deinem Programm die Log.i(); // Info benutzt, statt Log.d(); // Debug
    dann sieht man es besser.
    Nochmals vielen Dank, guten Rutsch und sechs richtige im Lotto
    Heinz-Dieter

    1. Post
      Author

      Hallo Heinz-Dieter,

      danke für dein Lob und den Logcat Hinweis! Ja, in der Tat. Der Aufwand für diese Art von Tutorials ist sehr hoch, gemessen an der Zeit, die man für das Absolvieren der Reihe benötigt. Aber der Aufwand hat sich für mich gelohnt. Man lernt am meisten selbst, wenn man anderen etwas lernt. Dann merkt man direkt, ob man manche Zusammenhänge wirklich schon verstanden hat oder selbst noch etwas Studium benötigt.

      Viele Grüße, Chris

  19. Hallo Chris, mir hat das Tutorial sehr gut gefallen. Alles hat auf Anhieb wie beschrieben funktioniert. Ich hatte keine einzige Fehlermeldung. Das Tutorial lohnt sich wirklich. Für einen Anfänger wie mich war es klasse und sehr lehrreich.

    Im Teil 5 beschreibst Du zwei Tabellen. Eine mit „Primärschlüssel“ und eine zweite mit zusätzlichem Fremdschlüssel. Vielleicht könntest Du zu diesem Thema das Tutorial erweitern? Zum Beispiel um eine Tabelle mit Geschäften bei denen man die einzelnen Produkte erwerben kann. Dann könnte man auch eine 1:n Beziehung realisieren …

    Ich habe bisher noch keine Vorstellung wie man so etwas realisieren könnte. Im Netz fand ich bisher keine wirklich hilfreichen Hinweise.

    Gruß Alfred

    1. Post
      Author

      Hallo Alfred,

      danke für dein Lob! Mir gefällt dein Vorschlag sehr. In einer zukünftigen Aktualisierung dieses Tutorials könnte ich ein 1:n Beziehung einbauen. Dies wäre wahrscheinlich für einige Leser sehr interessant.

      Viele Grüße, Chris

  20. Seit vielen Jahren beschäftige ich mich mit Programmierung und habe Dutzende Bücher und Tutorials zu unterschiedlichen Sprachen gelesen. Eine derart sorgfältige Dokumentation ist mir dabei noch nicht begegnet – meine Hochachtung!
    Hier stimmt nicht nur der Code vollständig (was durchaus nicht selbstverständlich ist), sondern die einzelnen Schritte sind hervorragend aufgebaut, verständlich beschrieben und großartig illustriert.
    Vielen Dank!

    1. Post
      Author
  21. Hallo Chris

    ich bin jetzt mit deinem Tutorial auch komplett durch. Und schließe mich den anderen an.
    Das Tutorial ist sehr gut aufgbaut, sehr gut strukturiert. Man kann jederzeit sehr gut nachvollziehen, wo was eingefügt werden muss.
    Auch die einzelnen Erklärungen der entsprechenden Zeilen ist sehr gut.

    Ein Verbesserungsvorschlag noch für die 8. Lektion:
    Wenn ein Upgrade der DB durchgeführt wird, wird in deinem Beispiel der komplette Inhalt der DB gelöscht. Eine Idee wäre, wenn der Inhalt vorher ausgelesen werden könnte in eine temporäre Arraylist? und nach dem erneuten createDB der Inhalt direkt zurückgeschrieben. Wäre das eine mögliche Lösung, um Datenverlust vorzubeugen?

    Viele Grüße
    Carsten

    1. Post
      Author

      Hallo Carsten,

      danke für dein Lob! Ja dein Vorschlag ist die deutlich bessere Lösung und sollte sich auch schnell und sicher umsetzen lassen.

      Viele Grüße, Chris

  22. Hallo Chris

    und gleich noch eine Frage zur 8. Lektion:
    Wenn ich ein Item „abhake“ wird ja in dem „OnItemClickListener“ definiert, dass „memo.checked“ umgedreht wird.
    Wenn ich dein Beispiel richtig verstehe, hast du keine Arraylist mitlaufen, wo der Wert geändert wird, und über adapter.notifyDataSetChanged() der Adapter angewiesen wird, die View zu aktualisieren, oder?

    Daher leerst Du den Adapter, und befüllst ihn aus der Datenbank immer komplett neu.
    Ginge das auch anders? Könnte mir vorstellen, dass es bei sehr großen DB’s dadurch langsamer wird.

    Gruß
    Carsten

    1. Post
      Author

      Hallo Carsten,

      ja dies sollte man bei größeren Datenbanken unbedingt optimieren. Im Tutorial habe ich versucht den Code so einfach und kurz wie möglich zu halten.

      Viele Grüße, Chris

  23. Mein Android Studio meckert beim Build
    in Zeile 59: List emptyListForInitialization = new ArrayList();
    meckert das Build einen Fehler:
    error: cannot find symbol class ArrayList
    uses unchecked or unsafe operations

    ich kann keinen fehler im quellcode im vergleich zu dem obigen entdecken.

    P.S. das ist die Coolste Seite die ich in Bezug auf Android Programmierung bisher gefunden habe. Ich persönlich finde, das man Programmieren am besten anhand eines Projektes lernen kann, welche die verschiedenen Aspekte der Programmiersprache beleuchtet.

    Insgesamt Java an sich ist ja recht simpel, ähnlich wie C. Aber die Klassen und Interfaces etc.. da muss man sich schon sehr tief reinwühlen. Das finde ich immer noch sehr schwierig nachzuvollziehen.

    Bei der Implementation des Codes und der Beschreibung hätte ich mir vielleicht gewünscht eine Grafik auf die Tabelle mit einzubinden die den Sachverhalt mit den Zeigern verdeutlicht. Dennoch großes Lob, das beste was ich bisher sah.

  24. Hallo Chris,

    wirklich das beste Tutorial im Web!
    Mir hat es den Einstieg in die App Entwicklung sehr erleichtert.

    Vielleicht hättest du noch einen Tip für die Anwendung.
    Wie könnte man die Listview nach dem Markieren der Checkbox sortieren.
    Also die markierten Produkte unten anzeigen?

    Beste Grüße
    Dirk

  25. Super Tutorial!

    Aber eine Frage, auf die Settings die wir im Options Menu Angelegt haben, sind wir gar nicht eingegangen oder hab ich was verpasst?

    Viele Grüße

    1. Post
      Author

      Hallo Paul,

      der Settings-Eintrag stammt aus einer alten Version dieses Tutorials. In der aktuellen Version besitzt er keine Funktion.

      Viele Grüße, Chris

  26. Ein wirklich tolles Tutorial – Für einen Laien wie mich perfekt aufbereitet. Es hat bei mir auchalles einwandfrei funktioniert! Darauf lässt sich aufbauen und nun selber experimentieren 🙂

    Eine Frage habe ich nur für das in Lektion 1 eingebaute Menu. Für was haben wir den Menupunkt „Settings“ implementiert? Dieser hat keine Funktion oder habe ich was überlesen.

    1. Post
      Author

      Hallo Marko,

      danke für’s Lob! Der Settings-Menüeintrag ist ein Überbleibsel aus einer alten Version des Tutorials. Er besitzt in der Tat keine Funktion.

      Viele Grüße, Chris

  27. „In dieser Methode verändern die die Eigenschaften des TextView-Elements,…“

    *In dieser Methode verändern WIR die Eigenschaften…

    1. Post
      Author

      Hallo Daniel,

      danke für deinen Hinweis! Ich habe den Fehler korrigiert.

      Viele Grüße, Chris

  28. hallo chris,
    danke erstmal für diesen hervorragenden Kurs hier.
    Hier meine Frage, da ich die Appidee leicht abgeändert habe(richtung Sporttracker) würde ich noch gerne wissen wie man in sqlite sortiert bzw. auf einzelne werte zugreift ( für HighscoreListe). Im Netz hab ich da bisher wenig gut erklärtes Material gefunden. Es wäre schön, wenn du mir dabei helfen könntest.
    Gruss
    Timo

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.