Migracja pokoju do wersji wieloplatformowej Kotlin

Z tego dokumentu dowiesz się, jak przenieść dotychczasową implementację sal do który wykorzystuje platformę Kotlin Multiplatform (KMP).

Migracja danych o wykorzystaniu sal z istniejącej bazy kodu Androida do wspólnej współdzielonej platformy KMP mogą mieć różne trudności w zależności od używanych interfejsów API sal baza kodu korzysta już z Coroutines. W tej sekcji znajdziesz wskazówki i wskazówki podczas próby przeniesienia wykorzystania pokoju do wspólnego modułu.

Warto najpierw zapoznać się z różnicami między wersją pokoju na Androida a wersją KMP, wymaganych czynnościach. Ogólnie udana migracja wymaga refaktoryzacji. zastosowania interfejsów API SupportSQLite* i zastąpienie ich interfejsami SQLite Driver API wraz z przenoszeniem deklaracji sal (klasa z adnotacjami: @Database, DAO, encji itd.) we wspólny kod.

Zanim przejdziesz dalej, sprawdź te informacje:

W kolejnych sekcjach opisano różne kroki wymagane do skutecznego migracji danych.

Przejście z Support SQLite na sterownik SQLite

Interfejsy API w androidx.sqlite.db są przeznaczone tylko na Androida i trzeba je zmodyfikowano za pomocą interfejsów SQLite Driver API. Aby zapewnić zgodność wsteczną, RoomDatabase jest skonfigurowany z zastosowaniem SupportSQLiteOpenHelper.Factory (tzn. nie ustawiono SQLiteDriver), sala działa w „trybie zgodności” gdzie Interfejsy API obsługi SQLite i SQLite Driver API działają zgodnie z oczekiwaniami. Dzięki temu migracje przyrostowe, dzięki którym nie trzeba konwertować całego pomocniczego SQLite do sterownika SQLite w jednej zmianie.

Poniższe przykłady przedstawiają typowe zastosowania Support SQLite i ich SQLite Odpowiedniki kierowców:

Obsługa SQLite (od)

Wykonaj zapytanie bez wyniku

val database: SupportSQLiteDatabase = ...
database.execSQL("ALTER TABLE ...")

Wykonaj zapytanie z wynikiem bez argumentów

val database: SupportSQLiteDatabase = ...
database.query("SELECT * FROM Pet").use { cursor ->
  while (cusor.moveToNext()) {
    // read columns
    cursor.getInt(0)
    cursor.getString(1)
  }
}

Wykonaj zapytanie z wynikiem i argumentami

database.query("SELECT * FROM Pet WHERE id = ?", id).use { cursor ->
  if (cursor.moveToNext()) {
    // row found, read columns
  } else {
    // row not found
  }
}

Sterownik SQLite (do)

Wykonaj zapytanie bez wyniku

val connection: SQLiteConnection = ...
connection.execSQL("ALTER TABLE ...")

Wykonaj zapytanie z wynikiem bez argumentów

val connection: SQLiteConnection = ...
connection.prepare("SELECT * FROM Pet").use { statement ->
  while (statement.step()) {
    // read columns
    statement.getInt(0)
    statement.getText(1)
  }
}

Wykonaj zapytanie z wynikiem i argumentami

connection.prepare("SELECT * FROM Pet WHERE id = ?").use { statement ->
  statement.bindInt(1, id)
  if (statement.step()) {
    // row found, read columns
  } else {
    // row not found
  }
}

Interfejsy API transakcji baz danych są dostępne bezpośrednio w SupportSQLiteDatabase z użyciem beginTransaction(), setTransactionSuccessful() i endTransaction(). Są one też dostępne w pokoju za pomocą runInTransaction(). Przenieś te i zastosowaniach w interfejsach SQLite Driver API.

Obsługa SQLite (od)

Wykonaj transakcję (przy użyciu aplikacji RoomDatabase)

val database: RoomDatabase = ...
database.runInTransaction {
  // perform database operations in transaction
}

Wykonaj transakcję (przy użyciu aplikacji SupportSQLiteDatabase)

val database: SupportSQLiteDatabase = ...
database.beginTransaction()
try {
  // perform database operations in transaction
  database.setTransactionSuccessful()
} finally {
  database.endTransaction()
}

Sterownik SQLite (do)

Wykonaj transakcję (przy użyciu aplikacji RoomDatabase)

val database: RoomDatabase = ...
database.useWriterConnection { transactor ->
  transactor.immediateTransaction {
    // perform database operations in transaction
  }
}

Wykonaj transakcję (przy użyciu aplikacji 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")
}

Zastąpienie różnych wywołań zwrotnych trzeba też przenieść do odpowiednich odpowiedników:

Obsługa SQLite (od)

Podklasy migracji

object Migration_1_2 : Migration(1, 2) {
  override fun migrate(db: SupportSQLiteDatabase) {
    // ...
  }
}

Podklasy specyfikacji automatycznej migracji

class AutoMigrationSpec_1_2 : AutoMigrationSpec {
  override fun onPostMigrate(db: SupportSQLiteDatabase) {
    // ...
  }
}

Podklasy wywołania zwrotnego bazy danych

