Odayı Kotlin Çoklu Platforma Taşıma

Bu belgede, mevcut bir Oda uygulamasının Kotlin Multiplatform (KMP) kullanan bir uygulamaya nasıl taşınacağı açıklanmaktadır.

Mevcut bir Android kod tabanında Oda kullanımlarını ortak bir paylaşılan KMP modülüne taşımanın zorluğu, kullanılan Room API'lerine veya kod tabanının zaten Coroutines kullanıp kullanmadığına bağlı olarak büyük ölçüde farklılık gösterebilir. Bu bölümde, Room kullanımlarını ortak bir modüle taşımaya çalışırken bazı yol gösterici bilgiler ve ipuçları sunulmaktadır.

Öncelikle, ilgili kurulumla birlikte Room'un Android sürümü ile KMP sürümü arasındaki farklılıkları ve eksik özellikleri öğrenmeniz önemlidir. Özetle başarılı bir taşıma işlemi, SupportSQLite* API'lerinin kullanımlarının yeniden düzenlenmesini ve bunların yerine SQLite Driver API'lerinin kullanılmasını ve Oda bildirimlerinin (@Database ek açıklamalı sınıf, DAO, varlıklar vb.) ortak koda taşınmasını gerektirir.

Devam etmeden önce aşağıdaki bilgileri tekrar inceleyin:

Sonraki bölümlerde, başarılı bir taşıma işlemi için gereken çeşitli adımlar açıklanmaktadır.

Support SQLite'tan SQLite Driver'a geçiş

androidx.sqlite.db API'leri yalnızca Android'de kullanılabilir ve tüm kullanımların SQLite Driver API'leri ile yeniden düzenlenmesi gerekir. Geriye dönük uyumluluk söz konusu olduğunda ve RoomDatabase, bir SupportSQLiteOpenHelper.Factory ile yapılandırıldığı (yani SQLiteDriver ayarlanmamış) sürece Oda, hem Support SQLite hem de SQLite Driver API'lerin beklendiği gibi çalıştığı "uyumluluk modunda" davranır. Bu, ek taşımalara olanak tanır. Böylece, tüm Support SQLite kullanımlarınızı tek bir değişiklikle SQLite Sürücüsü'ne dönüştürmeniz gerekmez.

Aşağıdaki örnekler, Support SQLite ve bunların SQLite Sürücüsü eşdeğerlerinin yaygın kullanımlarıdır:

SQLite desteği (kaynak)

Sonuç vermeyen bir sorgu yürüt

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

Sonuç olup bağımsız değişkeni olmayan bir sorgu yürüt

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

Sonuç ve bağımsız değişkenlerle sorgu yürütme

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

SQLite Sürücüsü (hedef)

Sonuç vermeyen bir sorgu yürüt

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

Sonuç olup bağımsız değişkeni olmayan bir sorgu yürüt

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

Sonuç ve bağımsız değişkenlerle sorgu yürütme

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

Veritabanı işlem API'leri doğrudan SupportSQLiteDatabase üzerinden beginTransaction(), setTransactionSuccessful() ve endTransaction() ile kullanılabilir. Bunlara Oda üzerinden de runInTransaction() üzerinden erişebilirsiniz. Bu kullanımları SQLite Driver API'lerine taşıyın.

SQLite desteği (kaynak)

İşlem gerçekleştirme (RoomDatabase kullanarak)

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

İşlem gerçekleştirme (SupportSQLiteDatabase kullanarak)

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

SQLite Sürücüsü (hedef)

İşlem gerçekleştirme (RoomDatabase kullanarak)

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

İşlem gerçekleştirme (SQLiteConnection kullanarak)

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")
}

Çeşitli geri çağırma geçersiz kılmalarının da sürücü eşdeğerlerine taşınması gerekir:

SQLite desteği (kaynak)

Taşıma alt sınıfları

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

Otomatik taşıma spesifikasyonu alt sınıfları

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

Veritabanı geri çağırma alt sınıfları

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

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

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

SQLite Sürücüsü (hedef)

Taşıma alt sınıfları

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

