Rheinwerk Computing < openbook >

 
Inhaltsverzeichnis
Vorwort
Teil I Grundlagen
1 Android – eine offene, mobile Plattform
2 Hallo Android!
3 Von der Idee zur Veröffentlichung
Teil II Elementare Anwendungsbausteine
4 Wichtige Grundbausteine von Apps
5 Benutzeroberflächen
6 Multitasking
Teil III Gerätefunktionen nutzen
7 Telefonieren und surfen
8 Sensoren, GPS und Bluetooth
Teil IV Dateien und Datenbanken
9 Dateien lesen, schreiben und drucken
10 Datenbanken
Teil V Multimedia und Produktivität
11 Multimedia
12 Kontakte und Organizer
A Einführung in Kotlin
B Jetpack Compose
C Häufig benötigte Codebausteine
D Literaturverzeichnis
E Die Begleitmaterialien
Stichwortverzeichnis

Ihre Meinung?
Spacer
<< zurück
Android 11 von Thomas Künneth
Das Praxisbuch für App-Entwickler
Buch: Android 11

Android 11
Pfeil 10 Datenbanken
Pfeil 10.1 Erste Schritte mit SQLite
Pfeil 10.1.1 Einstieg in SQLite
Pfeil 10.1.2 SQLite in Apps nutzen
Pfeil 10.2 Fortgeschrittene Operationen
Pfeil 10.2.1 Klickverlauf mit SELECT ermitteln
Pfeil 10.2.2 Daten mit UPDATE ändern und mit DELETE löschen
Pfeil 10.3 Implementierung eines eigenen Content Providers
Pfeil 10.3.1 Auf einen Content Provider zugreifen
Pfeil 10.3.2 Die Klasse »android.content.ContentProvider«
Pfeil 10.4 Zusammenfassung
 
Zum Seitenanfang

10    Datenbanken Zur vorigen ÜberschriftZur nächsten Überschrift

Datenbanken eignen sich hervorragend, um viele gleichförmige Informationen abzuspeichern. Mit Content Providern können Sie anderen Programmen die Daten Ihrer App zur Verfügung stellen. Wie Sie beides kombinieren, zeige ich Ihnen in diesem Kapitel.

Oft folgen die Daten, die mit einem Programm verarbeitet werden, einer wohldefinierten Struktur. Kontakte beispielsweise enthalten fast immer einen Namen, eine Anschrift, eine oder mehrere Telefonnummern sowie ein Geburtsdatum. In einem Literaturverzeichnis erwarten Sie den Titel des Buches, den Namen des Verfassers und das Erscheinungsjahr. Und um Termine zu speichern, sollten Beginn und Ende eines Termins (jeweils mit Datum und Uhrzeit), der Ort und eine Beschreibung vorhanden sein. Meine drei Beispiele zeigen Datensätze. Ein Datensatz fasst Datenfelder zusammen. Name, Titel und Beschreibung sind solche Felder.

Wie oder wo Datensätze gespeichert werden, ist in erster Linie eine technische Frage. Indem Sie eine Karteikarte aus Papier beschriften und einsortieren, legen Sie einen Datensatz an. Programme können Daten in klassischen Dateien speichern. Wie das funktioniert, beschreibe ich im vorangehenden Kapitel 9, »Dateien lesen, schreiben und drucken«. Welche Struktur hierbei entsteht, bestimmt die schreibende Anwendung. Möchten andere Programme darauf zugreifen, dann müssen sie das Format dieser Datei kennen. Eine interessante Alternative ist deshalb, die Speicherung einem Datenbanksystem zu überlassen. Android bringt eine solche Komponente mit.

 
Zum Seitenanfang

10.1    Erste Schritte mit SQLite Zur vorigen ÜberschriftZur nächsten Überschrift

Sehr häufig werden Datensätze als Tabellen dargestellt, wobei die Spaltenüberschriften Datenfelder repräsentieren und die Zeilen der Tabelle die eigentlichen Nutzdaten enthalten. Dies ist die zentrale Idee von relationalen Datenbanksystemen. Sie folgen dem relationalen Modell, das E. F. Codd erstmals 1970 vorgeschlagen hat. Die Grundlage dieses Modells bildet die mathematische Relation. Relationen bestehen aus einer Menge von Tupeln. Ein Tupel wiederum entspricht einer Zeile einer Tabelle. Welche Operationen auf eine Relation angewendet werden können, wird durch die relationale Algebra bestimmt.

