このドキュメントでは、既存の 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 でリファクタリングされています。下位互換性が確保されており
RoomDatabase
が SupportSQLiteOpenHelper.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
は使用できません(コールバックのオーバーライドでなど)(onMigrate
、
onCreate
など)。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 を使用できません。代わりに、RoomDatabase
を
CoroutineContext
で設定する。コンテキストは以下を使用して設定できます:
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
}