Grundlagen von Android und Kotlin 06.1: Room-Datenbank erstellen

Dieses Codelab ist Teil des Kurses „Grundlagen von Android und Kotlin“. Sie können diesen Kurs am besten nutzen, wenn Sie die Codelabs der Reihe nach durcharbeiten. Alle Codelabs des Kurses sind auf der Landingpage für Codelabs zu den Grundlagen von Android und Kotlin aufgeführt.

Einführung

Die meisten Apps haben Daten, die auch nach dem Schließen der App durch den Nutzer aufbewahrt werden müssen. Die App speichert beispielsweise eine Playlist, ein Inventar von Spielelementen, Aufzeichnungen von Ausgaben und Einnahmen, einen Katalog von Sternbildern oder Schlafdaten im Zeitverlauf. Normalerweise verwenden Sie eine Datenbank zum Speichern persistenter Daten.

Room ist eine Datenbankbibliothek, die Teil von Android Jetpack ist. Room übernimmt viele der Aufgaben, die mit dem Einrichten und Konfigurieren einer Datenbank verbunden sind, und ermöglicht es Ihrer App, über normale Funktionsaufrufe mit der Datenbank zu interagieren. Room ist eine Abstraktionsschicht über einer SQLite-Datenbank. Die Terminologie von Room und die Abfragesyntax für komplexere Abfragen folgen dem SQLite-Modell.

Das Bild unten zeigt, wie die Room-Datenbank in die in diesem Kurs empfohlene Gesamtarchitektur passt.

Was Sie bereits wissen sollten

Sie sollten mit Folgendem vertraut sein:

  • Einfache Benutzeroberfläche für eine Android-App erstellen
  • Aktivitäten, Fragmente und Ansichten verwenden
  • Zwischen Fragmenten navigieren und Safe Args (ein Gradle-Plug-in) verwenden, um Daten zwischen Fragmenten zu übergeben.
  • Modelle, Viewmodel-Factories, LiveData und seine Observer ansehen. Diese Themen zu Architecture Components werden in einem früheren Codelab in diesem Kurs behandelt.
  • Grundkenntnisse in SQL-Datenbanken und der SQLite-Sprache. Eine kurze Übersicht oder Auffrischung finden Sie im SQLite-Primer.

Lerninhalte

  • So erstellen Sie eine Room-Datenbank und interagieren mit ihr, um Daten zu speichern.
  • So erstellen Sie eine Datenklasse, die eine Tabelle in der Datenbank definiert.
  • Verwendung eines Data Access Object (DAO) zum Zuordnen von Kotlin-Funktionen zu SQL-Abfragen.
  • So testen Sie, ob Ihre Datenbank funktioniert.

Aufgaben

  • Erstelle eine Room-Datenbank mit einer Schnittstelle für Daten zum nächtlichen Schlaf.
  • Testen Sie die Datenbank mit den bereitgestellten Tests.

In diesem Codelab erstellen Sie den Datenbankteil einer App, die die Schlafqualität erfasst. Die App verwendet eine Datenbank, um Schlafdaten im Zeitverlauf zu speichern.

Die App hat zwei Bildschirme, die durch Fragmente dargestellt werden, wie in der Abbildung unten zu sehen ist.

Auf dem ersten Bildschirm links befinden sich Schaltflächen zum Starten und Beenden des Trackings. Auf dem Bildschirm werden alle Schlafdaten des Nutzers angezeigt. Mit der Schaltfläche Löschen werden alle Daten, die die App für den Nutzer erhoben hat, dauerhaft gelöscht.

Auf dem zweiten Bildschirm rechts können Sie eine Bewertung der Schlafqualität auswählen. In der App wird die Bewertung numerisch dargestellt. Zu Entwicklungszwecken werden in der App sowohl die Gesichtssymbole als auch ihre numerischen Entsprechungen angezeigt.

