Room を Kotlin マルチプラットフォームに移行する

このドキュメントでは、既存の Room の実装を Kotlin マルチプラットフォーム(KMP)を使用する

既存の Android コードベースでの Room の使用状況を共通の共有 KMP に移行する 使用する Room API や Cloud Storage の設定によって、 コードベースではすでにコルーチンが使用されています。このセクションではガイダンスとヒントを示します。 Room の使用を共通モジュールに移行する際に注意すべき点を確認します。

まず、両者の違いと不足している知識を理解しておくことが Room の Android バージョンと KMP バージョンの間の機能に加え、 確認します。基本的に移行を成功させるには、リファクタリングが SupportSQLite* API の使用と SQLite Driver API への置き換え Room 宣言(@Database アノテーション付きのクラス、DAO、 共通のコードに変換できます。

続行する前に、次の情報を再確認してください。

以降のセクションでは、 移行します。

サポート SQLite から SQLite ドライバに移行する

androidx.sqlite.db の API は Android 専用です。使用する場合は、 SQLite Driver API でリファクタリングされています。下位互換性が確保されており RoomDatabaseSupportSQLiteOpenHelper.Factory で構成されている(つまり、 SQLiteDriver が設定されていない場合、Room は「互換モード」で動作ここで Support SQLite API と SQLite Driver API の両方が期待どおりに動作します。これにより、 サポート SQLite をすべて変換する必要がない増分移行 SQLite ドライバへの変更を 1 回変更する必要がありました。

次の例は、サポート SQLite とその SQLite の一般的な使用方法です 対応するドライバー:

SQLite(から)をサポートする

結果がないクエリを実行する

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

結果は引数なしでクエリを実行する

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

結果と引数を使用してクエリを実行する

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

SQLite ドライバ(to)

結果がないクエリを実行する

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

結果は引数なしでクエリを実行する

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

結果と引数を使用してクエリを実行する

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

データベース トランザクション API は、SupportSQLiteDatabase で直接使用できます。 beginTransaction()setTransactionSuccessful()endTransaction()runInTransaction() を使用して Room から選択することもできます。これらを移行する SQLite Driver API に渡します。

SQLite(から)をサポートする

トランザクションを実行する(RoomDatabase を使用)

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

トランザクションを実行する(SupportSQLiteDatabase を使用)

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

SQLite ドライバ(to)

トランザクションを実行する(RoomDatabase を使用)

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

トランザクションを実行する(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")
}

さまざまなコールバックのオーバーライドも、対応するドライバに移行する必要があります。

SQLite(から)をサポートする

移行のサブクラス

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

自動移行仕様のサブクラス

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

データベース コールバックのサブクラス

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

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

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

SQLite ドライバ(to)

移行のサブクラス

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

自動移行仕様のサブクラス

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

データベース コールバックのサブクラス

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

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

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

まとめると、SQLiteDatabase の使用を SQLiteConnection に置き換えます。 RoomDatabase は使用できません(コールバックのオーバーライドでなど)(onMigrateonCreate など)。RoomDatabase が利用可能な場合は、基盤となる RoomDatabase.useReaderConnection を使用したデータベース接続と RoomDatabase.useWriterConnection: RoomDatabase.openHelper.writtableDatabase

ブロッキング DAO 関数を suspend 関数に変換する

KMP バージョンの Room はコルーチンを使用して I/O を実行する 構成済みの CoroutineContext に対するオペレーション。つまり、 ブロッキング DAO 関数を suspend 関数に移行する必要があります。

DAO 関数のブロック(from)

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

DAO 関数の一時停止(to)

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

既存の DAO ブロッキング関数を suspend 関数に移行すると、 既存のコードベースにコルーチンがまだ組み込まれていない場合、複雑になります。 コルーチンの使用を開始するには、Android のコルーチンをご覧ください。 確認できます。

リアクティブの戻り値の型を Flow に変換する

すべての DAO 関数を suspend 関数である必要はありません。返される DAO 関数 LiveData や RxJava の Flowable などのリアクティブ型は変換しないでください suspend 関数になります。ただし、LiveData などの一部のタイプは KMP ではありません 対応しています。リアクティブ型の戻り値の型を含む DAO 関数は、 渡されます。

互換性のない KMP タイプ(移行元)

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

対応する KMP タイプ(~)

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

フローの使用を開始するには、Android のフローをご覧ください。 学びました。

コルーチンのコンテキストを設定する(省略可)

共有アプリケーションで RoomDatabase を構成することもできます。 RoomDatabase.Builder.setQueryExecutor() を使用してデータベースを実行するエグゼキュータ 必要があります。エグゼキュータは KMP と互換性がないため、Room の setQueryExecutor() 一般的なソースでは API を使用できません。代わりに、RoomDatabaseCoroutineContext で設定する。コンテキストは以下を使用して設定できます: RoomDatabase.Builder.setCoroutineContext(): 何も設定されていない場合、 RoomDatabase はデフォルトで Dispatchers.IO を使用します。

SQLite ドライバを設定する

SQLite 使用状況のサポートが SQLite Driver API に移行されると、 ドライバは、RoomDatabase.Builder.setDriver を使用して構成する必要があります。「 推奨されるドライバは BundledSQLiteDriver です。ドライバの実装を参照してください。 利用可能なドライバ実装の説明です。

次を使用してカスタム SupportSQLiteOpenHelper.Factory を構成しました RoomDatabase.Builder.openHelperFactory() は KMP、 カスタム オープン ヘルパーで提供される機能は、 SQLite ドライバ インターフェース。

会議室の宣言を移動する

移行ステップのほとんどが完了したら、Room を 共通のソースセットにマッピングできます。なお、expect / actual 戦略は、 Room 関連の定義を段階的に移行するために使用できます。たとえば ブロッキング DAO 関数は suspend 関数に移行できますが、 共通コードでは空であるが、expect @Dao アノテーション付きインターフェースを宣言する には、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
}