Challenge : Enregistrer des données dans SQLite DB via Room

Temps : 2h.
Difficulté : ***
Pre-requis : Android 101, Kotlin, Bonne pratique Gradle

Dans ce challenge, une base de données contenant des plages est implémentée avec la bibliothèque Room et une architecture légère.
En particulier, une BDD locale est nécessaire pour enregistrer les plages préférées de l'utilisateur.

Voici les étapes d'implémentation :
> Configurer le projet Android Studio
> Créer le modèle de données
> Créer, remplir la BDD Room
> Instancier la BDD
> Tester la BDD

> Configurer le projet Android Studio

Pour commencer il s'agit de configurer un projet Android Studio pour utiliser Room.
Plus précisément cela consiste à modifier les fichiers Gradle.

  1. Créer un nouveau projet Android, nommons-le RoomApp, avec Empty Activity comme modèle de projet.
  2. En haut du fichier Gradle Script du module app/, build.gradle(Module: RoomApp), ajouter le plugin kapt :
    
      plugins {
          id 'com.android.application'
          id 'kotlin-android'
          id 'kotlin-kapt'
      }
    
    
  3. Ajouter les dépendances Room dans la section dependencies de build.gradle(Module: RoomApp) :
    
    dependencies {
      implementation "androidx.appcompat:appcompat:$appCompatVersion"
        implementation "com.google.android.material:material:$materialVersion"
        implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion"
        testImplementation 'junit:junit:4.+'
        androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
        androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
    
        implementation("androidx.room:room-runtime:$roomVersion")
        annotationProcessor("androidx.room:room-compiler:$roomVersion")
    
        // To use Kotlin annotation processing tool (kapt)
        kapt("androidx.room:room-compiler:$roomVersion")
    
        // optional - Kotlin Extensions and Coroutines support for Room
        implementation("androidx.room:room-ktx:$roomVersion")
    }
    
    
  4. Dans le fichier Gradle Script du projet, build.gradle(Project: RoomApp), ajouter les variables de version dans la section buildscript :
    
    ext {
        kotlin_version = '1.5.31'
        appCompatVersion = '1.4.1'
        constraintLayoutVersion = '2.1.3'
        materialVersion = '1.5.0'
        roomVersion = '2.4.1'
        // testing
        junitVersion = '4.13.2'
        espressoVersion = '3.4.0'
        androidxJunitVersion = '1.1.3'
    }
    
    

> Créer le modèle de données

Le but est de créer une entité représentant un objet plage.
Une plage a :

  1. Ajouter la classe Kotlin nommée Beach, avec ces annotations Room :
  2. 
    @Entity(tableName = "beach_table")
    class Beach(
    
        @PrimaryKey @ColumnInfo(name = "name") val name: String,
        @ColumnInfo(name = "favorite") val favorite: Boolean
    
    )
    
    
  3. Ajouter les imports nécessaires via Android Studio :
  4. 
    import androidx.room.ColumnInfo
    import androidx.room.Entity
    import androidx.room.PrimaryKey
    
    
  5. Ajouter l'interface DAO, un fichier Kotlin nommé BeachDao :
  6. 
    @Dao
    interface BeachDao {
    
        @Query("SELECT * FROM beach_table ORDER BY name ASC")
        fun getBeach(): List<Beach>
    
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        suspend fun insert(beach: Beach)
    
        @Query("DELETE FROM beach_table")
        suspend fun deleteAll()
    }
    
    
  7. Ajouter les imports nécessaires via Android Studio :
  8. 
    import androidx.room.Dao
    import androidx.room.Insert
    import androidx.room.OnConflictStrategy
    import androidx.room.Query
    
    

> Créer, remplir la BDD Room

L'objectif est de créer la BDD Room et créer la classe pour instancier et remplir la BDD.

  1. Ajouter la classe BeachRoomDatabase :
  2. 
    @Database(entities = arrayOf(Beach::class), version = 1, exportSchema = false)
    abstract class BeachRoomDatabase : RoomDatabase() {
    
        abstract fun beachDao(): BeachDao
    
        companion object {
            // Singleton prevents multiple instances of database opening at the
            // same time.
            @Volatile
            private var INSTANCE: BeachRoomDatabase? = null
    
            fun getDatabase(context: Context, scope: CoroutineScope): BeachRoomDatabase {
                // if the INSTANCE is not null, then return it,
                // if it is, then create the database
                return INSTANCE ?: synchronized(this) {
                    val instance = Room.databaseBuilder(
                        context.applicationContext,
                        BeachRoomDatabase::class.java,
                        "beach_database"
                    )
                        .addCallback(BeachDatabaseCallback(scope))
                        .build()
                    INSTANCE = instance
                    // return instance
                    instance
                }
            }
        }
    }
    
    
  3. Ajouter la classe interne BeachDatabaseCallback dans BeachRoomDatabase :
  4. 
    private class BeachDatabaseCallback(private val scope: CoroutineScope) :
        RoomDatabase.Callback() {
        override fun onCreate(db: SupportSQLiteDatabase) {
            super.onCreate(db)
            INSTANCE?.let { database ->
                scope.launch {
                    var beachDao = database.beachDao()
                    beachDao.deleteAll()
                    // Populate the db:
                    var beach = Beach("Plage des Rochers, Cannes")
                    beachDao.insert(beach)
                }
            }
        }
    }
    
    

Note: le numéro de version doit être incrementé lorsque des changements sont fait sur la BDD.

> Instancier la BDD

Il s'agit d'instancier la BDD Room via la classe Application.

  1. Ajouter la classe Kotlin nommée BeachApplication :
  2. 
    class BeachApplication : Application() {
      val applicationScope = CoroutineScope(SupervisorJob())
      val database by lazy { BeachRoomDatabase.getDatabase(this, applicationScope) }
    }
    
    

> Tester la BDD

Dans cette ultime étape, des données sont manipulées et analisées via la console logcat (des plages sont ajoutées à la volée dans MainActivity).

  1. Ajouter les fonctions suivantes dans la classe Kotlin MainActivity :
  2. 
    //In the repository for component architecture with Flow stuff (dynamic display)
    fun showBeach() {
        (application as BeachApplication).applicationScope.launch {
            allBeach = (application as BeachApplication).database.beachDao().getBeach()
    
            for (i in 0..(allBeach.size - 1)) {
                Log.i(
                    MainActivity::class.java.simpleName,
                    "BEACH My favorite beach ${allBeach[i].name}"
                )
            }
        }
    }
    
    //In the ViewModel for component architecture
    fun addBeach() {
        val beach = Beach("Praia de Carcavelos, Lisbon")
        (application as BeachApplication).applicationScope.launch {
            insert(beach)
        }
    }
    
    // In the repository for component architecture
    suspend fun insert(beach: Beach) {
        (application as BeachApplication).database.beachDao()
            .insert(beach)
    }
    
    
  3. Ajouter les appels dans la méthode onCreate() de MainActivity :
  4. 
      showBeach()
      addBeach()
    
    
  5. Exécuter, observer les logs, enlever l'appel de addBeach(), exécuter.

Finallement, ce challenge est juste une introduction à la BDD avec Room.
Globalement, l'idée est d'utiliser un RecyclerView pour afficher les plages; ensuite d'ajouter un bouton étoile pour indiquer une préférence de plage (ou un FAB pour permettre à l'utilisateur d'ajouter des plages en BDD).

</> Solution du challenge

Obtenez les codes sources dans les Ressources supplémentaires de ce thème sur la BDD.