object MyRoomCallback : RoomDatabase.Callback {
  override fun onCreate(db: SupportSQLiteDatabase) {
    // ...
  }

  override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
    // ...
  }

  override fun onOpen(db: SupportSQLiteDatabase) {
    // ...
  }
}

Sterownik SQLite (do)

Podklasy migracji

object Migration_1_2 : Migration(1, 2) {
  override fun migrate(connection: SQLiteConnection) {
    // ...
  }
}

Podklasy specyfikacji automatycznej migracji

class AutoMigrationSpec_1_2 : AutoMigrationSpec {
  override fun onPostMigrate(connection: SQLiteConnection) {
    // ...
  }
}

Podklasy wywołania zwrotnego bazy danych

object MyRoomCallback : RoomDatabase.Callback {
  override fun onCreate(connection: SQLiteConnection) {
    // ...
  }

  override fun onDestructiveMigration(connection: SQLiteConnection) {
    // ...
  }

  override fun onOpen(connection: SQLiteConnection) {
    // ...
  }
}

Podsumowując, zastąp zastosowania SQLiteDatabase ciągiem SQLiteConnection, gdy Metoda RoomDatabase jest niedostępna, np. w zastąpieniach wywołania zwrotnego (onMigrate, onCreate itp.). Jeśli usługa RoomDatabase jest dostępna, przejdź do źródła połączenie z bazą danych za pomocą RoomDatabase.useReaderConnection i RoomDatabase.useWriterConnection zamiast RoomDatabase.openHelper.writtableDatabase

Przekonwertuj blokujące funkcje DAO na zawieszanie funkcji

Wersja pokoju w KMP wykorzystuje współrzędne do przeprowadzania I/O operacji na skonfigurowanym elemencie CoroutineContext. Oznacza to, że musisz musisz przenieść blokujące funkcje DAO do zawieszania funkcji.

Blokowanie funkcji DAO (z)

@Query("SELECT * FROM Todo")
fun getAllTodos(): List<Todo>

Zawieszam funkcję DAO (do)

@Query("SELECT * FROM Todo")
suspend fun getAllTodos(): List<Todo>

Migracja istniejących funkcji blokujących DAO do funkcji zawieszania może być jeśli bieżąca baza kodu nie zawiera jeszcze współrzędnych. Aby dowiedzieć się, jak korzystać z współrzędnych, zapoznaj się z informacjami o kogutynach w Androidzie. w bazie kodu.

Przekształć typy zwrotów reaktywnych na Flow

Nie wszystkie funkcje DAO muszą być funkcjami zawieszania. Funkcje DAO, które zwracają typów reaktywnych, takich jak LiveData czy Flowable RxJava, nie należy konwertować do zawieszania funkcji. Jednak niektóre typy, takie jak LiveData, nie są obsługiwane przez KMP są zgodne. Funkcje DAO z reaktywnymi typami zwrotów trzeba przenieść do współrzędnych przepływów.

Niezgodny typ KMP (z)

@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>

Zgodny typ KMP (do)

@Query("SELECT * FROM Todo")
fun getTodosFlow(): Flow<List<Todo>>

Aby zacząć korzystać z przepływów w aplikacji, przeczytaj artykuł Flows in Android. bazy kodu.

Ustawianie kontekstu Coroutine (opcjonalnie)

Interfejs RoomDatabase można opcjonalnie skonfigurować ze współdzieloną aplikacją wykonawcy używający RoomDatabase.Builder.setQueryExecutor() do wykonywania bazy danych operacji. Wykonawcy nie są zgodne z KMP, więc setQueryExecutor() Room Interfejs API jest niedostępny dla popularnych źródeł. Zamiast tego RoomDatabase musi skonfigurować za pomocą CoroutineContext. Kontekst można określić za pomocą funkcji RoomDatabase.Builder.setCoroutineContext(), jeśli nie ustawisz żadnej, RoomDatabase będzie domyślnie używać Dispatchers.IO.

Ustaw sterownik SQLite

Po przeniesieniu zastosowań pomocniczych SQLite do interfejsów SQLite Driver API sterownik należy skonfigurować za pomocą programu RoomDatabase.Builder.setDriver. zalecany sterownik to BundledSQLiteDriver. Zobacz Implementacje sterowników dla: opisy dostępnych wdrożeń sterowników.

Niestandardowy zasób SupportSQLiteOpenHelper.Factory skonfigurowany za pomocą Parametry RoomDatabase.Builder.openHelperFactory() nie są obsługiwane w KMP, funkcji udostępnianych przez niestandardowy otwarty pomocnik trzeba będzie ponownie zaimplementować za pomocą Interfejsy SQLite Driver.

Przenoszenie deklaracji dotyczących sal

Po zakończeniu większości etapów migracji można przenieść pokój do wspólnego zbioru źródłowego. Pamiętaj, że strategie expect / actual mogą służą do stopniowego przenoszenia definicji związanych z salami. Jeśli na przykład nie wszystkie blokujące funkcje DAO można przenieść do zawieszania funkcji, istnieje możliwość zadeklarować interfejs z adnotacjami expect @Dao, który jest pusty w kodzie wspólnym, ale zawiera funkcje blokujące w Androidzie.

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