Der Ablauf für den Nutzer sieht so aus:

  • Der Nutzer öffnet die App und sieht den Bildschirm für die Schlafanalyse.
  • Der Nutzer tippt auf die Schaltfläche Starten. Die Startzeit wird aufgezeichnet und angezeigt. Die Schaltfläche Starten ist deaktiviert und die Schaltfläche Beenden ist aktiviert.
  • Der Nutzer tippt auf die Schaltfläche Stopp. Dadurch wird die Endzeit aufgezeichnet und der Bildschirm „Schlafqualität“ geöffnet.
  • Der Nutzer wählt ein Symbol für die Schlafqualität aus. Der Bildschirm wird geschlossen und auf dem Tracking-Bildschirm werden die Endzeit des Schlafs und die Schlafqualität angezeigt. Die Schaltfläche Beenden ist deaktiviert und die Schaltfläche Starten ist aktiviert. Die App ist bereit für eine weitere Nacht.
  • Die Schaltfläche Löschen ist immer dann aktiviert, wenn Daten in der Datenbank vorhanden sind. Wenn der Nutzer auf die Schaltfläche Löschen tippt, werden alle seine Daten unwiderruflich gelöscht. Es wird keine Bestätigungsmeldung angezeigt.

Diese App verwendet eine vereinfachte Architektur, wie unten im Kontext der vollständigen Architektur dargestellt. Die App verwendet nur die folgenden Komponenten:

  • UI-Controller
  • Modell und LiveData ansehen
  • Eine Room-Datenbank

Schritt 1: Start-App herunterladen und ausführen

  1. Laden Sie die App TrackMySleepQuality-Starter von GitHub herunter.
  2. Erstellen Sie die App und führen Sie sie aus. In der App wird die Benutzeroberfläche für das Fragment SleepTrackerFragment angezeigt, aber keine Daten. Die Tasten reagieren nicht auf Antippen.

Schritt 2: Startanwendung prüfen

  1. Sehen Sie sich die Gradle-Dateien an:
  • Gradle-Datei des Projekts
    : In der build.gradle-Datei auf Projektebene sehen Sie die Variablen, mit denen die Bibliotheksversionen angegeben werden. Die in der Starter-App verwendeten Versionen funktionieren gut zusammen und auch mit dieser App. Wenn Sie dieses Codelab abgeschlossen haben, werden Sie in Android Studio möglicherweise aufgefordert, einige der Versionen zu aktualisieren. Es liegt an Ihnen, ob Sie die Versionen in der App aktualisieren oder beibehalten möchten. Wenn Sie auf „seltsame“ Kompilierungsfehler stoßen, versuchen Sie es mit der Kombination von Bibliotheksversionen, die in der App mit der endgültigen Lösung verwendet werden.
  • Die Gradle-Datei des Moduls. Beachten Sie die bereitgestellten Abhängigkeiten für alle Android Jetpack-Bibliotheken, einschließlich Room, und die Abhängigkeiten für Coroutines.
  1. Pakete und Benutzeroberfläche Die App ist nach Funktionen strukturiert. Das Paket enthält Platzhalterdateien, in die Sie im Laufe dieser Codelabs Code einfügen.
  • Das Paket database für den gesamten Code, der sich auf die Room-Datenbank bezieht.
  • Die Pakete sleepquality und sleeptracker enthalten das Fragment, das View-Modell und die View-Modell-Factory für jeden Bildschirm.
  1. Sehen Sie sich die Datei Util.kt an, die Funktionen zum Anzeigen von Daten zur Schlafqualität enthält. Einige Codezeilen sind auskommentiert, da sie auf ein View-Modell verweisen, das Sie später erstellen.
  2. Sehen Sie sich den androidTest-Ordner (SleepDatabaseTest.kt) an. Mit diesem Test können Sie prüfen, ob die Datenbank wie vorgesehen funktioniert.

Unter Android werden Daten in Datenklassen dargestellt. Der Zugriff auf die Daten und die Änderung der Daten erfolgt über Funktionsaufrufe. In der Welt der Datenbanken benötigen Sie jedoch Entitäten und Abfragen.

  • Eine Entität stellt ein Objekt oder Konzept und seine Attribute dar, die in der Datenbank gespeichert werden sollen. Eine Entitätsklasse definiert eine Tabelle und jede Instanz dieser Klasse stellt eine Zeile in der Tabelle dar. Jede Eigenschaft definiert eine Spalte. In Ihrer App enthält die Entität Informationen zu einer Nacht Schlaf.
  • Eine Abfrage ist eine Anfrage nach Daten oder Informationen aus einer Datenbanktabelle oder einer Kombination von Tabellen oder eine Anfrage zum Ausführen einer Aktion für die Daten. Häufige Anfragen sind das Abrufen, Einfügen und Aktualisieren von Entitäten. Du könntest beispielsweise alle aufgezeichneten Schlafnächte nach Startzeit sortiert abfragen.

