
Room est une bibliothĂšque qui fait partie des composants architecturaux d' Android. Il facilite l' SQLiteDatabase
objets SQLiteDatabase
dans l'application, rĂ©duisant la quantitĂ© de code standard et vĂ©rifiant les requĂȘtes SQL au moment de la compilation.
Vous avez déjà un projet Android qui utilise SQLite pour stocker des données? Si tel est le cas, vous pouvez le migrer vers Room. Voyons comment prendre un projet existant et le refactoriser pour utiliser Room en 7 étapes simples.
TL; DR: mettez à jour les dépendances de gradle, créez vos entités, DAO et base de données, remplacez les appels SQLiteDatabase
appels de méthode DAO, testez tout ce que vous avez créé ou modifié et supprimez les classes inutilisées. C'est tout!
Dans notre exemple d'application pour la migration, nous travaillons avec des objets de type User
. Nous avons utilisé des versions de produit pour démontrer diverses implémentations au niveau des données:
- sqlite - utilise
SQLiteOpenHelper
et les interfaces SQLite traditionnelles. - room - remplace l'implémentation par Room et permet la migration.
Chaque option utilise la mĂȘme couche d'interface utilisateur, qui fonctionne avec la classe UserRepository
grĂące au modĂšle MVP.
Dans la variante sqlite, vous verrez beaucoup de code qui est souvent dupliqué et utilise la base de données dans les LocalUserDataSource
UsersDbHelper
et UsersDbHelper
. Les requĂȘtes sont ContentValues
aide de ContentValues
et les données renvoyées par les objets Cursor
sont lues colonne par colonne. Tout ce code contribue Ă l'apparition d'erreurs implicites. Par exemple, vous pouvez ignorer l'ajout d'une colonne Ă une requĂȘte ou assembler de maniĂšre incorrecte un objet Ă partir d'une base de donnĂ©es.
Voyons comment Room améliore notre code. Au départ, nous copions simplement les classes de la variante sqlite et les modifions progressivement.
Ătape 1. Mise Ă jour des dĂ©pendances Gradle
Les dépendances pour Room sont disponibles via le nouveau référentiel Google Maven. Ajoutez-le simplement à la liste des référentiels dans votre fichier build.gradle
principal:
allprojects { repositories { google() jcenter() } }
DĂ©finissez la version de la bibliothĂšque Room dans le mĂȘme fichier. Bien qu'il soit dans la version alpha, mais restez Ă l'Ă©coute pour les mises Ă jour de version sur les pages de dĂ©veloppeur :
ext { roomVersion = '2.1.0-alpha03' }
Dans votre app/build.gradle
ajoutez des dépendances pour Room:
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â }
Pour migrer vers Room, nous devons augmenter la version de la base de données et pour enregistrer les données utilisateur, nous devons implémenter la classe Migration . Pour tester la migration , nous devons exporter le schéma. Pour ce faire, ajoutez le code suivant au 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()) } ...
Ătape 2. Mise Ă jour des classes de modĂšle en entitĂ©s
Room crée une table pour chaque classe marquée @Entity . Les champs de la classe correspondent aux colonnes du tableau. Par conséquent, les classes d'entités sont, en rÚgle générale, de petites classes de modÚles qui ne contiennent aucune logique. Notre classe User
représente un modÚle de données dans une base de données. Donc, mettons-le à jour pour indiquer à Room qu'il doit créer une table basée sur cette classe:
@Entity
la classe avec @Entity
et utilisez la propriété tableName
pour définir le nom de la table.- Définissez la clé primaire en ajoutant l'annotation
@PrimaryKey
aux champs corrects - dans notre cas, il s'agit de l'ID utilisateur. - Spécifiez le nom de colonne pour les champs de classe à l'aide de l'annotation
@ColumnInfo(name = "column_name")
. Vous pouvez ignorer cette Ă©tape si vos champs sont dĂ©jĂ nommĂ©s car la colonne doit ĂȘtre nommĂ©e. - S'il y a plusieurs constructeurs dans la classe, ajoutez l'annotation
@Ignore
pour indiquer Ă Room lequel utiliser et lequel ne pas le faire.
@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; } ... }
Remarque: pour une migration en douceur, prĂȘtez une attention particuliĂšre aux noms des tables et des colonnes dans l'implĂ©mentation d'origine et assurez-vous de les dĂ©finir correctement dans les @ColumnInfo
@Entity
et @ColumnInfo
.
Ătape 3. CrĂ©ation d'objets d'accĂšs aux donnĂ©es (DAO)
Les DAO sont responsables de la dĂ©finition des mĂ©thodes d'accĂšs Ă la base de donnĂ©es. Dans l'implĂ©mentation initiale de notre projet SQLite, toutes les requĂȘtes de base de donnĂ©es ont Ă©tĂ© exĂ©cutĂ©es dans la classe LocalUserDataSource
, oĂč nous avons travaillĂ© avec des objets Cursor
. Dans Room, nous n'avons pas besoin de tout le code associé au curseur, et nous pouvons simplement définir nos demandes à l'aide d'annotations dans la classe UserDao
.
Par exemple, lors de l'interrogation de tous les utilisateurs de la base de données, Room fait tout le «travail acharné», et nous avons juste besoin d'écrire:
@Query(âSELECT * FROM Usersâ) List<User> getUsers();
Ătape 4. CrĂ©ation d'une base de donnĂ©es
Nous avons déjà défini notre table Users
et ses requĂȘtes correspondantes, mais nous n'avons pas encore créé de base de donnĂ©es qui rĂ©unira tous ces composants de Room. Pour ce faire, nous devons dĂ©finir une classe abstraite qui Ă©tend RoomDatabase
. Cette classe est marquée @Database
, qui rĂ©pertorie les objets contenus dans la base de donnĂ©es et le DAO qui y accĂšdent. La version de la base de donnĂ©es devrait ĂȘtre augmentĂ©e de 1 par rapport Ă la valeur d'origine, donc dans notre cas, elle sera de 2.
@Database(entities = {User.class}, version = 2) @TypeConverters(DateConverter.class) public abstract class UsersDatabase extends RoomDatabase { private static UsersDatabase INSTANCE; public abstract UserDao userDao();
Puisque nous voulons enregistrer les données utilisateur, nous devons implémenter la classe Migration
indiquant à Room ce qu'il doit faire lors du passage de la version 1 à 2. Dans notre cas, le schéma de la base de données n'ayant pas changé, nous fournissons simplement une implémentation vide:
static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) {
Créez un objet de base de données dans la classe UsersDatabase
en définissant le nom de la base de données et la migration:
database = Room.databaseBuilder(context.getApplicationContext(), UsersDatabase.class, "Sample.db") .addMigrations(MIGRATION_1_2) .build();
Pour en savoir plus sur la façon d'implémenter la migration de base de données et leur fonctionnement sous le capot, consultez cet article .
Ătape 5. Mise Ă jour du rĂ©fĂ©rentiel pour utiliser Room
Nous avons créé notre base de donnĂ©es, notre table d'utilisateurs et nos requĂȘtes, il est donc temps de les utiliser. Ă ce stade, nous mettrons Ă jour la classe UserDao
pour utiliser les méthodes UserDao
. Pour ce faire, nous mettons d'abord Ă jour le constructeur: supprimez le Context
et ajoutez UserDao
. Bien sûr, toute classe qui crée une instance de LocalUserDataSource
doit Ă©galement ĂȘtre mise Ă jour.
Ensuite, nous mettrons à jour les méthodes LocalUserDataSource
qui effectuent des requĂȘtes en appelant les mĂ©thodes UserDao
. Par exemple, une méthode qui demande à tous les utilisateurs ressemble maintenant à ceci:
public List<User> getUsers() { return mUserDao.getUsers(); }
Et il est maintenant temps de lancer ce que nous avons fait.
L'une des meilleures fonctionnalités de Room est que si vous effectuez des opérations de base de données sur le thread principal, votre application se bloque avec le message d'erreur suivant:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
Un moyen fiable de déplacer les opérations d'E / S du thread principal consiste à créer un nouveau Runnable
qui crĂ©era un nouveau thread pour chaque requĂȘte de base de donnĂ©es. Comme nous utilisons dĂ©jĂ cette approche dans la variante sqlite, aucune modification n'a Ă©tĂ© requise.
Ătape 6. Test sur l'appareil
Nous avons créé de nouvelles classes - UserDao
et UsersDatabase
et changé notre LocalUserDataSource
pour utiliser la base de données Room. Maintenant, nous devons les tester.
Test de UserDao
Pour tester UserDao
, nous devons créer une classe de test AndroidJUnit4
. La fonctionnalité étonnante de Room est la possibilité de créer une base de données en mémoire. Cela élimine le besoin de nettoyage aprÚs chaque test.
@Before public void initDb() throws Exception { mDatabase = Room.inMemoryDatabaseBuilder( InstrumentationRegistry.getContext(), UsersDatabase.class) .build(); }
Nous devons également nous assurer que nous fermons la connexion à la base de données aprÚs chaque test.
@After public void closeDb() throws Exception { mDatabase.close(); }
Par exemple, pour tester la connexion d'un utilisateur, nous ajoutons un utilisateur, puis vérifions si nous pouvons obtenir cet utilisateur de la base de données.
@Test public void insertAndGetUser() {
Test de l'utilisation de UserDao dans LocalUserDataSource
Il est facile de s'assurer que LocalUserDataSource
fonctionne toujours correctement, car nous avons déjà des tests qui décrivent le comportement de cette classe. Tout ce que nous devons faire est de créer une base de données en mémoire, d'en extraire un objet UserDao
et de l'utiliser comme paramĂštre pour le constructeur LocalUserDataSource
.
@Before public void initDb() throws Exception { mDatabase = Room.inMemoryDatabaseBuilder( InstrumentationRegistry.getContext(), UsersDatabase.class) .build(); mDataSource = new LocalUserDataSource(mDatabase.userDao()); }
Encore une fois, nous devons nous assurer que nous fermons la base de données aprÚs chaque test.
Test de migration de base de données
Vous pouvez en savoir plus sur la façon d'implémenter des tests de migration de base de données, ainsi que sur le fonctionnement de MigrationTestHelper
, dans cet article .
Vous pouvez également voir le code à partir d'un exemple d' application de migration plus détaillé.
Ătape 7. Suppression de tout ce qui n'est pas nĂ©cessaire
Supprimez toutes les classes et lignes de code inutilisées qui sont désormais remplacées par la fonctionnalité Room. Dans notre projet, nous avons juste besoin de supprimer la classe UsersDbHelper
, qui a étendu la classe SQLiteOpenHelper
.
Si vous avez une base de données plus grande et plus complexe et que vous souhaitez passer progressivement à la salle, nous vous recommandons ce poste .
Maintenant, la quantité de code standard sujet aux erreurs a diminué, les demandes sont vérifiées au moment de la compilation et tout est testé. En 7 étapes simples, nous avons pu migrer notre application existante vers Room. Vous pouvez voir un exemple d'application ici .