Glücklicherweise tritt dieser stark mathematische Aspekt beim Programmieren in den Hintergrund, denn mit relationalen Datenbanken hat sich auch die Datenbanksprache SQL (Structured Query Language) etabliert. Mit ihr ist es unter anderem möglich, Daten abzufragen, zu verwalten und zu verändern.

 
Zum Seitenanfang

10.1.1    Einstieg in SQLite Zur vorigen ÜberschriftZur nächsten Überschrift

Eine Software zur Eingabe, Verwaltung und Bearbeitung von in Datenbanken abgelegten Informationen wird traditionell Datenbankmanagementsystem genannt. Oft werden die Programme, die die eigentliche Speicherung und Wiedergewinnung übernehmen, auf einem eigenen Datenbankserver installiert. Ein Client verbindet sich über ein Netzwerk mit ihm, übermittelt SQL-Anweisungen und empfängt Ergebnisse (zum Beispiel von Suchanfragen).

Damit das klappt, müssen beide Maschinen entsprechend konfiguriert, gegebenenfalls Benutzerkonten angelegt oder abgeglichen und Zugriffsrechte vergeben werden. Für den Einsatz in einem Unternehmen ist dieser Aufwand zweifellos gerechtfertigt. Möchten Sie in einer Anwendung »einfach nur« Daten speichern und abfragen, sind andere Qualitäten wichtig. Hierzu gehören unter anderem:

  • ein geringer Speicherverbrauch

  • eine möglichst einfache Konfiguration

  • eine schlanke Programmierschnittstelle