Otomatik taşıma spesifikasyonu alt sınıfları

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

Veritabanı geri çağırma alt sınıfları

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

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

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

Özetlemek gerekirse, geri çağırma geçersiz kılmaları (onMigrate, onCreate vb.) gibi RoomDatabase kullanılamadığında SQLiteDatabase kullanımlarını SQLiteConnection ile değiştirin. RoomDatabase varsa temel veritabanı bağlantısına RoomDatabase.openHelper.writtableDatabase yerine RoomDatabase.useReaderConnection ve RoomDatabase.useWriterConnection kullanarak erişin.

Engelleyen DAO işlevlerini askıya alma işlevlerine dönüştürme

Room'un KMP sürümü, yapılandırılmış CoroutineContext üzerinde G/Ç işlemleri gerçekleştirmek için eş çinlerden yararlanır. Bu nedenle, işlevleri askıya almak için engelleyen tüm DAO işlevlerini taşımanız gerekir.

DAO işlevini engelleme (gönderen)

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

DAO işlevi askıya alınıyor (alıcı)

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

Mevcut kod tabanı halihazırda eş yordamlar içermiyorsa mevcut DAO engelleme işlevlerinin askıya alma işlevlerine taşınması karmaşık olabilir. Kod tabanınızda eş yordamları kullanmaya başlamak için Android'deki Koutinler bölümüne bakın.

Reaktif dönüş türlerini Akışa dönüştürme

Bazı DAO işlevlerinin askıya alma işlevleri olması gerekmez. LiveData veya RxJava'nın Flowable öğesi gibi reaktif türleri döndüren DAO işlevleri, askıya alma işlevlerine dönüştürülmemelidir. Ancak LiveData gibi bazı türler KMP uyumlu değildir. Reaktif dönüş türlerine sahip DAO işlevleri, eş yordam akışlarına taşınmalıdır.

Uyumsuz KMP türü (gönderen)

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

Uyumlu KMP türü (alıcı)

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

Kod tabanınızda Akışları kullanmaya başlamak için Android'de Akışlar bölümüne bakın.

Ortak Bağlamı Belirleyin (İsteğe bağlı)

RoomDatabase, veritabanı işlemlerini gerçekleştirmek için RoomDatabase.Builder.setQueryExecutor() kullanan paylaşılan uygulama yürütücüleriyle isteğe bağlı olarak yapılandırılabilir. Yürütücüler KMP uyumlu olmadığından Room'un setQueryExecutor() API'si ortak kaynaklar için kullanılamaz. Bunun yerine RoomDatabase, CoroutineContext ile yapılandırılmalıdır. RoomDatabase.Builder.setCoroutineContext() kullanılarak bir bağlam ayarlanabilir. Herhangi bir içerik ayarlanmazsa RoomDatabase varsayılan olarak Dispatchers.IO kullanır.

SQLite Sürücüsü ayarlayın

Support SQLite kullanımları SQLite Driver API'lerine taşındıktan sonra RoomDatabase.Builder.setDriver kullanılarak bir sürücünün yapılandırılması gerekir. Önerilen sürücü: BundledSQLiteDriver. Kullanılabilir sürücü uygulamalarının açıklamaları için Sürücü uygulamaları bölümüne bakın.

RoomDatabase.Builder.openHelperFactory() kullanılarak yapılandırılan özel SupportSQLiteOpenHelper.Factory, KMP'de desteklenmez. Özel açık yardımcı tarafından sağlanan özelliklerin SQLite Sürücü arayüzleriyle yeniden uygulanması gerekir.

Oda bildirimlerini taşı

Taşıma adımlarının çoğu tamamlandıktan sonra Oda tanımları ortak bir kaynak kümesine taşınabilir. Oda ile ilgili tanımları aşamalı olarak taşımak için expect / actual stratejilerinin kullanılabileceğini unutmayın. Örneğin, tüm engelleme DAO işlevleri askıya alma işlevleri için taşınamazsa ortak kodda boş olan ancak Android'de engelleme işlevleri içeren bir expect @Dao ek açıklamalı arayüzü beyan edilebilir.

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