Questo documento descrive come eseguire la migrazione di un'implementazione di stanze esistente a uno che utilizza Kotlin Multiplatform (KMP).
Migrazione degli utilizzi delle stanze in un codebase Android esistente a un KMP condiviso comune modulo può variare notevolmente in termini di difficoltà a seconda delle API Room utilizzate o il codebase utilizza già Coroutines. Questa sezione offre indicazioni e suggerimenti durante la migrazione degli utilizzi di Room a un modulo comune.
È importante prima familiarizzare con le differenze e i mancati
di funzionalità tra la versione per Android di Room e la versione KMP, oltre a
la configurazione prevista. In sostanza, una migrazione di successo prevede il refactoring
di utilizzo delle API SupportSQLite*
e la loro sostituzione con le API SQLite Driver
oltre allo spostamento delle dichiarazioni della stanza (@Database
classe annotata, DAO,
entità e così via) in codice comune.
Prima di continuare, rivedi le seguenti informazioni:
Nelle sezioni successive vengono descritti i vari passaggi necessari per creare migrazione.
Esegui la migrazione da Support SQLite a SQLite Driver
Le API in androidx.sqlite.db
sono solo Android e tutti gli utilizzi devono essere
con il refactoring delle API SQLite Driver. Per la compatibilità con le versioni precedenti e a condizione che
RoomDatabase
è configurato con un SupportSQLiteOpenHelper.Factory
(ad es.
nessun SQLiteDriver
è impostato), la stanza si comporta in "modalità di compatibilità" dove
entrambe le API Support SQLite e SQLite Driver funzionano come previsto. Ciò consente
migrazioni incrementali in modo da non dover convertire tutte le tue richieste SQLite
di utilizzo di SQLite Driver in una singola modifica.
I seguenti esempi sono utilizzi comuni di Support SQLite e delle relative SQLite Contrassegni dei conducenti:
Supporta SQLite (da)
Esegui una query senza risultato
val database: SupportSQLiteDatabase = ...
database.execSQL("ALTER TABLE ...")
Esegui una query con risultato ma senza argomenti
val database: SupportSQLiteDatabase = ...
database.query("SELECT * FROM Pet").use { cursor ->
while (cusor.moveToNext()) {
// read columns
cursor.getInt(0)
cursor.getString(1)
}
}
Esegui una query con risultato e argomenti
database.query("SELECT * FROM Pet WHERE id = ?", id).use { cursor ->
if (cursor.moveToNext()) {
// row found, read columns
} else {
// row not found
}
}
Driver SQLite (a)
Esegui una query senza risultato
val connection: SQLiteConnection = ...
connection.execSQL("ALTER TABLE ...")
Esegui una query con risultato ma senza argomenti
val connection: SQLiteConnection = ...
connection.prepare("SELECT * FROM Pet").use { statement ->
while (statement.step()) {
// read columns
statement.getInt(0)
statement.getText(1)
}
}
Esegui una query con risultato e argomenti
connection.prepare("SELECT * FROM Pet WHERE id = ?").use { statement ->
statement.bindInt(1, id)
if (statement.step()) {
// row found, read columns
} else {
// row not found
}
}
Le API per le transazioni dei database sono disponibili direttamente in SupportSQLiteDatabase
con
beginTransaction()
, setTransactionSuccessful()
e endTransaction()
.
Sono disponibili anche tramite la Stanza virtuale con runInTransaction()
. Esegui la migrazione di questi
di utilizzo delle API SQLite Driver.
Supporta SQLite (da)
Eseguire una transazione (utilizzando RoomDatabase
)
val database: RoomDatabase = ...
database.runInTransaction {
// perform database operations in transaction
}
Eseguire una transazione (utilizzando SupportSQLiteDatabase
)
val database: SupportSQLiteDatabase = ...
database.beginTransaction()
try {
// perform database operations in transaction
database.setTransactionSuccessful()
} finally {
database.endTransaction()
}
Driver SQLite (a)
Eseguire una transazione (utilizzando RoomDatabase
)
val database: RoomDatabase = ...
database.useWriterConnection { transactor ->
transactor.immediateTransaction {
// perform database operations in transaction
}
}
Eseguire una transazione (utilizzando SQLiteConnection
)
val connection: SQLiteConnection = ...
connection.execSQL("BEGIN IMMEDIATE TRANSACTION")
try {
// perform database operations in transaction
connection.execSQL("END TRANSACTION")
} catch(t: Throwable) {
connection.execSQL("ROLLBACK TRANSACTION")
}
È necessario eseguire anche la migrazione di vari override di callback alle rispettive controparti del driver:
Supporta SQLite (da)
Sottoclassi di migrazione
object Migration_1_2 : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
// ...
}
}
Sottoclassi delle specifiche della migrazione automatica
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
// ...
}
}
Sottoclassi di callback del database
object MyRoomCallback : RoomDatabase.Callback {
override fun onCreate(db: SupportSQLiteDatabase) {
// ...
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
// ...
}
override fun onOpen(db: SupportSQLiteDatabase) {
// ...
}
}
Driver SQLite (a)
Sottoclassi di migrazione
object Migration_1_2 : Migration(1, 2) {
override fun migrate(connection: SQLiteConnection) {
// ...
}
}
Sottoclassi delle specifiche della migrazione automatica
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(connection: SQLiteConnection) {
// ...
}
}
Sottoclassi di callback del database
object MyRoomCallback : RoomDatabase.Callback {
override fun onCreate(connection: SQLiteConnection) {
// ...
}
override fun onDestructiveMigration(connection: SQLiteConnection) {
// ...
}
override fun onOpen(connection: SQLiteConnection) {
// ...
}
}
Ricapitolando, sostituisci gli utilizzi di SQLiteDatabase
, con SQLiteConnection
quando
RoomDatabase
non è disponibile, ad esempio negli override dei callback (onMigrate
,
onCreate
e così via). Se è disponibile un RoomDatabase
, accedi all'elemento sottostante
connessione al database utilizzando RoomDatabase.useReaderConnection
e
RoomDatabase.useWriterConnection
invece di
RoomDatabase.openHelper.writtableDatabase
.
Converti le funzioni DAO di blocco in funzioni di sospensione
La versione KMP di Room si basa sulle coroutine per eseguire l'I/O
operazioni sul criterio CoroutineContext
configurato. Ciò significa che
devono eseguire la migrazione delle funzioni DAO di blocco per sospendere le funzioni.
Blocco funzione DAO (da)
@Query("SELECT * FROM Todo")
fun getAllTodos(): List<Todo>
Sospensione della funzione DAO (to)
@Query("SELECT * FROM Todo")
suspend fun getAllTodos(): List<Todo>
È possibile eseguire la migrazione delle funzioni di blocco DAO esistenti alle funzioni di sospensione complicata se il codebase esistente non include già le coroutine. Consulta l'articolo Coroutine in Android per iniziare a utilizzare le coroutine nel tuo codebase.
Converti i tipi di reso reattivi in Flow
Non tutte le funzioni DAO devono essere funzioni di sospensione. Funzioni DAO che restituiscono
tipi reattivi come LiveData
o Flowable
di RxJava non devono essere convertiti
per sospendere le funzioni. Alcuni tipi, tuttavia, come LiveData
non sono KMP
compatibili. È necessario eseguire la migrazione delle funzioni DAO con tipi restituiti reattivi in
fluisce della coroutina.
Tipo KMP incompatibile (da)
@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>
Tipo KMP compatibile (to)
@Query("SELECT * FROM Todo")
fun getTodosFlow(): Flow<List<Todo>>
Consulta Flows in Android per iniziare a utilizzare Flows nelle tue codebase.
(Facoltativo) Imposta un contesto per la Coroutine
Facoltativamente, un RoomDatabase
può essere configurato con un'applicazione condivisa
esecutori che usano RoomDatabase.Builder.setQueryExecutor()
per eseguire il database
operazioni. Poiché gli esecutori non sono compatibili con KMP, il valore setQueryExecutor()
della stanza
L'API non è disponibile per le origini comuni. RoomDatabase
deve invece
deve essere configurata con un CoroutineContext
. È possibile impostare un contesto utilizzando
RoomDatabase.Builder.setCoroutineContext()
, se non è impostato nulla, allora
RoomDatabase
utilizzerà Dispatchers.IO
per impostazione predefinita.
Imposta un driver SQLite
Una volta eseguita la migrazione degli utilizzi Support SQLite alle API SQLite Driver, viene
il driver deve essere configurato utilizzando RoomDatabase.Builder.setDriver
. La
il conducente consigliato è BundledSQLiteDriver
. Consulta Implementazioni dei driver per
le descrizioni delle implementazioni
disponibili dei driver.
SupportSQLiteOpenHelper.Factory
personalizzata configurata con
RoomDatabase.Builder.openHelperFactory()
non sono supportati in KMP,
le funzionalità fornite dall'helper aperto personalizzato dovranno essere implementate nuovamente
Interfacce del driver SQLite.
Sposta dichiarazioni della stanza
Una volta completata la maggior parte dei passaggi della migrazione, è possibile spostare la stanza
definizioni di un insieme di origini comune. Tieni presente che expect
strategie su actual
possono
per spostare in modo incrementale le definizioni relative alle stanze. Ad esempio, se non tutti
di blocco delle funzioni DAO, è possibile eseguire la migrazione
delle funzioni di sospensione,
dichiarare un'interfaccia annotata @Dao
expect
vuota nel codice comune, ma
contiene funzioni di blocco in Android.
// shared/src/commonMain/kotlin/Database.kt
@Database(entities = [TodoEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun getDao(): TodoDao
abstract fun getBlockingDao(): BlockingTodoDao
}
@Dao
interface TodoDao {
@Query("SELECT count(*) FROM TodoEntity")
suspend fun count(): Int
}
@Dao
expect interface BlockingTodoDao
// shared/src/androidMain/kotlin/BlockingTodoDao.kt
@Dao
actual interface BlockingTodoDao {
@Query("SELECT count(*) FROM TodoEntity")
fun count(): Int
}