SQLite[ 17 ](https://www.sqlite.org) erfüllt diese Anforderungen. Es handelt sich hierbei um eine in sich geschlossene, serverlose und ohne Konfigurationsaufwand nutzbare transaktionale SQL-Datenbankmaschine. Google hat diese sehr kompakte, nur wenige 100 KB große In-Process-Bibliothek in Android integriert und stellt sie App-Entwicklern über einige leicht einsetzbare Klassen zur Verfügung. Wie Sie in den folgenden Abschnitten sehen werden, gelingt der Einstieg in die faszinierende Welt der Datenbanken ohne große Mühe. Und wenn Sie tiefer in die Materie einsteigen möchten, empfehle ich Ihnen zusätzlich einen Blick in das Literaturverzeichnis in Anhang D.

Wie Sie bereits wissen, werden Nutzdaten (zum Beispiel der Name einer Person und ihr Alter) als Zeilen einer benannten Tabelle gespeichert. Auch deren Spaltenüberschriften haben Namen, die nötig sind, um eine ganz bestimmte Spalte ansprechen zu können. Welche Werte sie aufnehmen kann, wird bei der Definition der Tabelle festgelegt. Auch Datenbanken selbst erhalten – Sie ahnen es sicher – einen Namen.

Nun fragen Sie sich bestimmt, wie Sie Datenbanken oder Tabellen anlegen und Inhalte einfügen, abfragen und verändern können. Vieles ist über SQL standardisiert möglich, anderes hängt vom verwendeten Datenbankmanagementsystem ab. Vor allem große Systeme kennen beispielsweise die Anweisung CREATE DATABASE, um eine neue Datenbank anzulegen. In SQLite entspricht eine Datenbank hingegen genau einer Datei, deshalb müssen Sie eine Datei erzeugen, um mit einer neuen Datenbank zu arbeiten. Ein zusätzlicher Befehl ist nicht nötig.

Bevor ich Ihnen zeige, wie Sie in Ihren Programmen auf SQLite zugreifen, möchte ich Ihnen ein paar grundlegende SQL-Kenntnisse vermitteln. Falls Sie bereits mit SQL vertraut sind, können Sie diesen Abschnitt gefahrlos überspringen. Da man sich Dinge am leichtesten merken kann, wenn man sie selbst ausprobiert hat, werden Sie diese Befehle auf der Kommandozeile eingeben. Damit das funktioniert, müssen Sie die Kommandozeilentools des Android SDK dem Standardsuchpfad hinzugefügt haben. Weitere Hinweise finden Sie in Abschnitt 1.3.1, »Android Studio und Android SDK installieren«.

Datenbanken und Tabellen anlegen

Als Erstes müssen Sie SQLite starten. Wechseln Sie in der Eingabeaufforderung, dem Terminal oder einer anderen Shell in ein Verzeichnis, in dem Sie die Datenbank ablegen möchten. Geben Sie dann sqlite3 test.db ein, und drücken Sie die (¢)-Taste. test.db ist der Name der Datenbankdatei, mit der Sie arbeiten möchten. Da diese Datei noch nicht existiert, legt das System sie für Sie an. Als Nächstes erzeugen Sie eine – zunächst leere – Tabelle. Hierfür ist CREATE TABLE zuständig. Geben Sie den folgenden Befehl in einer Zeile ein, und bestätigen Sie mit der (¢)-Taste:

CREATE TABLE testtabelle (age INTEGER, name VARCHAR(32));

Achten Sie dabei auf den Strichpunkt am Ende. Ihre Eingabe besteht aus den drei folgenden Bereichen:

  1. der auszuführenden Operation (CREATE TABLE)

  2. dem Namen der anzulegenden Tabelle

  3. einer zwischen runde Klammern gesetzten Liste mit Spaltendefinitionen, deren Elemente durch ein Komma voneinander getrennt werden

Wie Sie bereits wissen, werden den Spalten einer Datenbanktabelle Namen und Datentypen zugewiesen. testtabelle besteht aus den beiden Spalten age und name. Der Name einer Person kann in diesem Fall bis zu 32 Zeichen enthalten. Das Alter wird als ganze Zahl angegeben. Anstelle des Spaltentyps VARCHAR können Sie übrigens auch TEXT verwenden. Dann entfällt die Längenangabe in Klammern.

Datensätze ablegen

Mit INSERT INTO fügen Sie einer Tabelle Datensätze hinzu:

INSERT INTO testtabelle (age, name) VALUES (49, 'Thomas');

Ihre Eingabe besteht aus fünf Bereichen:

  1. der auszuführenden Operation (INSERT INTO)

  2. dem Namen der zu befüllenden Tabelle

  3. einer zwischen runde Klammern gesetzten Liste mit Spaltennamen, deren Elemente durch ein Komma voneinander getrennt werden

  4. dem Schlüsselwort VALUES

  5. einer zwischen runde Klammern gesetzten Liste mit Werten, deren Elemente durch ein Komma voneinander getrennt werden

Die erste geklammerte Liste gibt an, welche Spalten einer Tabelle mit Werten gefüllt werden sollen. Die zweite Liste enthält die abzulegenden Daten. Der SQL-Befehl INSERT INTO ist sehr mächtig. Beispielsweise können Sie unter bestimmten Umständen Spalten auslassen. Fügen Sie der Tabelle noch ein paar Zeilen hinzu. Diesmal lassen wir die zu befüllenden Spalten weg. Das ist möglich, wenn die Liste nach VALUES für jede Spalte der Datenbank einen Wert enthält und die Werte in derselben Reihenfolge wie bei der Definition der Tabellen übergeben werden.

INSERT INTO testtabelle VALUES (53, 'Andreas');
INSERT INTO testtabelle VALUES (82, 'Rudolf');

Daten abfragen

Mit der Anweisung

SELECT * FROM testtabelle;

können Sie sich die Tabelle ansehen. Anstelle des Asterisks (*) können Sie eine durch Kommata getrennte Liste von Spaltennamen übergeben. Das Kommando

SELECT age, name FROM testtabelle;

liefert hier also dasselbe Ergebnis. Lassen Sie uns noch ein wenig mit SQL experimentieren. Um die Anzahl der Zeilen von testtabelle zu zählen, verwenden Sie folgenden Befehl:

SELECT COUNT(*) FROM testtabelle;

Das Durchschnittsalter der gespeicherten Personen erfragen Sie mit folgender Anweisung:

SELECT AVG(age) FROM testtabelle;

Geben Sie zum Schluss noch folgendes Kommando ein:

SELECT age FROM testtabelle WHERE name IS 'Thomas';

Diese Anweisung liefert das Alter einer Person mit dem Namen Thomas. Falls mehrere solcher Datensätze existieren (was natürlich problemlos möglich ist), werden entsprechend viele Zahlen ausgegeben. Mit .exit (achten Sie auf den führenden Punkt) beenden Sie SQLite. Die vollständige Sitzung mit allen ausgeführten SQL-Anweisungen ist in Abbildung 10.1 zu sehen.

[+]  Tipp

In Bezug auf Schlüsselwörter (CREATE, SELECT, ...) unterscheidet SQL nicht zwischen Groß- und Kleinschreibung. Allein schon aus Gründen der Lesbarkeit sollten Sie aber der Konvention folgen, diese in Großbuchstaben zu notieren, Bezeichner hingegen, zum Beispiel Tabellen- und Spaltennamen, in Kleinbuchstaben.

Interaktive sqlite3-Sitzung

Abbildung 10.1    Interaktive sqlite3-Sitzung

Sie haben jetzt erste Erfahrungen mit der Datenbanksprache SQL gesammelt und interaktiv mit SQLite gearbeitet. Nun wollen wir uns ansehen, wie Sie in Ihren Apps auf Datenbanken zugreifen.

 
Zum Seitenanfang

10.1.2    SQLite in Apps nutzen Zur vorigen ÜberschriftZur nächsten Überschrift

In diesem Abschnitt stelle ich Ihnen die Beispiel-App DBDemo1 vor. Ihre Benutzeroberfläche ist in Abbildung 10.2 zu sehen. Sie besteht aus vier Schaltflächen – drei Smileys sowie VERLAUF. Die nicht ganz ernst gemeinte Idee ist, durch Anklicken eines der drei Gesichter Ihre aktuelle Stimmung zu dokumentieren. Das Programm legt den Zeitpunkt des Klicks sowie den Smiley-Typ in einer Datenbanktabelle ab.

Die App »TKMoodley«

Abbildung 10.2    Die App »TKMoodley«

onCreate() (Listing 10.1) registriert für drei der vier Schaltflächen einen OnClickListener. Alle Smileys rufen imageButtonClicked() auf. Diese private Methode erzeugt einen neuen Eintrag in einer Datenbank (wie das funktioniert, erkläre ich gleich) und gibt die Meldung Gespeichert in einem Toast aus. Ein Klick auf VERLAUF bewirkt im Moment noch nichts. Das holen wir in Abschnitt 10.2.1, »Klickverlauf mit SELECT ermitteln«, nach.

package com.thomaskuenneth.androidbuch.dbdemo1

import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class DBDemo1Activity : AppCompatActivity() {
private lateinit var openHelper: DBDemo1OpenHelper

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fine.setOnClickListener { imageButtonClicked(MOOD_FINE) }
ok.setOnClickListener { imageButtonClicked(MOOD_OK) }
bad.setOnClickListener { imageButtonClicked(MOOD_BAD) }
openHelper = DBDemo1OpenHelper(this)
}

override fun onPause() {
super.onPause()
openHelper.close()
}

private fun imageButtonClicked(mood: Int) {
openHelper.insert(mood, System.currentTimeMillis())
Toast.makeText(this, R.string.saved, Toast.LENGTH_SHORT)
.show()
}
}

