Camera (multipiattaforma Kotlin)

La libreria di persistenza Room fornisce un livello di astrazione su SQLite per consentire un accesso al database più solido sfruttando al contempo tutta la potenza di SQLite. Questa pagina è incentrata sull'utilizzo di Room nei progetti Kotlin Multiplatform (KMP). Per ulteriori informazioni sull'utilizzo di Room, consulta Salvare i dati in un database locale utilizzando Room o i nostri sample ufficiali.

Configurazione delle dipendenze

La versione corrente di Room che supporta KMP è 2.7.0-alpha01 o successive.

Per configurare Room nel tuo progetto KMP, aggiungi le dipendenze per gli elementi nel build.gradle.kts file del modulo:

  • androidx.room:room-gradle-plugin - Il plug-in Gradle per configurare gli schemi Room
  • androidx.room:room-compiler: il processore KSP che genera il codice
  • androidx.room:room-runtime: la parte di runtime della libreria
  • androidx.sqlite:sqlite-bundled - (Facoltativo) La libreria SQLite inclusa

Inoltre, devi configurare il driver SQLite di Room. Questi driver differiscono in base alla piattaforma di destinazione. Consulta Implementazioni del driver per le descrizioni delle implementazioni del driver disponibili.

Per ulteriori informazioni sulla configurazione, consulta quanto segue:

Definizione delle classi di database

Devi creare una classe di database annotata con @Database insieme a DAO e entità all'interno del set di origine comune del tuo modulo KMP condiviso. Se li inserisci in origini comuni, potrai condividerli su tutte le piattaforme di destinazione.

Quando dichiari un oggetto expect con l'interfaccia RoomDatabaseConstructor, il compilatore Room genera le implementazioni actual. Android Studio potrebbe emettere un avviso "Expected object 'AppDatabaseConstructor' has no actual declaration in module"; puoi ignorarlo con @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
)

Tieni presente che, facoltativamente, puoi utilizzare le dichiarazioni actual / expect per creare implementazioni di Room specifiche per la piattaforma. Ad esempio, puoi aggiungere un DAO specifico per la piattaforma definito nel codice comune utilizzando expect e poi specificare le definizioni di expect con query aggiuntive in set di origini specifici per la piattaforma.actual

Creazione del generatore di database

Devi definire un generatore di database per creare istanze di Room su ogni piattaforma. Questa è l'unica parte dell'API che deve trovarsi in set di origini specifici della piattaforma a causa delle differenze nelle API del file system. Ad esempio, su Android, la posizione del database viene solitamente ottenuta tramite l'API Context.getDatabasePath(), mentre su iOS viene ottenuta utilizzando NSFileManager.

Android

Per creare l'istanza di database, specifica un contesto insieme al percorso del database.

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

Per creare l'istanza del database, specifica un percorso del database utilizzando NSFileManager, in genere situato in 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 (computer)

Per creare l'istanza del database, fornisci un percorso del database utilizzando le API Java o 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,
    )
}

Minimizzazione e offuscamento

Se il progetto è minimizzato o offuscato, deve essere inclusa la seguente regola ProGuard affinché Room possa trovare l'implementazione generata della definizione del database:

-keep class * extends androidx.room.RoomDatabase { <init>(); }

Istanziazione del database

Dopo aver ottenuto RoomDatabase.Builder da uno dei costruttori specifici della piattaforma, puoi configurare il resto del database Room nel codice comune insieme all'istanza effettiva del database.

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

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

Selezione di un driver SQLite

Gli snippet di codice precedenti utilizzano BundledSQLiteDriver. Si tratta del driver consigliato che include SQLite compilato dal codice sorgente, che fornisce la versione più coerente e aggiornata di SQLite su tutte le piattaforme. Se vuoi utilizzare SQLite fornito dal sistema operativo, utilizza l'API setDriver in set di origini specifici della piattaforma che specificano un driver specifico della piattaforma. Per Android, puoi utilizzare AndroidSQLiteDriver, mentre per iOS puoi utilizzare NativeSQLiteDriver. Per usare NativeSQLiteDriver, devi fornire un'opzione del linker in modo che l'app iOS si colleghi dinamicamente a SQLite di sistema.

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

Differenze

Room è stata originariamente sviluppata come libreria Android e in seguito è stata eseguita la migrazione a KMP con un'attenzione particolare alla compatibilità con le API. La versione KMP di Room è leggermente diversa tra le piattaforme e dalla versione specifica per Android. Queste differenze sono elencate e descritte di seguito.

Funzioni DAO di blocco

Quando utilizzi Room per KMP, tutte le funzioni DAO compilate per piattaforme non Android devono essere funzioni suspend, ad eccezione dei tipi di ritorno reattivi, come 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 sfrutta la ricca libreria kotlinx.coroutines asincrona che Kotlin offre per più piattaforme. Per una funzionalità ottimale, le funzioni suspend vengono applicate ai DAO compilati in un progetto KMP, ad eccezione dei DAO specifici per Android per mantenere la compatibilità con le versioni precedenti del codebase esistente.

Differenze di funzionalità con KMP

Questa sezione descrive le differenze tra le funzionalità delle versioni di Room per la piattaforma KMP e Android.

Funzioni DAO @RawQuery

Le funzioni annotate con @RawQuery compilate per piattaforme diverse da Android dovranno dichiarare un parametro di tipo RoomRawQuery anziché SupportSQLiteQuery.

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

Un RoomRawQuery può quindi essere utilizzato per creare una query in fase di esecuzione:

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

Callback di query

Le seguenti API per la configurazione dei callback delle query non sono disponibili in comune e quindi non sono disponibili su piattaforme diverse da Android.

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

Abbiamo intenzione di aggiungere il supporto per il callback della query in una versione futura di Room.

L'API per configurare un RoomDatabase con un callback per query RoomDatabase.Builder.setQueryCallback e l'interfaccia di callback RoomDatabase.QueryCallback non sono disponibili in comune e quindi non sono disponibili in altre piattaforme oltre ad Android.

Database con chiusura automatica

L'API per attivare la chiusura automatica dopo un timeout,RoomDatabase.Builder.setAutoCloseTimeout, è disponibile solo su Android e non su altre piattaforme.

Database precompilato

Le seguenti API per creare un RoomDatabase utilizzando un database esistente (ovvero un database precompilato) non sono disponibili in comune e, di conseguenza, non sono disponibili su altre piattaforme oltre ad Android. Queste API sono:

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

Abbiamo intenzione di aggiungere il supporto per i database precompilati in una versione futura di Room.

Annullamento convalida multi-istanza

L'API per abilitare l'invalidazione multi-istanza,RoomDatabase.Builder.enableMultiInstanceInvalidation è disponibile solo su Android e non è disponibile in altre piattaforme comuni.