La libreria di persistenza Room fornisce un livello di astrazione su SQLite per consentire un accesso più affidabile al database 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 esempi ufficiali.
Configurare le dipendenze
Per configurare Room nel tuo progetto KMP, aggiungi le dipendenze per gli artefatti nel file build.gradle.kts per il tuo modulo KMP.
Definisci le dipendenze nel file libs.versions.toml:
[versions]
room = "2.8.4"
sqlite = "2.6.2"
ksp = "<kotlinCompatibleKspVersion>"
[libraries]
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
# Optional SQLite Wrapper available in version 2.8.0 and higher
androidx-room-sqlite-wrapper = { module = "androidx.room:room-sqlite-wrapper", version.ref = "room" }
[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
androidx-room = { id = "androidx.room", version.ref = "room" }
Aggiungi il plug-in Gradle di Room per configurare gli schemi di Room e il plug-in KSP.
plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.androidx.room)
}
Aggiungi la dipendenza di runtime di Room e la libreria SQLite in bundle:
commonMain.dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.bundled)
}
// Optional when using Room SQLite Wrapper
androidMain.dependencies {
implementation(libs.androidx.room.sqlite.wrapper)
}
Aggiungi le dipendenze KSP al blocco dependencies root. Tieni presente che devi aggiungere tutti i target utilizzati dalla tua app. Per ulteriori informazioni, consulta KSP con
Kotlin Multiplatform.
dependencies {
add("kspAndroid", libs.androidx.room.compiler)
add("kspIosSimulatorArm64", libs.androidx.room.compiler)
add("kspIosX64", libs.androidx.room.compiler)
add("kspIosArm64", libs.androidx.room.compiler)
// Add any other platform target you use in your project, for example kspDesktop
}
Definisci la directory dello schema di Room. Per ulteriori informazioni, consulta Impostare la posizione dello schema utilizzando il plug-in Gradle di Room.
room {
schemaDirectory("$projectDir/schemas")
}
Definire le classi di database
Devi creare una classe di database annotata con @Database insieme a DAO ed entità all'interno del set di risorse comune del modulo KMP condiviso. Se inserisci queste classi nelle origini comuni, potrai condividerle su tutte le piattaforme di destinazione.
// 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("KotlinNoActualForExpect")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> {
override fun initialize(): AppDatabase
}
Quando dichiari un oggetto expect con l'interfaccia RoomDatabaseConstructor, il compilatore Room genera le implementazioni actual. Android Studio potrebbe emettere il seguente avviso, che puoi
sopprimere con @Suppress("KotlinNoActualForExpect"):
Expected object 'AppDatabaseConstructor' has no actual declaration in module`
Successivamente, definisci una nuova interfaccia DAO o sposta una esistente in
commonMain:
// shared/src/commonMain/kotlin/TodoDao.kt
@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>>
}
Definisci o sposta le entità in commonMain:
// shared/src/commonMain/kotlin/TodoEntity.kt
@Entity
data class TodoEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String
)
Creare il builder di database specifico della piattaforma
Devi definire un builder di database per creare un'istanza di Room su ogni piattaforma. Questa è l'unica parte dell'API che deve trovarsi nei set di origine specifici della piattaforma a causa delle differenze nelle API del file system.
Android
Su Android, la posizione del database viene in genere ottenuta tramite l'
Context.getDatabasePath() API. Per creare l'istanza del database, specifica un
Context insieme al percorso del database.
// shared/src/androidMain/kotlin/Database.android.kt
fun getDatabaseBuilder(context: Context): RoomDatabase.Builder<AppDatabase> {
val appContext = context.applicationContext
val dbFile = appContext.getDatabasePath("my_room.db")
return Room.databaseBuilder<AppDatabase>(
context = appContext,
name = dbFile.absolutePath
)
}
iOS
Per creare l'istanza del database su iOS, fornisci un percorso del database utilizzando il
NSFileManager, in genere situato in NSDocumentDirectory.
// shared/src/iosMain/kotlin/Database.ios.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 (desktop)
Per creare l'istanza del database, fornisci un percorso del database utilizzando le API Java o Kotlin.
// shared/src/jvmMain/kotlin/Database.desktop.kt
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
val dbFile = File(System.getProperty("java.io.tmpdir"), "my_room.db")
return Room.databaseBuilder<AppDatabase>(
name = dbFile.absolutePath,
)
}
Creare un'istanza del database
Una volta ottenuto RoomDatabase.Builder da uno dei costruttori specifici della piattaforma, puoi configurare il resto del database Room nel codice comune insieme alla creazione dell'istanza del database.
// shared/src/commonMain/kotlin/Database.kt
fun getRoomDatabase(
builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
return builder
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
Selezionare un driver SQLite
Lo snippet di codice precedente chiama la funzione del builder setDriver per definire il driver SQLite che deve utilizzare il database Room. Questi driver variano in base alla piattaforma di destinazione. Gli snippet di codice precedenti utilizzano BundledSQLiteDriver.
Questo è il driver consigliato che include SQLite compilato dall'origine, 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 nei set di origine specifici della piattaforma che specificano un driver specifico della piattaforma. Consulta
Implementazioni dei driver per le descrizioni delle implementazioni dei driver disponibili. Puoi utilizzare una delle seguenti opzioni:
AndroidSQLiteDriverinandroidMainNativeSQLiteDriveriniosMain
Per utilizzare NativeSQLiteDriver, devi fornire un'opzione del linker -lsqlite3 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")
}
}
}
Impostare un contesto di coroutine (facoltativo)
Un oggetto RoomDatabase su Android può essere configurato facoltativamente con executor di applicazioni condivise utilizzando RoomDatabase.Builder.setQueryExecutor() per eseguire operazioni di database.
Poiché gli executor non sono compatibili con KMP, l'API setQueryExecutor() di Room non è disponibile in commonMain. L'oggetto RoomDatabase deve invece essere configurato
con un CoroutineContext, che può essere impostato utilizzando
RoomDatabase.Builder.setCoroutineContext(). Se non viene impostato alcun contesto, l'oggetto RoomDatabase utilizzerà per impostazione predefinita Dispatchers.IO.
Minimizzazione e offuscamento
Se il progetto è minimizzato o offuscato, devi includere la seguente regola ProGuard in modo che Room possa trovare l'implementazione generata della definizione del database:
-keep class * extends androidx.room.RoomDatabase { <init>(); }
Eseguire la migrazione a Kotlin Multiplatform
Room è stato sviluppato originariamente come libreria Android e successivamente è stato migrato a KMP con un'attenzione particolare alla compatibilità delle API. La versione KMP di Room è leggermente diversa tra le piattaforme e dalla versione specifica di Android. Queste differenze sono elencate e descritte di seguito.
Eseguire la migrazione da Support SQLite a SQLite Driver
Tutti gli utilizzi di SupportSQLiteDatabase e di altre API in
androidx.sqlite.db devono essere sottoposti a refactoring con le API SQLite Driver,
perché le API in androidx.sqlite.db sono solo per Android (nota il
pacchetto diverso dal pacchetto KMP).
Per la compatibilità con le versioni precedenti e finché RoomDatabase è configurato con un SupportSQLiteOpenHelper.Factory (ad esempio, non è impostato alcun SQLiteDriver), Room si comporta in "modalità di compatibilità" in cui le API Support SQLite e SQLite Driver funzionano come previsto. In questo modo è possibile eseguire migrazioni incrementali, in modo da non dover convertire tutti gli utilizzi di Support SQLite in SQLite Driver in un'unica modifica.
Utilizzare il wrapper SQLite di Room (facoltativo)
L'artefatto androidx.room:room-sqlite-wrapper fornisce API per colmare il divario tra SQLiteDriver e SupportSQLiteDatabase durante la migrazione.
Per ottenere un SupportSQLiteDatabase da un RoomDatabase configurato con un
SQLiteDriver, utilizza la nuova funzione di estensione
RoomDatabase.getSupportWrapper(). Questo wrapper di compatibilità aiuta a mantenere
gli utilizzi esistenti di SupportSQLiteDatabase (spesso ottenuti da
RoomDatabase.openHelper.writableDatabase) durante l'adozione di SQLiteDriver,
in particolare per i codebase con un utilizzo esteso dell'API SupportSQLite che vogliono
utilizzare BundledSQLiteDriver.
Convertire le sottoclassi di migrazioni
Le sottoclassi di migrazioni devono essere migrate alle controparti di SQLite Driver:
Kotlin Multiplatform
Sottoclassi di migrazioni
object Migration_1_2 : Migration(1, 2) {
override fun migrate(connection: SQLiteConnection) {
// …
}
}
Sottoclassi di specifiche di migrazione automatica
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(connection: SQLiteConnection) {
// …
}
}
Solo per Android
Sottoclassi di migrazioni
object Migration_1_2 : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
// …
}
}
Sottoclassi di specifiche di migrazione automatica
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
// …
}
}
Convertire il callback del database
I callback del database devono essere migrati alle controparti di SQLite Driver:
Kotlin Multiplatform
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(connection: SQLiteConnection) {
// …
}
override fun onDestructiveMigration(connection: SQLiteConnection) {
// …
}
override fun onOpen(connection: SQLiteConnection) {
// …
}
}
Solo per Android
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
// …
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
// …
}
override fun onOpen(db: SupportSQLiteDatabase) {
// …
}
}
Convertire le funzioni DAO @RawQuery
Le funzioni annotate con @RawQuery compilate per piattaforme non Android dovranno dichiarare un parametro di tipo RoomRawQuery anziché SupportSQLiteQuery.
Kotlin Multiplatform
Definire la query non elaborata
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: RoomRawQuery): List<TodoEntity>
}
È quindi possibile utilizzare un RoomRawQuery per creare una query in fase di runtime:
suspend fun AppDatabase.getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = RoomRawQuery(
sql = "SELECT * FROM TodoEntity WHERE title = ?",
onBindStatement = {
it.bindText(1, title.lowercase())
}
)
return todoDao().getTodos(query)
}
Solo per Android
Definire la query non elaborata
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: SupportSQLiteQuery): List<TodoEntity>
}
È quindi possibile utilizzare un SimpleSQLiteQuery per creare una query in fase di runtime:
suspend fun AndroidOnlyDao.getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = SimpleSQLiteQuery(
query = "SELECT * FROM TodoEntity WHERE title = ?",
bindArgs = arrayOf(title.lowercase())
)
return getTodos(query)
}
Convertire le funzioni DAO di blocco
Room sfrutta la libreria asincrona kotlinx.coroutines ricca di funzionalità 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 implementati in androidMain per mantenere la compatibilità con le versioni precedenti del codebase esistente. Quando utilizzi Room per KMP, tutte le funzioni DAO compilate per piattaforme non Android devono essere funzioni suspend.
Kotlin Multiplatform
Sospensione delle query
@Query("SELECT * FROM Todo")
suspend fun getAllTodos(): List<Todo>
Sospensione delle transazioni
@Transaction
suspend fun transaction() { … }
Solo per Android
Blocco delle query
@Query("SELECT * FROM Todo")
fun getAllTodos(): List<Todo>
Blocco delle transazioni
@Transaction
fun blockingTransaction() { … }
Convertire i tipi reattivi in Flow
Non tutte le funzioni DAO devono essere funzioni di sospensione. Le funzioni DAO che restituiscono tipi reattivi come LiveData o Flowable di RxJava non devono essere convertite in funzioni di sospensione. Alcuni tipi, tuttavia, come LiveData, non sono compatibili con KMP. Le funzioni DAO con tipi di restituzione reattivi devono essere migrate ai flussi di coroutine.
Kotlin Multiplatform
Tipi reattivi Flows
@Query("SELECT * FROM Todo")
fun getTodosFlow(): Flow<List<Todo>>
Solo per Android
Tipi reattivi come LiveData o Flowable di RxJava
@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>
Convertire le API delle transazioni
Le API delle transazioni di database per Room KMP possono distinguere tra transazioni di scrittura (useWriterConnection) e di lettura (useReaderConnection).
Kotlin Multiplatform
val database: RoomDatabase = …
database.useWriterConnection { transactor ->
transactor.immediateTransaction {
// perform database operations in transaction
}
}
Solo per Android
val database: RoomDatabase = …
database.withTransaction {
// perform database operations in transaction
}
Transazioni di scrittura
Utilizza le transazioni di scrittura per assicurarti che più query scrivano i dati in modo atomico, in modo che i lettori possano accedere ai dati in modo coerente. Puoi farlo utilizzando useWriterConnection con uno dei tre tipi di transazione:
immediateTransaction: in modalità Write-Ahead Logging (WAL) (impostazione predefinita), questo tipo di transazione acquisisce un blocco all'avvio, ma i lettori possono continuare a leggere. Questa è la scelta preferita per la maggior parte dei casi.deferredTransaction: la transazione non acquisisce un blocco fino alla prima istruzione di scrittura. Utilizza questo tipo di transazione come ottimizzazione quando non sei sicuro se sarà necessaria un'operazione di scrittura all'interno della transazione. Ad esempio, se avvii una transazione per eliminare i brani da una playlist dato solo il nome della playlist e la playlist non esiste, non è necessaria alcuna operazione di scrittura (eliminazione).exclusiveTransaction: questa modalità si comporta in modo identico aimmediateTransactionin modalità WAL. In altre modalità di journaling, impedisce ad altre connessioni al database di leggere il database durante la transazione.
Transazioni di lettura
Utilizza le transazioni di lettura per leggere più volte in modo coerente dal database. Ad esempio, quando hai due o più query separate e non utilizzi una clausola JOIN. Nelle connessioni di lettura sono consentite solo le transazioni differite. Il tentativo di avviare una transazione immediata o esclusiva in una connessione di lettura genererà un'eccezione, in quanto queste sono considerate operazioni di "scrittura".
val database: RoomDatabase = …
database.useReaderConnection { transactor ->
transactor.deferredTransaction {
// perform database operations in transaction
}
}
Non disponibile in Kotlin Multiplatform
Alcune delle API disponibili per Android non sono disponibili in Kotlin Multiplatform.
Callback delle query
Le seguenti API per la configurazione dei callback delle query non sono disponibili in common e quindi non sono disponibili su piattaforme diverse da Android.
RoomDatabase.Builder.setQueryCallbackRoomDatabase.QueryCallback
Abbiamo in programma di aggiungere il supporto per il callback delle query in una versione futura di Room.
L'API per configurare un RoomDatabase con un callback delle query
RoomDatabase.Builder.setQueryCallback insieme all'interfaccia di callback
RoomDatabase.QueryCallback non è disponibile in common e quindi non è disponibile
su altre piattaforme diverse da Android.
Chiusura automatica del database
L'API per abilitare la chiusura automatica dopo un timeout, RoomDatabase.Builder.setAutoCloseTimeout, è disponibile solo su Android e non è disponibile su altre piattaforme.
Database precompilato
Le seguenti API per creare un RoomDatabase utilizzando un database esistente (ovvero un database precompilato) non sono disponibili in common e quindi non sono disponibili su altre piattaforme diverse da Android. Queste API sono:
RoomDatabase.Builder.createFromAssetRoomDatabase.Builder.createFromFileRoomDatabase.Builder.createFromInputStreamRoomDatabase.PrepackagedDatabaseCallback
Abbiamo in programma di aggiungere il supporto per i database precompilati in una versione futura di Room.
Invalidazione multi-istanza
L'API per abilitare l'invalidazione multi-istanza, RoomDatabase.Builder.enableMultiInstanceInvalidation, è disponibile solo su Android e non è disponibile in common o su altre piattaforme.
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Codelab Eseguire la migrazione delle app esistenti su Room KMP
- Codelab Iniziare a utilizzare KMP
- Salvare i dati nel database locale con Room