Listing 10.1    Die Klasse »DBDemo1Activity«

openHelper = DBDemo1OpenHelper(this) instanziiert ein Objekt des Typs DBDemo1OpenHelper. Auf diese Instanzvariable wird in den Methoden imageButtonClicked() (Aufruf von insert()) und onPause() (Aufruf von close()) zugegriffen. Damit eine App eine Datenbank verwenden kann, muss diese unter anderem angelegt und geöffnet werden. Hierbei hilft Ihnen android.database.sqlite.SQLiteOpenHelper. Die abstrakte Klasse vereinfacht die Kommunikation mit SQLite. Kinder müssen deren Methoden onCreate() und onUpgrade() implementieren.

[»]  Hinweis

In meinem Beispiel wird onPause() überschrieben, um eine geöffnete Datenbank beim Pausieren der Activity zu schließen. Ein explizites erneutes Öffnen ist aber nicht nötig. Das geschieht in DBDemo1OpenHelper implizit beim Zugriff.

Möchte eine App auf eine Datenbank zugreifen, prüft Android, ob diese schon existiert. Ist dies nicht der Fall, ruft das System onCreate() auf. In dieser Methode sollten mit CREATE TABLE alle Tabellen angelegt und gegebenenfalls mit Grunddaten versorgt werden. Um das Anlegen der Datenbank selbst müssen Sie sich nicht kümmern. onUpgrade() wird aufgerufen, wenn sich die Version einer Datenbank geändert hat. Diese Nummer übermitteln Sie bei der Instanziierung Ihrer SQLiteOpenHelper-Ableitung an das System. Benötigen Sie in einer Tabelle zusätzliche Spalten oder müssen Sie die komplette Struktur Ihrer Datenbank ändern, erhöhen Sie die Versionsnummer um 1. Einfache Änderungen an der Tabellenstruktur sind mit ALTER TABLE möglich.

Die im Folgenden abgedruckte Beispielimplementierung löscht mit dem SQL-Befehl DROP TABLE die einzige Tabelle der Datenbank und ruft anschließend onCreate() auf. Das hat natürlich zur Folge, dass vorhandene Datensätze verloren gehen. Wenn Sie Ihre App im Google Play Store anbieten oder auf andere Weise veröffentlichen, sollten Sie diese Datensätze stattdessen in geeigneter Weise »parken« und nach der Aktualisierung der Tabellenstruktur wieder einspielen.

