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
}