Room을 Kotlin 멀티플랫폼으로 이전

이 문서에서는 기존 Room 구현을 하나의 Room으로 이전하는 방법을 설명합니다. 는 Kotlin 멀티플랫폼 (KMP)을 사용합니다.

기존 Android 코드베이스의 Room 사용을 공통 공유 KMP로 이전 모듈의 난이도는 사용된 Room API에 따라 크게 다를 수 있습니다. 코드베이스는 이미 코루틴을 사용하고 있습니다. 이 섹션에서는 몇 가지 지침과 팁을 제공합니다. 일반적인 모듈로 이전하려고 할 때 발생합니다.

먼저 API 간의 차이점과 누락되는 부분을 숙지하는 것이 중요합니다. KMP 버전 간의 차이점과 더불어 확인할 수 있습니다 본질적으로 성공적인 마이그레이션에는 리팩토링이 수반됩니다 SupportSQLite* API 사용 및 SQLite Driver API로 대체 또한 Room 선언 (@Database 주석이 달린 클래스, DAO, 항목 등)를 공통 코드로 변환합니다.

계속하기 전에 다음 정보를 다시 검토하세요.

다음 섹션에서는 성공적인 캠페인 운영에 필요한 다양한 단계를 살펴보겠습니다

지원 SQLite에서 SQLite 드라이버로 이전

androidx.sqlite.db의 API는 Android 전용이며 모든 용도는 다음과 같아야 합니다. SQLite Driver API로 리팩터링되었습니다. 이전 버전과의 호환성 및 RoomDatabaseSupportSQLiteOpenHelper.Factory로 구성됩니다 (즉, SQLiteDriver가 설정되지 않은 경우) Room은 '호환성 모드'에서 동작함 각 항목의 의미는 다음과 같습니다. SQLite API와 SQLite Driver API가 모두 예상대로 작동합니다. 이렇게 하면 모든 지원 SQLite를 변환할 필요가 없도록 증분 이전 한 번의 변경으로 SQLite Driver에 대한 사용법이 추가되었습니다.

다음 예는 Support 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 드라이버 (대상)

결과가 없는 쿼리 실행

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 드라이버 (대상)

트랜잭션 실행 (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 드라이버 (대상)

마이그레이션 서브클래스

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 함수를 정지 함수로 변환

Room의 KMP 버전은 코루틴을 사용하여 I/O를 실행합니다. 구성된 CoroutineContext에 대한 작업 즉, 차단 함수로 차단 DAO 함수를 이전해야 합니다.

DAO 차단 함수 (소스)

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

DAO 함수 정지 (to)

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

기존 DAO 차단 함수를 정지 함수로 이전하는 것은 기존 코드베이스에 아직 코루틴이 통합되지 않으면 복잡해질 수 있습니다. 코루틴 사용을 시작하려면 Android의 코루틴을 참고하세요. 코드베이스에 있습니다

반응형 반환 유형을 Flow로 변환

모든 DAO 함수가 정지 함수일 필요는 없습니다. 반환하는 DAO 함수 LiveData 또는 RxJava의 Flowable와 같은 반응형 유형은 변환해서는 안 됩니다. 함수를 정지할 수 있습니다. 그러나 LiveData와 같은 일부 유형은 KMP가 아닙니다. 있습니다. 반응형 반환 유형이 있는 DAO 함수는 코루틴 플로우가 있습니다.

호환되지 않는 KMP 유형 (보낸사람)

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

호환되는 KMP 유형 (대상)

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

개발자 콘솔에서 Flow를 사용하려면 Android의 Flow를 제공합니다

코루틴 컨텍스트 설정 (선택사항)

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 함수를 정지 함수로 이전할 수 있으며 공통 코드에서 비어 있는 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
}