package com.thomaskuenneth.androidbuch.dbdemo1

import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteException
import android.database.sqlite.SQLiteOpenHelper
import android.util.Log

// Konstanten für die Stimmungen
const val MOOD_FINE = 1
const val MOOD_OK = 2
const val MOOD_BAD = 3

// Name und Version der Datenbank
private const val DATABASE_NAME = "tkmoodley.db"
private const val DATABASE_VERSION = 1

private val TAG = DBDemo1OpenHelper::class.simpleName
class DBDemo1OpenHelper(context: Context) :
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {

// Name und Attribute der Tabelle "mood"
private val id = "_id"
private val tableMoodName = "mood"
private val moodTime = "timeMillis"
private val moodMood = "mood"

// Tabelle "mood" anlegen
private val tableMoodCreate = """
CREATE TABLE $tableMoodName (
$id INTEGER PRIMARY KEY AUTOINCREMENT,
$moodTime INTEGER,
$moodMood INTEGER);")
""".trimIndent()

// Tabelle "mood" löschen
private val tableMoodDrop = "DROP TABLE IF EXISTS $tableMoodName"

override fun onCreate(db: SQLiteDatabase) {
db.execSQL(tableMoodCreate)
}

override fun onUpgrade(
db: SQLiteDatabase, oldVersion: Int,
newVersion: Int
) {
Log.w(
TAG, """
Upgrade der Datenbank von Version $oldVersion zu $newVersion.
Alle Daten werden gelöscht."

""".trimIndent()
)
db.
execSQL(tableMoodDrop)
onCreate(db)
}


fun insert(mood: Int, timeMillis: Long) {
var rowId = -1L
try {

// Datenbank öffnen
Log.d(TAG, "Pfad: " + writableDatabase.path)
// die zu speichernden Werte
val values = ContentValues()
values.put(moodMood, mood)
values.put(moodTime, timeMillis)
// in die Tabelle "mood" einfügen
rowId = writableDatabase.insert(tableMoodName, null, values)
} catch (e: SQLiteException) {
Log.e(TAG, "insert()", e)
} finally {
Log.d(TAG, "insert(): rowId=$rowId")
}
}
}

Listing 10.2    Die Klasse »DBDemo1OpenHelper«

Die Methode insert() wird aus DBDemo1Activity aufgerufen, wenn der Benutzer einen Smiley angeklickt hat. Sie baut ein ContentValues-Objekt zusammen und speichert es mit writableDatabase.insert() in der Datenbank. Es enthält Informationen darüber, welchen Spalten welche Werte zugewiesen werden sollen. writableDatabase verweist auf ein Objekt des Typs SQLiteDatabase. Außer für das Einfügen von Tabellenzeilen brauchen Sie solche Objekte unter anderem auch für das Anlegen oder Löschen von Tabellen. Es wird beispielsweise onUpgrade() und onCreate() übergeben. Dort verwende ich es, um mit execSQL() SQL-Anweisungen auszuführen.

Sehen Sie sich die Definition der Instanzvariablen tableMoodCreate an. Die Variable enthält eine vollständige CREATE TABLE-Anweisung, mit der die Tabelle mood erstellt wird. Diese besteht aus den drei Spalten _id, timeMillis und mood. Die Spalte _id wird aufgrund des Schlüsselwortes AUTOINCREMENT selbsttätig befüllt. Aus diesem Grund finden Sie in insert() nur zwei Aufrufe der Methode put(), nämlich einmal für timeMillis und einmal für mood.

Das Erfassen von Stimmungen funktioniert jetzt also schon tadellos. Im folgenden Abschnitt zeige ich Ihnen, wie Sie den Verlauf Ihrer Klicks anzeigen. Außerdem werden wir die App um die Möglichkeit erweitern, den Smiley-Typ nachträglich zu ändern und einen Eintrag komplett zu löschen.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
Zur Rheinwerk-Konferenz für Kotlin
 Buchempfehlungen
Zum Rheinwerk-Shop: Kotlin

Kotlin


Zum Rheinwerk-Shop: Praxisbuch Usability und UX

Praxisbuch Usability und UX


Zum Rheinwerk-Shop: Flutter und Dart

Flutter und Dart


Zum Rheinwerk-Shop: App-Design

App-Design


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2023
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 
[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern