7 Schritte zur Verwendung von Room. Exemplarische Vorgehensweise zum Migrieren Ihrer Anwendung nach Room

7 Schritte zur Verwendung von Room. Exemplarische Vorgehensweise zum Migrieren Ihrer Anwendung nach Room


Room ist eine Bibliothek, die Teil der Architekturkomponenten von Android ist. Es erleichtert die Arbeit mit SQLiteDatabase Objekten in der Anwendung, reduziert die Menge an Standardcode und überprüft SQL-Abfragen beim Kompilieren.


Haben Sie bereits ein Android-Projekt, das SQLite zum Speichern von Daten verwendet? Wenn ja, können Sie es in Room migrieren. Lassen Sie uns sehen, wie Sie ein vorhandenes Projekt in 7 einfachen Schritten umgestalten, um Room zu verwenden.


TL; DR: Aktualisieren Sie die Gradle-Abhängigkeiten, erstellen Sie Ihre Entitäten, DAO und Datenbank, ersetzen Sie SQLiteDatabase Aufrufe durch DAO-Methodenaufrufe, testen Sie alles, was Sie erstellt oder geändert haben, und löschen Sie nicht verwendete Klassen. Das ist alles!

In unserer Beispielanwendung für die Migration arbeiten wir mit Objekten vom Typ User . Wir haben Produktvarianten verwendet, um verschiedene Implementierungen auf Datenebene zu demonstrieren:


  1. sqlite - verwendet SQLiteOpenHelper und herkömmliche SQLite-Schnittstellen.
  2. room - ersetzt die Implementierung durch Room und bietet Migration.

Jede Option verwendet dieselbe Benutzeroberflächenebene, die dank des MVP-Musters mit der UserRepository Klasse funktioniert.


In der SQLite-Variante sehen Sie viel Code, der häufig dupliziert wird und die Datenbank in den LocalUserDataSource UsersDbHelper und LocalUserDataSource . Abfragen werden mit ContentValues , und die von ContentValues Daten werden spaltenweise gelesen. All dieser Code trägt zum Auftreten impliziter Fehler bei. Sie können beispielsweise das Hinzufügen einer Spalte zu einer Abfrage überspringen oder ein Objekt aus einer Datenbank falsch zusammenstellen.


Mal sehen, wie Room unseren Code verbessert. Zunächst kopieren wir einfach die Klassen aus der SQLite-Variante und ändern sie schrittweise.


Schritt 1. Aktualisieren der Gradle-Abhängigkeiten


Abhängigkeiten für Room sind über das neue Google Maven-Repository verfügbar. build.gradle Sie es einfach der Liste der Repositorys in Ihrer Hauptdatei build.gradle :


 allprojects { repositories { google() jcenter() } } 

Definieren Sie die Version der Raumbibliothek in derselben Datei. Während es in der Alpha-Version ist, aber bleiben Sie auf dem Laufenden für Versionsaktualisierungen auf den Entwicklerseiten :


 ext { roomVersion = '2.1.0-alpha03' } 

app/build.gradle Sie in app/build.gradle Abhängigkeiten für Room hinzu:


 dependencies { implementation “android.arch.persistence.room:runtime:$rootProject.roomVersion” annotationProcessor “android.arch.persistence.room:compiler:$rootProject.roomVersion” androidTestImplementation “android.arch.persistence.room:testing:$rootProject.roomVersion” } 

