Room (multiplateforme Kotlin)

La bibliothèque de persistance de Room fournit une couche d'abstraction sur SQLite pour permettre un accès plus robuste à la base de données tout en exploitant toute la puissance de SQLite. Cette page porte sur l'utilisation de Room dans les projets de multiplateforme Kotlin (KMP). Pour en savoir plus sur l'utilisation de Room, consultez Enregistrer des données dans une base de données locale à l'aide de Room ou nos exemples officiels.

Configurer des dépendances

La version actuelle de Room compatible avec KMP est la version 2.7.0-alpha01 ou ultérieure.

Pour configurer Room dans votre projet KMP, ajoutez les dépendances des artefacts dans le fichier build.gradle.kts de votre module:

  • androidx.room:room-gradle-plugin : plug-in Gradle permettant de configurer des schémas Room
  • androidx.room:room-compiler : processeur KSP qui génère le code
  • androidx.room:room-runtime : partie de l'environnement d'exécution de la bibliothèque
  • androidx.sqlite:sqlite-bundled : bibliothèque SQLite intégrée (facultatif)

Vous devez également configurer le pilote SQLite de Room. Ces pilotes diffèrent en fonction de la plate-forme cible. Consultez la section Implémentations de pilotes pour obtenir une description des implémentations de pilotes disponibles.

Pour en savoir plus sur la configuration, consultez les articles suivants:

Définir les classes de base de données

Vous devez créer une classe de base de données annotée avec @Database, ainsi que des DAO et des entités dans l'ensemble de sources commun de votre module KMP partagé. Si vous placez ces classes dans des sources communes, elles pourront être partagées sur toutes les plates-formes cibles.

// shared/src/commonMain/kotlin/Database.kt

@Database(entities = [TodoEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
  abstract fun getDao(): TodoDao
}

@Dao
interface TodoDao {
  @Insert
  suspend fun insert(item: TodoEntity)

  @Query("SELECT count(*) FROM TodoEntity")
  suspend fun count(): Int

  @Query("SELECT * FROM TodoEntity")
  fun getAllAsFlow(): Flow<List<TodoEntity>>
}

@Entity
data class TodoEntity(
  @PrimaryKey(autoGenerate = true) val id: Long = 0,
  val title: String,
  val content: String
)

Notez que vous pouvez utiliser des déclarations réelles / prévues pour créer des implémentations de Room spécifiques à la plate-forme. Par exemple, vous pouvez ajouter un DAO spécifique à la plate-forme qui est défini dans le code commun à l'aide de expect, puis spécifier les définitions de actual avec des requêtes supplémentaires dans les ensembles de sources spécifiques à la plate-forme.

Créer le compilateur de base de données

Vous devez définir un compilateur de base de données pour instancier Room sur chaque plate-forme. Il s'agit de la seule partie de l'API qui doit figurer dans des ensembles de sources spécifiques à la plate-forme en raison des différences au niveau des API de système de fichiers. Par exemple, sous Android, l'emplacement de la base de données est généralement obtenu via l'API Context.getDatabasePath(), tandis que pour iOS, l'emplacement de la base de données est obtenu à l'aide de NSHomeDirectory.

Android

Pour créer l'instance de base de données, spécifiez un contexte ainsi que le chemin d'accès à la base de données. Vous n'avez pas besoin de spécifier une fabrique de base de données.

// shared/src/androidMain/kotlin/Database.kt

fun getDatabaseBuilder(ctx: Context): RoomDatabase.Builder<AppDatabase> {
    val appContext = ctx.applicationContext
    val dbFile = appContext.getDatabasePath("my_room.db")
    return Room.databaseBuilder<AppDatabase>(
        context = appContext,
        name = dbFile.absolutePath
    )
}

iOS

Pour créer l'instance de base de données, fournissez une fabrique de base de données ainsi que le chemin d'accès de la base de données. La fabrique de base de données est une fonction lambda qui appelle une fonction d'extension générée dont le nom est instantiateImpl avec un récepteur de type KClass<T>, où T est le type de la classe annotée @Database.

// shared/src/iosMain/kotlin/Database.kt

fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
    val dbFilePath = NSHomeDirectory() + "/my_room.db"
    return Room.databaseBuilder<AppDatabase>(
        name = dbFilePath,
        factory =  { AppDatabase::class.instantiateImpl() }
    )
}

JVM (ordinateur de bureau)

Pour créer l'instance de base de données, spécifiez uniquement le chemin d'accès de la base de données. Vous n'avez pas besoin de fournir une fabrique de base de données.

// shared/src/commonMain/kotlin/Database.kt

fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
    val dbFile = File(System.getProperty("java.io.tmpdir"), "my_room.db")
    return Room.databaseBuilder<AppDatabase>(
        name = dbFile.absolutePath,
    )
}

Instanciation de bases de données

Une fois que vous avez obtenu le RoomDatabase.Builder auprès de l'un des constructeurs spécifiques à la plate-forme, vous pouvez configurer le reste de la base de données Room dans du code commun, ainsi que l'instanciation réelle de la base de données.