Room übernimmt die ganze Arbeit für Sie, um von Kotlin-Datenklassen zu Entitäten zu gelangen, die in SQLite-Tabellen gespeichert werden können, und von Funktionsdeklarationen zu SQL-Abfragen.

Sie müssen jede Entität als annotierte Datenklasse und die Interaktionen als annotierte Schnittstelle, ein Data Access Object (DAO), definieren. Room verwendet diese annotierten Klassen, um Tabellen in der Datenbank und Abfragen zu erstellen, die auf die Datenbank zugreifen.

Schritt 1: SleepNight-Entität erstellen

In dieser Aufgabe definieren Sie eine Nacht Schlaf als annotierte Datenklasse.

Für eine Nacht Schlaf müssen Sie die Start- und Endzeit sowie eine Qualitätsbewertung aufzeichnen.

Außerdem benötigen Sie eine ID, um die Nacht eindeutig zu identifizieren.

  1. Suchen Sie im Paket database nach der Datei SleepNight.kt und öffnen Sie sie.
  2. Erstelle die Datenklasse SleepNight mit Parametern für eine ID, eine Startzeit (in Millisekunden), eine Endzeit (in Millisekunden) und eine numerische Bewertung der Schlafqualität.
  • Sie müssen sleepQuality initialisieren und auf -1 setzen, um anzugeben, dass keine Qualitätsdaten erhoben wurden.
  • Sie müssen auch die Endzeit initialisieren. Legen Sie sie auf die Startzeit fest, um anzugeben, dass noch keine Endzeit aufgezeichnet wurde.
data class SleepNight(
       var nightId: Long = 0L,
       val startTimeMilli: Long = System.currentTimeMillis(),
       var endTimeMilli: Long = startTimeMilli,
       var sleepQuality: Int = -1
)
  1. Annotieren Sie die Datenklasse vor der Klassendeklaration mit @Entity. Geben Sie der Tabelle den Namen daily_sleep_quality_table. Das Argument für tableName ist optional, wird aber empfohlen. Weitere Argumente finden Sie in der Dokumentation.

    Importieren Sie bei Aufforderung Entity und alle anderen Anmerkungen aus der androidx-Bibliothek.
@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(...)
  1. Wenn Sie nightId als Primärschlüssel festlegen möchten, kommentieren Sie die Property nightId mit @PrimaryKey. Legen Sie den Parameter autoGenerate auf true fest, damit Room die ID für jede Einheit generiert. So wird sichergestellt, dass die ID für jede Nacht eindeutig ist.
@PrimaryKey(autoGenerate = true)
var nightId: Long = 0L,...
  1. Kommentieren Sie die verbleibenden Properties mit @ColumnInfo. Passen Sie die Attributnamen mit Parametern an, wie unten dargestellt.
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(
       @PrimaryKey(autoGenerate = true)
       var nightId: Long = 0L,

       @ColumnInfo(name = "start_time_milli")
       val startTimeMilli: Long = System.currentTimeMillis(),

       @ColumnInfo(name = "end_time_milli")
       var endTimeMilli: Long = startTimeMilli,

       @ColumnInfo(name = "quality_rating")
       var sleepQuality: Int = -1
)
  1. Erstellen und führen Sie Ihren Code aus, um sicherzustellen, dass er keine Fehler enthält.

In dieser Aufgabe definieren Sie ein Data Access Object (DAO). Unter Android bietet das DAO praktische Methoden zum Einfügen, Löschen und Aktualisieren der Datenbank.

Wenn Sie eine Room-Datenbank verwenden, fragen Sie die Datenbank ab, indem Sie Kotlin-Funktionen in Ihrem Code definieren und aufrufen. Diese Kotlin-Funktionen werden SQL-Abfragen zugeordnet. Sie definieren diese Zuordnungen in einem DAO mithilfe von Annotationen und Room generiert den erforderlichen Code.

Ein DAO definiert eine benutzerdefinierte Schnittstelle für den Zugriff auf Ihre Datenbank.

Für gängige Datenbankvorgänge bietet die Room-Bibliothek praktische Anmerkungen wie @Insert, @Delete und @Update. Für alles andere gibt es die Annotation @Query. Sie können jede Abfrage schreiben, die von SQLite unterstützt wird.

Wenn Sie Ihre Abfragen in Android Studio erstellen, prüft der Compiler Ihre SQL-Abfragen auf Syntaxfehler.