Um nach Room zu migrieren, müssen wir die Version der Datenbank erhöhen und um Benutzerdaten zu speichern, müssen wir die Migrationsklasse implementieren. Um die Migration zu testen , müssen wir das Schema exportieren. app/build.gradle den folgenden Code app/build.gradle :


 android { defaultConfig { ... // used by Room, to test migrations javaCompileOptions { annotationProcessorOptions { arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] } } } // used by Room, to test migrations sourceSets { androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } ... 

Schritt 2. Aktualisieren von Modellklassen auf Entitäten


Room erstellt für jede Klasse mit der Bezeichnung @Entity eine Tabelle. Die Felder in der Klasse entsprechen den Spalten in der Tabelle. Folglich sind Entitätsklassen in der Regel kleine Klassen von Modellen, die keine Logik enthalten. Unsere User repräsentiert ein Modell für Daten in einer Datenbank. Aktualisieren wir es also, um Room mitzuteilen, dass eine Tabelle basierend auf dieser Klasse erstellt werden soll:


  • Kommentieren Sie die Klasse mit @Entity und legen Sie den Tabellennamen mit der Eigenschaft tableName .
  • @PrimaryKey Sie den Primärschlüssel fest, indem Sie die Annotation @PrimaryKey zu den richtigen Feldern hinzufügen. In unserem Fall ist dies die Benutzer-ID.
  • Geben Sie den Spaltennamen für die Klassenfelder mithilfe der Annotation @ColumnInfo(name = "column_name") . Sie können diesen Schritt überspringen, wenn Ihre Felder bereits so benannt sind, wie die Spalte benannt werden soll.
  • Wenn die Klasse mehrere Konstruktoren enthält, fügen Sie die Annotation @Ignore , um Room mitzuteilen, welche verwendet werden sollen und welche nicht.

 @Entity(tableName = "users") public class User { @PrimaryKey @ColumnInfo(name = "userid") private String mId; @ColumnInfo(name = "username") private String mUserName; @ColumnInfo(name = "last_update") private Date mDate; @Ignore public User(String userName) { mId = UUID.randomUUID().toString(); mUserName = userName; mDate = new Date(System.currentTimeMillis()); } public User(String id, String userName, Date date) { this.mId = id; this.mUserName = userName; this.mDate = date; } ... } 

Hinweis: @Entity Sie für eine reibungslose Migration genau auf die Namen der Tabellen und Spalten in der ursprünglichen Implementierung und stellen Sie sicher, dass Sie sie in den @ColumnInfo @Entity und @ColumnInfo korrekt @Entity @ColumnInfo .


Schritt 3. Erstellen von Datenzugriffsobjekten (DAO)


DAOs sind für die Definition von Datenbankzugriffsmethoden verantwortlich. Bei der ersten Implementierung unseres SQLite-Projekts wurden alle Datenbankabfragen in der LocalUserDataSource Klasse ausgeführt, in der wir mit LocalUserDataSource gearbeitet haben. In Room benötigen wir nicht den gesamten Code, der dem Cursor zugeordnet ist, und wir können unsere Anforderungen einfach mithilfe von Anmerkungen in der UserDao Klasse definieren.


Wenn Sie beispielsweise alle Benutzer aus der Datenbank abfragen, erledigt Room die gesamte „harte Arbeit“, und wir müssen nur schreiben:


 @Query(“SELECT * FROM Users”) List<User> getUsers(); 

Schritt 4. Erstellen einer Datenbank


Wir haben unsere Users und die entsprechenden Abfragen bereits definiert, aber noch keine Datenbank erstellt, die alle diese Komponenten von Room vereint. Dazu müssen wir eine abstrakte Klasse definieren, die RoomDatabase . Diese Klasse ist mit @Database gekennzeichnet und listet die in der Datenbank enthaltenen Objekte und das DAO auf, die auf sie zugreifen. Die Datenbankversion sollte im Vergleich zum ursprünglichen Wert um 1 erhöht werden, in unserem Fall also um 2.


 @Database(entities = {User.class}, version = 2) @TypeConverters(DateConverter.class) public abstract class UsersDatabase extends RoomDatabase { private static UsersDatabase INSTANCE; public abstract UserDao userDao(); 

Da wir Benutzerdaten speichern möchten, müssen wir die Migration implementieren, die Room mitteilt, was beim Wechsel von Version 1 zu 2 zu tun ist. In unserem Fall stellen wir einfach eine leere Implementierung bereit, da sich das Datenbankschema nicht geändert hat:


 static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) { //     ,      . } }; 

Erstellen Sie ein Datenbankobjekt in der UsersDatabase Klasse, indem Sie den Datenbanknamen und die Migration definieren:


 database = Room.databaseBuilder(context.getApplicationContext(), UsersDatabase.class, "Sample.db") .addMigrations(MIGRATION_1_2) .build(); 

In diesem Beitrag erfahren Sie mehr darüber, wie Sie die Datenbankmigration implementieren und wie sie unter der Haube funktioniert.


Schritt 5. Aktualisieren des Repositorys zur Verwendung von Room


Wir haben unsere Datenbank, unsere Benutzertabelle und Abfragen erstellt. Jetzt ist es Zeit, sie zu verwenden. Zu diesem Zeitpunkt aktualisieren wir die LocalUserDataSource Klasse, um UserDao Methoden zu verwenden. Dazu aktualisieren wir zuerst den Konstruktor: Entfernen Sie den Context und fügen Sie UserDao . Natürlich muss auch jede Klasse aktualisiert werden, die eine Instanz von LocalUserDataSource erstellt.


Als Nächstes aktualisieren wir die LocalUserDataSource Methoden, die Abfragen durchführen, indem wir die LocalUserDataSource Methoden aufrufen. Eine Methode, die jetzt alle Benutzer anfordert, sieht jetzt folgendermaßen aus:


 public List<User> getUsers() { return mUserDao.getUsers(); } 

Und jetzt ist die Zeit zu starten, was wir getan haben.


Eine der besten Funktionen von Room ist, dass Ihre Anwendung mit der folgenden Fehlermeldung abstürzt, wenn Sie Datenbankoperationen für den Hauptthread ausführen.


 java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time. 

Eine zuverlässige Möglichkeit, E / A-Vorgänge aus dem Hauptthread zu verschieben, besteht darin, eine neue Runnable zu erstellen, die für jede Datenbankabfrage einen neuen Thread erstellt. Da wir diesen Ansatz bereits in der SQLite-Variante verwenden, waren keine Änderungen erforderlich.


Schritt 6. Testen auf dem Gerät


Wir haben neue Klassen erstellt - UserDao und UsersDatabase und unsere LocalUserDataSource geändert, um die Room-Datenbank zu verwenden. Jetzt müssen wir sie testen.


Testen von UserDao


Um UserDao zu testen, müssen wir eine AndroidJUnit4 . Das Atemberaubende an Room ist die Möglichkeit, eine Datenbank im Speicher zu erstellen. Dadurch muss nach jedem Test nicht mehr gereinigt werden.


 @Before public void initDb() throws Exception { mDatabase = Room.inMemoryDatabaseBuilder( InstrumentationRegistry.getContext(), UsersDatabase.class) .build(); } 

Wir müssen auch sicherstellen, dass wir die Datenbankverbindung nach jedem Test schließen.


 @After public void closeDb() throws Exception { mDatabase.close(); } 

Um beispielsweise die Anmeldung eines Benutzers zu testen, fügen wir einen Benutzer hinzu und prüfen dann, ob wir diesen Benutzer aus der Datenbank abrufen können.


 @Test public void insertAndGetUser() { //      mDatabase.userDao().insertUser(USER); //        List<User> users = mDatabase.userDao().getUsers(); assertThat(users.size(), is(1)); User dbUser = users.get(0); assertEquals(dbUser.getId(), USER.getId()); assertEquals(dbUser.getUserName(), USER.getUserName()); } 

Testen der Verwendung von UserDao in LocalUserDataSource


Es ist einfach sicherzustellen, dass LocalUserDataSource weiterhin ordnungsgemäß funktioniert, da wir bereits Tests haben, die das Verhalten dieser Klasse beschreiben. Wir müssen UserDao eine Datenbank im Speicher erstellen, ein UserDao Objekt daraus UserDao und es als Parameter für den LocalUserDataSource Konstruktor verwenden.


 @Before public void initDb() throws Exception { mDatabase = Room.inMemoryDatabaseBuilder( InstrumentationRegistry.getContext(), UsersDatabase.class) .build(); mDataSource = new LocalUserDataSource(mDatabase.userDao()); } 

Auch hier müssen wir sicherstellen, dass wir die Datenbank nach jedem Test schließen.


Testen der Datenbankmigration


In diesem Beitrag MigrationTestHelper Sie mehr darüber, wie Datenbankmigrationstests implementiert werden und wie MigrationTestHelper funktioniert.


Sie können den Code auch anhand eines detaillierteren Beispiels für eine Migrationsanwendung sehen .


Schritt 7. Entfernen Sie alle unnötigen


Entfernen Sie alle nicht verwendeten Klassen und Codezeilen, die jetzt durch die Room-Funktionalität ersetzt werden. In unserem Projekt müssen wir nur die UsersDbHelper Klasse entfernen, die die SQLiteOpenHelper Klasse erweitert hat.


Wenn Sie eine größere und komplexere Datenbank haben und schrittweise zu Room wechseln möchten, empfehlen wir diesen Beitrag .


Jetzt hat sich die Menge des fehleranfälligen Standardcodes verringert, Anforderungen werden beim Kompilieren überprüft und alles wird getestet. In 7 einfachen Schritten konnten wir unsere vorhandene Anwendung nach Room migrieren. Eine Beispielanwendung finden Sie hier .

Source: https://habr.com/ru/post/de441934/


All Articles