// shared/src/commonMain/kotlin/Database.kt

fun getRoomDatabase(
    builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
    return builder
        .addMigrations(MIGRATIONS)
        .fallbackToDestructiveMigrationOnDowngrade()
        .setDriver(BundledSQLiteDriver())
        .setQueryCoroutineContext(Dispatchers.IO)
        .build()
}

Sélectionner un pilote SQLite

Les extraits de code précédents utilisent BundledSQLiteDriver. Il s'agit du pilote recommandé qui inclut SQLite compilé à partir de la source. Il fournit la version la plus cohérente et la plus récente de SQLite sur toutes les plates-formes. Si vous souhaitez utiliser SQLite fourni par le système d'exploitation, utilisez l'API setDriver dans des ensembles de sources spécifiques à la plate-forme qui spécifient un pilote spécifique à la plate-forme. Pour Android, vous pouvez utiliser AndroidSQLiteDriver, tandis que pour iOS, vous pouvez utiliser NativeSQLiteDriver. Pour utiliser NativeSQLiteDriver, vous devez fournir une option d'éditeur de liens afin que l'application iOS s'associe dynamiquement à SQLite du système.

// shared/build.gradle.kts

kotlin {
    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach { iosTarget ->
        iosTarget.binaries.framework {
            baseName = "TodoApp"
            isStatic = true
            // Required when using NativeSQLiteDriver
            linkerOpts.add("-lsqlite3")
        }
    }
}

Différences

Room a été initialement développé en tant que bibliothèque Android, puis a été migré vers KMP en mettant l'accent sur la compatibilité des API. La version KMP de Room diffère légèrement entre les plates-formes et la version spécifique à Android. Ces différences sont listées et décrites comme suit.

Bloquer les fonctions DAO

Lorsque vous utilisez Room pour KMP, toutes les fonctions DAO compilées pour des plates-formes non-Android doivent être des fonctions suspend, à l'exception des types renvoyés réactifs, tels que Flow.

// shared/src/commonMain/kotlin/MultiplatformDao.kt

@Dao
interface MultiplatformDao {
    // ERROR: Blocking function not valid for non-Android targets
    @Query("SELECT * FROM Entity")
    fun blockingQuery(): List<Entity>

    // OK
    @Query("SELECT * FROM Entity")
    suspend fun query(): List<Entity>

    // OK
    @Query("SELECT * FROM Entity")
    fun queryFlow(): Flow<List<Entity>>

    // ERROR: Blocking function not valid for non-Android targets
    @Transaction
    fun blockingTransaction() { // … }

    // OK
    @Transaction
    suspend fun transaction() { // … }
}

Room bénéficie de la bibliothèque asynchrone kotlinx.coroutines riche en fonctionnalités que Kotlin propose pour plusieurs plates-formes. Pour des fonctionnalités optimales, les fonctions suspend sont appliquées aux DAO compilés dans un projet KMP, à l'exception des DAO spécifiques à Android afin de maintenir la rétrocompatibilité avec le codebase existant.

Différences de fonctionnalités avec KMP

Cette section décrit les différences de fonctionnalités entre les versions KMP et Android de Room.

Fonctions DAO @RawQuery

Les fonctions annotées avec @RawQuery et compilées pour des plates-formes autres qu'Android génèrent une erreur. Nous prévoyons de prendre en charge @RawQuery dans une prochaine version de Room.

Rappel de requête

Les API suivantes pour la configuration des rappels de requête ne sont pas disponibles en commun et ne sont donc pas disponibles sur les plates-formes autres qu'Android.

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

Nous prévoyons d'ajouter la prise en charge du rappel de requête dans une prochaine version de Room.

L'API permettant de configurer un RoomDatabase avec un rappel de requête RoomDatabase.Builder.setQueryCallback et l'interface de rappel RoomDatabase.QueryCallback ne sont pas disponibles en commun et ne sont donc pas disponibles sur d'autres plates-formes qu'Android.

Fermeture automatique de la base de données

L'API permettant d'activer la fermeture automatique après un délai d'inactivité, RoomDatabase.Builder.setAutoCloseTimeout, n'est disponible que sur Android et n'est pas disponible sur les autres plates-formes.

Base de données préinstallée

Les API suivantes permettant de créer un RoomDatabase à l'aide d'une base de données existante (c'est-à-dire une base de données préinstallée) ne sont pas disponibles en commun et ne sont donc pas disponibles sur d'autres plates-formes qu'Android. Ces API sont les suivantes:

  • RoomDatabase.Builder.createFromAsset
  • RoomDatabase.Builder.createFromFile
  • RoomDatabase.Builder.createFromInputStream
  • RoomDatabase.PrepackagedDatabaseCallback

Nous prévoyons de prendre en charge les bases de données préinstallées dans une prochaine version de Room.

Invalidation de plusieurs instances

L'API permettant d'activer l'invalidation de plusieurs instances, RoomDatabase.Builder.enableMultiInstanceInvalidation, n'est disponible que sur Android et n'est pas disponible sur les plates-formes communes ou d'autres plates-formes.