Room (multiplateforme Kotlin)

La bibliothèque de persistance Room fournit une couche d'abstraction sur SQLite pour permettre pour un accès plus robuste aux bases de données tout en exploitant toute la puissance de SQLite. Ce se concentre sur l'utilisation de Room dans les projets KMP (Kotlin Multiplatform). Pour plus des informations sur l'utilisation de Room, consultez la section 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 2.7.0-alpha01 ou version ultérieure.

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

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

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

Pour en savoir plus sur la configuration, consultez les pages suivantes:

Définir les classes de base de données

Vous devez créer une classe de base de données annotée avec @Database avec les DAO. et les entités de l'ensemble de sources commun de votre module KMP partagé. Positionnement ces classes dans des sources communes permettront de les partager entre toutes plates-formes.

Lorsque vous déclarez un objet expect avec l'interface RoomDatabaseConstructor, le compilateur Room génère le actual mises en œuvre. Android Studio peut afficher un avertissement "Expected object 'AppDatabaseConstructor' has no actual declaration in module"; you can suppress the warning with@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>

@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 réelles / attendues. pour créer des implémentations Room spécifiques à la plate-forme. Par exemple, vous pouvez ajouter DAO spécifique à la plate-forme défini dans du code commun à l'aide de expect, puis spécifier les définitions actual avec des requêtes supplémentaires dans les paramètres spécifiques à la plate-forme ; d'ensembles de sources.

Créer le compilateur 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. Ce est la seule partie de l'API qui doit se trouver dans une source spécifique à la plate-forme en raison des différences dans les API de système de fichiers. Par exemple, sur Android, l'emplacement de la base de données est généralement obtenue via Context.getDatabasePath(), tandis que pour iOS, l'emplacement de la base de données est être obtenu à l'aide de NSFileManager.

Android

Pour créer l'instance de base de données, spécifiez un Context (Contexte) ainsi que la base de données. chemin d'accès.

// 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, indiquez un chemin d'accès à la base de données à l'aide de la méthode NSFileManager, généralement située dans le 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 de bureau)

Pour créer l'instance de base de données, indiquez un chemin d'accès à la base de données en Java ou Kotlin API.

// 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,
    )
}

Instanciation de base de données

Une fois que vous avez obtenu le RoomDatabase.Builder auprès de l'un des services vous pouvez configurer le reste de la base de données Room en 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 de la le pilote recommandé incluant SQLite compilé à partir de la source, qui fournit la version la plus cohérente et la plus récente de SQLite sur toutes les plateformes. Si vous si vous souhaitez utiliser SQLite fournie par l'OS, utilisez l'API setDriver dans les paramètres spécifiques à la plate-forme. de sources qui spécifient un pilote spécifique à la plate-forme. Pour Android, vous pouvez utiliser AndroidSQLiteDriver, tandis que pour iOS, vous pouvez utiliser NativeSQLiteDriver. À utilisez NativeSQLiteDriver, vous devez fournir une option d'éditeur de liens afin qu'iOS l'application est associée de manière dynamique au système SQLite.

// 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

À l'origine, Room a été 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 est légèrement différente entre les plateformes et à partir de la version spécifique à Android. Ces différences sont listées et décrites comme suit.

Bloquer des fonctions DAO

Lorsque vous utilisez Room pour KMP, toutes les fonctions DAO sont compilées pour les plates-formes autres qu'Android. doivent être des fonctions suspend, à l'exception des types renvoyés réactifs, tels que en tant 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() { // … }
}

Avantages de Room grâce à la bibliothèque kotlinx.coroutines asynchrone riche en fonctionnalités que Kotlin propose pour de nombreuses plates-formes. Pour un fonctionnement optimal, suspend sont appliquées aux DAO compilés dans un projet KMP, à l'exception des des DAO spécifiques à Android afin de maintenir la rétrocompatibilité avec les de votre codebase.

Différences de fonctionnalités avec KMP

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

Fonctions DAO de @RawQuery

Fonctions annotées avec @RawQuery compilées pour les plates-formes autres qu'Android devra déclarer un paramètre de type RoomRawQuery au lieu de SupportSQLiteQuery

@Dao
interface TodoDao {
  @RawQuery
  suspend fun getTodos(query RoomRawQuery): List<TodoEntity>
}

Un RoomRawQuery peut ensuite être utilisé 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, qui permettent de configurer des rappels de requête, ne sont pas disponibles en 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 future version de Room.

API permettant de configurer un RoomDatabase avec un rappel de requête RoomDatabase.Builder.setQueryCallback avec 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 est n'est pas disponible sur d'autres plates-formes.

Base de données prête à l'emploi

Les API suivantes permettent de créer un RoomDatabase à l'aide d'une base de données existante (par exemple, pré-empaquetée) ne sont pas disponibles en commun et ne sont donc pas disponibles dans d'autres plateformes autres qu'Android. Ces API sont les suivantes:

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

Nous prévoyons d'ajouter la prise en charge des bases de données prêtes à l'emploi dans une future version de Pièce.

Invalidation d'instances multiples

L'API permettant d'activer l'invalidation multi-instance RoomDatabase.Builder.enableMultiInstanceInvalidation n'est disponible que sur Android et n'est pas disponible sur la plate-forme courante ni sur d'autres plates-formes.