Für die Schlaftracker-Datenbank mit Schlafnächten müssen Sie Folgendes tun können:

  • Fügen Sie neue Nächte ein.
  • Aktualisieren Sie eine bestehende Nacht, um eine Endzeit und eine Qualitätsbewertung zu aktualisieren.
  • Get (Abrufen) einer bestimmten Nacht anhand ihres Schlüssels.
  • Alle Nächte abrufen, damit sie angezeigt werden können.
  • Die letzte Nacht ansehen
  • Löschen Sie alle Einträge in der Datenbank.

Schritt 1: SleepDatabase-DAO erstellen

  1. Öffnen Sie im Paket database die Datei SleepDatabaseDao.kt.
  2. Beachten Sie, dass interface SleepDatabaseDao mit @Dao annotiert ist. Alle DAOs müssen mit dem Schlüsselwort @Dao annotiert werden.
@Dao
interface SleepDatabaseDao {}
  1. Fügen Sie im Body der Schnittstelle eine @Insert-Annotation hinzu. Fügen Sie unter @Insert eine insert()-Funktion hinzu, die eine Instanz der Entity-Klasse SleepNight als Argument verwendet.

    Das war's auch schon. Room generiert den gesamten erforderlichen Code, um SleepNight in die Datenbank einzufügen. Wenn Sie insert() in Ihrem Kotlin-Code aufrufen, führt Room eine SQL-Abfrage aus, um die Entität in die Datenbank einzufügen. Hinweis: Sie können die Funktion beliebig benennen.
@Insert
fun insert(night: SleepNight)
  1. Fügen Sie für ein SleepNight die Annotation @Update mit einer update()-Funktion hinzu. Die aktualisierte Entität ist die Entität mit demselben Schlüssel wie der übergebene Schlüssel. Sie können einige oder alle anderen Attribute der Entität aktualisieren.
@Update
fun update(night: SleepNight)

Für die verbleibende Funktionalität gibt es keine Convenience-Annotation. Sie müssen also die Annotation @Query verwenden und SQLite-Abfragen angeben.

  1. Fügen Sie eine @Query-Annotation mit einer get()-Funktion hinzu, die ein Long-key -Argument akzeptiert und einen nullable SleepNight-Wert zurückgibt. Es wird ein Fehler für einen fehlenden Parameter angezeigt.
@Query
fun get(key: Long): SleepNight?
  1. Die Abfrage wird als Stringparameter an die Anmerkung übergeben. Fügen Sie @Query einen Parameter hinzu. Erstelle eine String, die eine SQLite-Abfrage ist.
  • Alle Spalten aus der daily_sleep_quality_table-Tabelle auswählen
  • WHERE – nightId stimmt mit dem Argument :key überein.

    Beachten Sie die :key. Sie verwenden die Doppelpunktnotation in der Abfrage, um auf Argumente in der Funktion zu verweisen.
