La bibliothèque de persistance Room fournit une couche d'abstraction sur SQLite afin de permettre un accès plus fiable à la base de données, tout en exploitant toute la puissance de SQLite. Cette page explique comment utiliser Room dans les projets Kotlin Multiplateforme (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 les 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 les schémas Roomandroidx.room:room-compiler
: processeur KSP qui génère du codeandroidx.room:room-runtime
: partie d'exécution de la bibliothèqueandroidx.sqlite:sqlite-bundled
: (facultatif) Bibliothèque SQLite groupée
Vous devez également configurer le pilote SQLite de Room. Ces pilotes varient selon la plate-forme cible. Pour en savoir plus sur les implémentations de pilotes disponibles, consultez la section Implémentations de pilotes.
Pour en savoir plus sur la configuration, consultez les ressources suivantes:
- Définissez l'emplacement du schéma à l'aide du plug-in Room Gradle.
- KSP avec Kotlin Multiplatform
- Ajouter des dépendances d'exécution
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é. Placer ces classes dans des sources communes leur permettra d'être partagées sur toutes les plates-formes cibles.
Lorsque vous déclarez un objet expect
avec l'interface RoomDatabaseConstructor
, le compilateur Room génère les implémentations actual
. Android Studio peut générer un avertissement "Expected object 'AppDatabaseConstructor' has no actual declaration in
module"
. Vous pouvez le supprimer avec @Suppress("NO_ACTUAL_FOR_EXPECT")
.
// shared/src/commonMain/kotlin/Database.kt
@Database(entities = [TodoEntity::class], version = 1)
@ConstructedBy(AppDatabaseConstructor::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun getDao(): TodoDao
}
// The Room compiler generates the `actual` implementations.
@Suppress("NO_ACTUAL_FOR_EXPECT")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> {
override fun initialize(): AppDatabase
}
@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 éventuellement utiliser des déclarations actual / expect pour créer des implémentations Room spécifiques à la plate-forme. Par exemple, vous pouvez ajouter un DAO spécifique à la plate-forme défini dans le code commun à l'aide de expect
, puis spécifier les définitions actual
avec des requêtes supplémentaires dans des ensembles de sources spécifiques à la plate-forme.
Créer l'outil de création de base de données
Vous devez définir un générateur de base de données pour instancier Room sur chaque plate-forme. Il s'agit de la seule partie de l'API qui doit se trouver dans des ensembles de sources spécifiques à la plate-forme en raison des différences entre les 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 sous iOS, l'emplacement de la base de données est obtenu à l'aide de NSFileManager
.
Android
Pour créer l'instance de base de données, spécifiez un Contexte avec le chemin d'accès à la 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 un chemin d'accès à la base de données à l'aide de NSFileManager
, qui se trouve généralement dans NSDocumentDirectory
.
// shared/src/iosMain/kotlin/Database.kt
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
val dbFilePath = documentDirectory() + "/my_room.db"
return Room.databaseBuilder<AppDatabase>(
name = dbFilePath,
)
}
private fun documentDirectory(): String {
val documentDirectory = NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)
return requireNotNull(documentDirectory?.path)
}
JVM (ordinateur)
Pour créer l'instance de base de données, fournissez un chemin d'accès à la base de données à l'aide d'API Java ou Kotlin.
// shared/src/jvmMain/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,
)
}
Minimisation et obscurcissement
Si le projet est minimisé ou obscurci, la règle ProGuard suivante doit être incluse pour que Room puisse trouver l'implémentation générée de la définition de la base de données:
-keep class * extends androidx.room.RoomDatabase { <init>(); }
Instanciation de la base de données
Une fois que vous avez obtenu le RoomDatabase.Builder
à partir de l'un des constructeurs spécifiques à la plate-forme, vous pouvez configurer le reste de la base de données Room dans le code commun, ainsi que l'instanciation 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 SQLiteDriver
Les extraits de code précédents utilisent BundledSQLiteDriver
. Il s'agit du pilote recommandé qui inclut SQLite compilé à partir de la source, qui fournit la version la plus cohérente et à jour de SQLite sur toutes les plates-formes. Si vous souhaitez utiliser SQLite fourni par l'OS, 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 de l'éditeur de liens afin que l'application iOS s'associe de manière dynamique au 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é développé à l'origine en tant que bibliothèque Android, puis migré vers KMP en mettant l'accent sur la compatibilité des API. La version KMP de Room diffère quelque peu d'une plate-forme à l'autre et de la version spécifique à Android. Ces différences sont listées et décrites comme suit.
Fonctions DAO bloquantes
Lorsque vous utilisez Room pour KMP, toutes les fonctions DAO compilées pour des plates-formes autres qu'Android doivent être des fonctions suspend
, à l'exception des types de retour 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 kotlinx.coroutines
asynchrone aux fonctionnalités nombreuses que Kotlin propose pour plusieurs plates-formes. Pour une fonctionnalité optimale, les fonctions suspend
sont appliquées aux DAO compilées 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 entre les fonctionnalités des versions KMP et de la plate-forme Android de Room.
Fonctions DAO @RawQuery
Les fonctions annotées avec @RawQuery
qui sont compilées pour des plates-formes autres qu'Android devront déclarer un paramètre de type RoomRawQuery
au lieu de SupportSQLiteQuery
.
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query RoomRawQuery): List<TodoEntity>
}
Vous pouvez ensuite utiliser un RoomRawQuery
pour créer une requête au moment de l'exécution:
suspend fun getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = RoomRawQuery(
sql = "SELECT * FROM TodoEntity WHERE title = ?"
onBindStatement = {
it.bindText(1, title.lowercase())
}
)
return todosDao.getTodos(query)
}
Rappel de requête
Les API suivantes pour configurer les rappels de requête ne sont pas disponibles en commun et ne sont donc pas disponibles sur d'autres plates-formes 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
ainsi que l'interface de rappel RoomDatabase.QueryCallback
ne sont pas disponibles en commun et ne sont donc pas disponibles sur d'autres plates-formes que Android.
Base de données à fermeture automatique
L'API permettant d'activer la fermeture automatique après un délai avant expiration, RoomDatabase.Builder.setAutoCloseTimeout
, n'est disponible que sur Android et non sur d'autres plates-formes.
Pré-packager la base de données
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éempaquetée) ne sont pas disponibles en commun et ne sont donc pas disponibles sur d'autres plates-formes que Android. Voici les API concernées:
RoomDatabase.Builder.createFromAsset
RoomDatabase.Builder.createFromFile
RoomDatabase.Builder.createFromInputStream
RoomDatabase.PrepackagedDatabaseCallback
Nous prévoyons d'ajouter la compatibilité avec les bases de données préemballées dans une prochaine version de Room.
Invalidation multi-instance
L'API permettant d'activer l'invalidation multi-instance, RoomDatabase.Builder.enableMultiInstanceInvalidation
, n'est disponible que sur Android et n'est pas disponible sur les plates-formes courantes ni sur d'autres plates-formes.