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 Roomandroidx.room:room-compiler
: il processore KSP che genera il codiceandroidx.room:room-runtime
: la parte di runtime della libreriaandroidx.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:
- Imposta la posizione dello schema utilizzando il plug-in Gradle di Room.
- KSP con Kotlin Multiplatform.
- Aggiunta di dipendenze di runtime.
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.