("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
  1. Fügen Sie ein weiteres @Query mit einer clear()-Funktion und einer SQLite-Abfrage hinzu, um alles aus dem daily_sleep_quality_table zu DELETE. Mit dieser Abfrage wird die Tabelle selbst nicht gelöscht.

    Mit der Annotation @Delete wird ein Element gelöscht. Sie können @Delete verwenden und eine Liste der zu löschenden Nächte angeben. Der Nachteil ist, dass Sie abrufen müssen, was in der Tabelle enthalten ist, oder es wissen müssen. Die Annotation @Delete eignet sich gut zum Löschen bestimmter Einträge, ist aber nicht effizient, um alle Einträge aus einer Tabelle zu entfernen.
@Query("DELETE FROM daily_sleep_quality_table")
fun clear()
  1. Fügen Sie ein @Query mit einer getTonight()-Funktion hinzu. Machen Sie den von getTonight() zurückgegebenen SleepNight nullable, damit die Funktion den Fall verarbeiten kann, in dem die Tabelle leer ist. Die Tabelle ist am Anfang und nach dem Löschen der Daten leer.

    Schreibe eine SQLite-Abfrage, die das erste Element einer Liste von Ergebnissen zurückgibt, die nach nightId in absteigender Reihenfolge sortiert sind, um „heute Abend“ aus der Datenbank abzurufen. Verwenden Sie LIMIT 1, um nur ein Element zurückzugeben.
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
fun getTonight(): SleepNight?
  1. Fügen Sie ein @Query mit einer getAllNights()-Funktion hinzu:
  • Lassen Sie von der SQLite-Abfrage alle Spalten aus daily_sleep_quality_table in absteigender Reihenfolge zurückgeben.
  • Lass getAllNights() eine Liste von SleepNight-Entitäten als LiveData zurückgeben. Room hält LiveData für Sie auf dem neuesten Stand. Sie müssen die Daten also nur einmal explizit abrufen.
  • Möglicherweise müssen Sie LiveData aus androidx.lifecycle.LiveData importieren.
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
fun getAllNights(): LiveData<List<SleepNight>>
  1. Obwohl keine sichtbaren Änderungen vorgenommen werden, sollten Sie Ihre App ausführen, um sicherzugehen, dass sie keine Fehler enthält.

In dieser Aufgabe erstellen Sie eine Room-Datenbank, die die Entity und das DAO verwendet, die Sie in der vorherigen Aufgabe erstellt haben.

Sie müssen eine abstrakte Datenbank-Holder-Klasse erstellen, die mit @Database annotiert ist. Diese Klasse hat eine Methode, die entweder eine Instanz der Datenbank erstellt, wenn die Datenbank nicht vorhanden ist, oder einen Verweis auf eine vorhandene Datenbank zurückgibt.

Das Abrufen einer Room-Datenbank ist etwas aufwendig. Hier ist der allgemeine Ablauf, bevor Sie mit dem Code beginnen:

  • Erstelle eine public abstract-Klasse, die extends RoomDatabase. Diese Klasse dient als Datenbankhalter. Die Klasse ist abstrakt, da Room die Implementierung für Sie erstellt.
  • Kommentieren Sie die Klasse mit @Database. Deklarieren Sie in den Argumenten die Entitäten für die Datenbank und legen Sie die Versionsnummer fest.
  • Definieren Sie in einem companion-Objekt eine abstrakte Methode oder Property, die einen SleepDatabaseDao zurückgibt. Room generiert den Text für Sie.
  • Sie benötigen nur eine Instanz der Room-Datenbank für die gesamte App. Machen Sie RoomDatabase daher zu einem Singleton.
  • Verwenden Sie den Datenbank-Builder von Room, um die Datenbank nur zu erstellen, wenn sie nicht vorhanden ist. Andernfalls wird die vorhandene Datenbank zurückgegeben.

Schritt 1: Datenbank erstellen

  1. Öffnen Sie im Paket database die Datei SleepDatabase.kt.
  2. Erstellen Sie in der Datei eine abstract-Klasse mit dem Namen SleepDatabase, die RoomDatabase.

    erweitert. Annotieren Sie die Klasse mit @Database.
@Database()
abstract class SleepDatabase : RoomDatabase() {}
  1. Es wird ein Fehler für fehlende Entitäten und Versionsparameter angezeigt. Für die Annotation @Database sind mehrere Argumente erforderlich, damit Room die Datenbank erstellen kann.
  • Geben Sie SleepNight als einziges Element in der Liste von entities an.
  • Legen Sie version als 1fest.  Wenn Sie das Schema ändern, müssen Sie die Versionsnummer erhöhen.
  • Legen Sie exportSchema auf false fest, damit keine Sicherungen des Schemaversionsverlaufs erstellt werden.
entities = [SleepNight::class], version = 1, exportSchema = false
  1. Die Datenbank muss das DAO kennen. Deklarieren Sie im Hauptteil der Klasse einen abstrakten Wert, der den SleepDatabaseDao zurückgibt. Sie können mehrere DAOs haben.
abstract val sleepDatabaseDao: SleepDatabaseDao
  1. Definieren Sie darunter ein companion-Objekt. Über das Companion-Objekt können Clients auf die Methoden zum Erstellen oder Abrufen der Datenbank zugreifen, ohne die Klasse instanziieren zu müssen. Da diese Klasse nur dazu dient, eine Datenbank bereitzustellen, gibt es keinen Grund, sie zu instanziieren.
 companion object {}
  1. Deklarieren Sie im companion-Objekt eine private, nullable Variable INSTANCE für die Datenbank und initialisieren Sie sie mit null. Die Variable INSTANCE enthält eine Referenz zur Datenbank, sobald eine erstellt wurde. So müssen Sie nicht immer wieder Verbindungen zur Datenbank öffnen, was ressourcenintensiv ist.

Kommentieren Sie INSTANCE mit @Volatile. Der Wert einer flüchtigen Variablen wird nie im Cache gespeichert und alle Schreib- und Lesevorgänge erfolgen im Hauptspeicher. So wird sichergestellt, dass der Wert von INSTANCE immer aktuell und für alle Ausführungs-Threads gleich ist. Das bedeutet, dass Änderungen, die von einem Thread an INSTANCE vorgenommen werden, sofort für alle anderen Threads sichtbar sind. So wird verhindert, dass beispielsweise zwei Threads dasselbe Element in einem Cache aktualisieren, was zu Problemen führen würde.

@Volatile
private var INSTANCE: SleepDatabase? = null
  1. Definieren Sie unter INSTANCE, aber noch innerhalb des companion-Objekts, eine getInstance()-Methode mit einem Context-Parameter, den der Datenbank-Builder benötigt. Gibt den Typ SleepDatabase zurück. Es wird ein Fehler angezeigt, da getInstance() noch nichts zurückgibt.
fun getInstance(context: Context): SleepDatabase {}
  1. Fügen Sie in getInstance() einen synchronized{}-Block ein. Übergeben Sie this, damit Sie auf den Kontext zugreifen können.

    Mehrere Threads können gleichzeitig eine Datenbankinstanz anfordern, was zu zwei Datenbanken anstelle von einer führt. Dieses Problem tritt in dieser Beispiel-App wahrscheinlich nicht auf, ist aber bei komplexeren Apps möglich. Wenn Sie den Code zum Abrufen der Datenbank in synchronized einschließen, kann jeweils nur ein Ausführungsthread in diesen Codeblock eintreten. So wird sichergestellt, dass die Datenbank nur einmal initialisiert wird.
synchronized(this) {}
  1. Kopieren Sie im synchronisierten Block den aktuellen Wert von INSTANCE in eine lokale Variable instance. So können Sie Smart Cast nutzen, das nur für lokale Variablen verfügbar ist.
var instance = INSTANCE
  1. Im synchronized-Block: return instance am Ende des synchronized-Blocks. Ignorieren Sie den Fehler wegen des nicht übereinstimmenden Rückgabetyps. Sie werden nach Abschluss der Aufgabe niemals „null“ zurückgeben.
return instance
  1. Fügen Sie über der return-Anweisung eine if-Anweisung hinzu, um zu prüfen, ob instance null ist, d. h. ob noch keine Datenbank vorhanden ist.
if (instance == null) {}
  1. Wenn instance gleich null ist, verwenden Sie den Datenbank-Builder, um eine Datenbank zu erhalten. Rufen Sie im Hauptteil der if-Anweisung Room.databaseBuilder auf und geben Sie den übergebenen Kontext, die Datenbankklasse und einen Namen für die Datenbank an, sleep_history_database. Um den Fehler zu beheben, müssen Sie in den folgenden Schritten eine Migrationsstrategie und build() hinzufügen.
instance = Room.databaseBuilder(
                           context.applicationContext,
                           SleepDatabase::class.java,
                           "sleep_history_database")
  1. Fügen Sie dem Builder die erforderliche Migrationsstrategie hinzu. Verwenden Sie .fallbackToDestructiveMigration().

    Normalerweise müssten Sie ein Migrationsobjekt mit einer Migrationsstrategie für den Fall bereitstellen, dass sich das Schema ändert. Ein Migrationsobjekt ist ein Objekt, das definiert, wie alle Zeilen mit dem alten Schema in Zeilen mit dem neuen Schema konvertiert werden, damit keine Daten verloren gehen. Die Migration wird in diesem Codelab nicht behandelt. Eine einfache Lösung besteht darin, die Datenbank zu zerstören und neu zu erstellen. Dabei gehen die Daten jedoch verloren.
.fallbackToDestructiveMigration()
  1. Rufen Sie abschließend .build() auf.
.build()
  1. Weisen Sie INSTANCE = instance als letzten Schritt in der if-Anweisung zu.
INSTANCE = instance
  1. Ihr endgültiger Code sollte so aussehen:
@Database(entities = [SleepNight::class], version = 1, exportSchema = false)
abstract class SleepDatabase : RoomDatabase() {

   abstract val sleepDatabaseDao: SleepDatabaseDao

   companion object {

       @Volatile
       private var INSTANCE: SleepDatabase? = null

       fun getInstance(context: Context): SleepDatabase {
           synchronized(this) {
               var instance = INSTANCE

               if (instance == null) {
                   instance = Room.databaseBuilder(
                           context.applicationContext,
                           SleepDatabase::class.java,
                           "sleep_history_database"
                   )
                           .fallbackToDestructiveMigration()
                           .build()
                   INSTANCE = instance
               }
               return instance
           }
       }
   }
}
  1. Code erstellen und ausführen

Sie haben jetzt alle Bausteine, um mit Ihrer Room-Datenbank zu arbeiten. Dieser Code wird kompiliert und ausgeführt, aber Sie können nicht feststellen, ob er tatsächlich funktioniert. Jetzt ist also ein guter Zeitpunkt, einige grundlegende Tests hinzuzufügen.

Schritt 2: SleepDatabase testen

In diesem Schritt führen Sie die bereitgestellten Tests aus, um zu prüfen, ob Ihre Datenbank funktioniert. So können Sie sicher sein, dass die Datenbank funktioniert, bevor Sie sie weiter ausbauen. Die bereitgestellten Tests sind einfach. Bei einer Produktions-App würden Sie alle Funktionen und Abfragen in allen DAOs ausführen.

Die Starter-App enthält den Ordner androidTest. Dieser androidTest-Ordner enthält Unittests, die Android-Instrumentierung umfassen. Das bedeutet, dass für die Tests das Android-Framework erforderlich ist. Sie müssen die Tests also auf einem physischen oder virtuellen Gerät ausführen. Natürlich können Sie auch reine Unit-Tests erstellen und ausführen, die das Android-Framework nicht einbeziehen.

  1. Öffnen Sie in Android Studio im Ordner androidTest die Datei SleepDatabaseTest.
  2. Wenn Sie den Kommentar aus dem Code entfernen möchten, wählen Sie den gesamten kommentierten Code aus und drücken Sie die Tastenkombination Cmd+/ oder Control+/.
  3. Sehen Sie sich die Datei an.

Hier ist ein kurzer Überblick über den Testcode, da es sich um einen weiteren Codeabschnitt handelt, den Sie wiederverwenden können:

  • SleepDabaseTest ist eine Testklasse.
  • Mit der Annotation @RunWith wird der Test-Runner identifiziert. Das ist das Programm, das die Tests einrichtet und ausführt.
  • Während der Einrichtung wird die mit @Before annotierte Funktion ausgeführt und es wird eine speicherinterne SleepDatabase mit der SleepDatabaseDao erstellt. „Im Arbeitsspeicher“ bedeutet, dass diese Datenbank nicht im Dateisystem gespeichert wird und nach dem Ausführen der Tests gelöscht wird.
  • Beim Erstellen der In-Memory-Datenbank wird im Code auch eine weitere testspezifische Methode aufgerufen: allowMainThreadQueries. Standardmäßig wird ein Fehler ausgegeben, wenn Sie versuchen, Abfragen im Hauptthread auszuführen. Mit dieser Methode können Sie Tests im Hauptthread ausführen. Das sollten Sie jedoch nur während des Testens tun.
  • In einer Testmethode, die mit @Test annotiert ist, erstellen, fügen Sie ein SleepNight ein und rufen es ab. Anschließend prüfen Sie, ob die beiden Objekte identisch sind. Wenn etwas schiefgeht, lösen Sie eine Ausnahme aus. In einem echten Test hätten Sie mehrere @Test -Methoden.
  • Nach Abschluss der Tests wird die mit @After annotierte Funktion ausgeführt, um die Datenbank zu schließen.
  1. Klicken Sie im Bereich Project (Projekt) mit der rechten Maustaste auf die Testdatei und wählen Sie Run 'SleepDatabaseTest' (SleepDatabaseTest ausführen) aus.
  2. Prüfe nach dem Ausführen der Tests im Bereich SleepDatabaseTest, ob alle Tests bestanden wurden.

Da alle Tests bestanden wurden, wissen Sie jetzt Folgendes:

  • Die Datenbank wird korrekt erstellt.
  • Sie können einen SleepNight in die Datenbank einfügen.
  • Sie können die SleepNight zurückerhalten.
  • SleepNight hat den richtigen Wert für die Qualität.

Android Studio-Projekt: TrackMySleepQualityRoomAndTesting

Beim Testen einer Datenbank müssen Sie alle im DAO definierten Methoden ausführen. Um die Tests abzuschließen , fügen Sie Tests hinzu und führen Sie sie aus, um die anderen DAO-Methoden zu testen.

  • Definieren Sie Ihre Tabellen als Datenklassen, die mit @Entity annotiert sind. Definieren Sie Attribute, die mit @ColumnInfo gekennzeichnet sind, als Spalten in den Tabellen.
  • Definieren Sie ein Data Access Object (DAO) als Schnittstelle mit der Annotation @Dao. Das DAO bildet Kotlin-Funktionen auf Datenbankabfragen ab.
  • Verwenden Sie Annotationen, um die Funktionen @Insert, @Delete und @Update zu definieren.
  • Verwenden Sie die Annotation @Query mit einem SQLite-Abfragestring als Parameter für alle anderen Abfragen.
  • Erstellen Sie eine abstrakte Klasse mit einer getInstance()-Funktion, die eine Datenbank zurückgibt.
  • Verwenden Sie instrumentierte Tests, um zu prüfen, ob Ihre Datenbank und Ihr DAO wie erwartet funktionieren. Sie können die bereitgestellten Tests als Vorlage verwenden.

Udacity-Kurs:

Android-Entwicklerdokumentation:

Weitere Dokumentation und Artikel:

In diesem Abschnitt werden mögliche Hausaufgaben für Schüler und Studenten aufgeführt, die dieses Codelab im Rahmen eines von einem Kursleiter geleiteten Kurses durcharbeiten. Es liegt in der Verantwortung des Kursleiters, Folgendes zu tun:

  • Weisen Sie bei Bedarf Aufgaben zu.
  • Teilen Sie den Schülern/Studenten mit, wie sie Hausaufgaben abgeben können.
  • Benoten Sie die Hausaufgaben.

Lehrkräfte können diese Vorschläge nach Belieben nutzen und auch andere Hausaufgaben zuweisen, die sie für angemessen halten.

Wenn Sie dieses Codelab selbst durcharbeiten, können Sie mit diesen Hausaufgaben Ihr Wissen testen.

Beantworten Sie diese Fragen

Frage 1

Wie geben Sie an, dass eine Klasse eine Entität darstellt, die in einer Room-Datenbank gespeichert werden soll?

  • Lassen Sie die Klasse DatabaseEntity erweitern.
  • Kommentieren Sie die Klasse mit @Entity.
  • Kommentieren Sie die Klasse mit @Database.
  • Lassen Sie die Klasse RoomEntity erweitern und annotieren Sie die Klasse auch mit @Room.

Frage 2

Das DAO (Data Access Object) ist eine Schnittstelle, die von Room verwendet wird, um Kotlin-Funktionen Datenbankabfragen zuzuordnen.

Wie geben Sie an, dass eine Schnittstelle ein DAO für eine Room-Datenbank darstellt?

  • Lassen Sie die Schnittstelle RoomDAO erweitern.
  • Lassen Sie die Schnittstelle EntityDao erweitern und implementieren Sie dann die Methode DaoConnection().
  • Kommentieren Sie die Schnittstelle mit @Dao.
  • Kommentieren Sie die Schnittstelle mit @RoomConnection.

Frage 3

Welche der folgenden Aussagen zur Room-Datenbank sind richtig? Wählen Sie alle zutreffenden Antworten aus.

  • Sie können Tabellen für eine Room-Datenbank als annotierte Datenklassen definieren.
  • Wenn Sie LiveData aus einer Abfrage zurückgeben, wird Room für Sie aktualisiert, wenn sich LiveData ändert.LiveData
  • Jede Room-Datenbank muss genau ein DAO haben.
  • Wenn Sie eine Klasse als Room-Datenbank kennzeichnen möchten, machen Sie sie zu einer abgeleiteten Klasse von RoomDatabase und versehen Sie sie mit der Annotation @Database.

Frage 4

Welche der folgenden Anmerkungen können Sie in Ihrer @Dao-Benutzeroberfläche verwenden? Wählen Sie alle zutreffenden Antworten aus.

  • @Get
  • @Update
  • @Insert
  • @Query

Frage 5

Wie können Sie prüfen, ob Ihre Datenbank funktioniert? Wähle alle zutreffenden Antworten aus.

  • Instrumentierte Tests schreiben
  • Schreiben Sie die App weiter und führen Sie sie aus, bis die Daten angezeigt werden.
  • Ersetzen Sie die Aufrufe der Methoden in der DAO-Schnittstelle durch Aufrufe der entsprechenden Methoden in der Klasse Entity.
  • Führen Sie die Funktion verifyDatabase() aus, die von der Bibliothek Room bereitgestellt wird.

Nächste Lektion: 6.2 Coroutines und Room

Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage für Android Kotlin Fundamentals-Codelabs.