Room 2.x'ten Room 3.0'a taşıma

Room 3.0, kitaplığı Kotlin'e öncelik verecek şekilde dönüştüren önemli bir ana sürüm güncellemesidir. Kotlin Multiplatform (KMP) desteği sunar, Kotlin Symbol Processing (KSP) gerektirir ve eşzamansız işlemler için coroutine'leri zorunlu kılar.

Mevcut Room 2.x uygulamaları ve geçişli bağımlılıklarla uyumluluk sorunlarını önlemek için Room 3.0 yeni bir pakette bulunur: androidx.room3.

Bu kılavuzda, mevcut Room 2.x uygulamanızı Room 3.0'a taşımak için gereken adımlar açıklanmaktadır.

Room 3.0'daki önemli değişiklikler

Taşıma işlemine başlamadan önce temel farklılıklar hakkında bilgi edinin:

  • Yeni paket ve yapılar: Tüm sınıflar androidx.room3 içinde yer alır. Yadigârlar, room3 önekini kullanır (ör. androidx.room3:room3-runtime).
  • Yalnızca Kotlin ve KSP: Room 3.0, Java kodu oluşturmayı desteklemez. KAPT veya Java ek açıklama işlemcileri yerine KSP'yi kullanın. Room 3.0, giriş olarak Java kaynaklarını desteklemeye devam ediyor.
  • Öncelikli olarak eş yordamlar: Gözlemlenebilir türler hariç, DAO işlevleri suspend işlevleri olmalıdır. CoroutineContext, yürütücülerin yerini alır.
  • No SupportSQLite: SQLiteDriver API'leri Room'u destekler. Oda, temel API'lerden SupportSQLiteDatabase kaldırılıyor.
  • API değişiklikleri: Taşıma işlemleri ve veritabanı geri çağırmaları, SupportSQLiteDatabase yerine SQLiteConnection kullanır.
  • Reaktif türler için dönüştürücüler: RxJava, LiveData, Guava ve Paging dönüş türleri için @DaoReturnTypeConverters kaydetmeniz gerekir.

İki ayrı aşamada geçiş yapmanızı öneririz: İlk olarak Room 2.x'te kod tabanınızı hazırlayıp modernize edin, ardından Room 3.0'a geçin.


Room 2.x'te hazırlık ve modernleştirme

Room 3.0'a geçmeden önce, Room 2.8 gibi mevcut Room 2.x sürümüne güncelleyerek modernizasyon çalışmalarının çoğunu gerçekleştirebilirsiniz. Room 2.8, Kotlin Multiplatform'u (KMP) destekler ve Room 3.0'ın kullandığı birçok sürücü API'si içerir.

Room 2.8 ve sonraki sürümlere güncelleme

Mevcut Room 2.x sürümünü kullanmak için derleme yapılandırmanızı güncelleyin:

[versions]
room2 = "2.8.4" # Use the current Room 2.8 version

[libraries]
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room2" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room2" }

KAPT'den KSP'ye taşıma

Room 3.0, Java ek açıklama işlemcilerini veya KAPT'yi desteklemez. Kotlin Symbol Processing (KSP) kullanmanız gerekir. Bu geçişi Room 2.x'te kalmaya devam ederken yapabilirsiniz.

  1. Modülünüzün build.gradle.kts bölümünde KSP eklentisini uygulayın:

    plugins {
        id("com.google.devtools.ksp") version "<ksp_version>"
    }
    

    KSP sürümünün Kotlin sürümünüzle uyumlu olduğundan emin olun.

  2. Oda derleyicisi bağımlılığı için kapt veya annotationProcessor yerine ksp kullanın:

    dependencies {
        implementation(libs.androidx.room.runtime)
        ksp(libs.androidx.room.compiler)
    }
    

Eş yordamları kullanma

Room 3.0, eşzamansız işlemler için eş yordamlar gerektirir.

  • DAO'larınızı güncelleyin: Flow veya RxJava türleri gibi gözlemlenebilir bir reaktif tür döndürmedikleri sürece tüm DAO işlevleri suspend işlevleri olmalıdır.
// Before (Blocking)
@Dao
interface UserDao {
    @Query("SELECT * FROM User")
    fun getAll(): List<User>
}

// After (Suspend)
@Dao
interface UserDao {
    @Query("SELECT * FROM User")
    suspend fun getAll(): List<User>
}
  • RoomDatabase'nizi veritabanı işlemlerini gerçekleştirmek için özel bir Executor ile yapılandırdıysanız oluşturucudaki setQueryCoroutineContext'ü kullanarak CoroutineContext'e taşıyın:
Room.databaseBuilder<AppDatabase>(context, "db")
    .setQueryCoroutineContext(Dispatchers.IO)
    .build()

Sürücü API'lerini kullanın ve Support SQLite'den kaçının

Room 3.0, SQLiteDriver tarafından tam olarak desteklenir ve temel API'lerinde artık SupportSQLiteDatabase desteklenmez.

Veritabanı oluşturucunuzda setDriver ayarlamak için SQLiteDriver işlevini çağırmazsanız Room 2.8, hem Support SQLite hem de Driver API'lerinin çalıştığı bir uyumluluk modunda çalışır. Bu uyumluluk modu, sürücüyü etkinleştirmeden önce kod tabanınızı kademeli olarak dönüştürmenize olanak tanır.

  • Dönüşüm taşıma işlemleri: Migration ve AutoMigrationSpec alt sınıflarınızı SupportSQLiteDatabase yerine SQLiteConnection kullanacak şekilde taşıyın.
// Before (SupportSQLiteDatabase)
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL")
    }
}

// After (SQLiteConnection - Room 2.8)
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.execSQL

val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(connection: SQLiteConnection) {
        connection.execSQL(
            "ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL"
        )
    }
}
  • Veritabanı geri çağırmalarını dönüştürme: RoomDatabase.Callback uygulamalarını SQLiteConnection kullanacak şekilde güncelleyin:
// Before (SupportSQLiteDatabase)
val callback = object : RoomDatabase.Callback() {
    override fun onCreate(db: SupportSQLiteDatabase) {
        // ...
    }
}

// After (SQLiteConnection - Room 2.8)
val callback = object : RoomDatabase.Callback() {
    override fun onCreate(connection: SQLiteConnection) {
        // ...
    }
}
  • @RawQuery DAO işlevlerini dönüştürme: @RawQuery ile açıklama eklenmiş işlevler için SupportSQLiteQuery yerine RoomRawQuery kullanın:
// Before (SupportSQLiteQuery)
@Dao
interface UserDao {
    @RawQuery
    fun getUser(query: SupportSQLiteQuery): User
}

// After (RoomRawQuery)
@Dao
interface UserDao {
    @RawQuery
    suspend fun getUser(query: RoomRawQuery): User
}

Çalışma zamanında RoomRawQuery oluşturabilirsiniz:

val query = RoomRawQuery(
    sql = "SELECT * FROM User WHERE id = ?",
    onBindStatement = { statement ->
        statement.bindInt(1, userId)
    }
)
  • İşlem API'lerini dönüştürme: Yalnızca Android'e özel withTransaction ve runInTransaction bloklarını withWriteTransaction veya withReadTransaction ile değiştirin:
// Before (withTransaction)
db.withTransaction {
    // perform database operations
}

// After (withWriteTransaction - Room 2.8)
import androidx.room.withWriteTransaction

db.withWriteTransaction {
    // perform database operations
}

İşlem bağlantısına doğrudan düşük düzeyde erişmeniz gerekiyorsa useWriterConnection ile immediateTransaction de kullanabilirsiniz.

  • SupportSQLiteDatabase doğrudan kullanmaktan kaçının: Hâlâ SupportSQLiteDatabase gerektiren kapsamlı bir eski kodunuz varsa ve henüz bunu taşımadıysanız androidx.room:room-sqlite-wrapper uyumluluk yapısını kullanın:
dependencies {
    implementation("androidx.room:room-sqlite-wrapper:$roomVersion")
}

Ardından, getSupportWrapper kullanarak Room veritabanı örneğinizden SupportSQLiteDatabase elde edin:

import androidx.room.support.getSupportWrapper

val legacyDb: SupportSQLiteDatabase = roomDatabase.getSupportWrapper()
  • SQLite sürücüsünü ayarlayın: Tüm Room API kullanımlarını sürücü API'lerine taşıdıktan sonra BundledSQLiteDriver veya AndroidSQLiteDriver gibi bir sürücüyü RoomDatabase oluşturucunuzda setDriver'i çağırarak yapılandırın:
import androidx.sqlite.driver.bundled.BundledSQLiteDriver

val db = Room.databaseBuilder<AppDatabase>(context, "db")
    .setDriver(BundledSQLiteDriver())
    .build()

Akış tabanlı geçersiz kılma takibini kullanma

Room 2.8, InvalidationTracker.createFlow API'sini kullanıma sunar. Bu API'yi, Room 2.x'te kalmaya devam ederken eski InvalidationTracker.Observer uygulamalarından geçiş yapmak için kullanın. Bu, kod tabanınızı Observer'ı tamamen kaldıran Room 3.0'a hazırlar.

// Before (InvalidationTracker.Observer)
val observer = object : InvalidationTracker.Observer("User") {
    override fun onInvalidated(tables: Set<String>) {
        // reload user data
    }
}
db.invalidationTracker.addObserver(observer)

// After (createFlow - Room 2.8)
val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
    userDao.getAllUsers()
}

Room 3.0'a taşıma

Uygulamanızı Room 2.x'te modernleştirdikten sonra Room 3.0'a geçmek için bağımlılıkları, paket içe aktarmalarını ve veritabanı geri çağırmalarını güncellemeniz gerekir.

Bağımlıları ve paket içe aktarmalarını güncelleme

  • Derleme yapılandırmanızda androidx.room bağımlılıklarını androidx.room3 ile değiştirin:
[versions]
room3 = "3.0.0" # Use the current Room 3.0 version

[libraries]
androidx-room3-runtime = { module = "androidx.room3:room3-runtime", version.ref = "room3" }
androidx-room3-compiler = { module = "androidx.room3:room3-compiler", version.ref = "room3" }
  • Bağımlılıklar bloğunuzu güncelleyin:
dependencies {
    implementation(libs.androidx.room3.runtime)
    ksp(libs.androidx.room3.compiler)
}
  • Paket içe aktarma işlemlerinizi güncelleyin. import androidx.room.* yerine import androidx.room3.* koyun.

Tür dönüştürücü API'lerini güncelleme

Room 3.0, sütun değerlerini dönüştürme konusundaki kullanımlarını netleştirmek ve DAO dönüş türü dönüştürücülerle karışıklığı önlemek için tür dönüştürücü API'lerini yeniden adlandırır.

Kod tabanınızdaki aşağıdaki ek açıklamaları ve işlevleri güncelleyin:

  • @TypeConverter öğesini @ColumnTypeConverter olarak yeniden adlandırın.
  • @TypeConverters öğesini @ColumnTypeConverters olarak yeniden adlandırın.
  • @ProvidedTypeConverter öğesini @ProvidedColumnTypeConverter olarak yeniden adlandırın.
  • RoomDatabase.Builder.addTypeConverter öğesini addColumnTypeConverter olarak yeniden adlandırın.

Örnek:

// Before
@ProvidedTypeConverter
class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? = ...
}

@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase()

val db = Room.databaseBuilder<AppDatabase>(...)
  .addTypeConverter(convertersInstance)
  .build()
// After
import androidx.room3.ColumnTypeConverter
import androidx.room3.ColumnTypeConverters
import androidx.room3.ProvidedColumnTypeConverter

@ProvidedColumnTypeConverter
class Converters {
  @ColumnTypeConverter
  fun fromTimestamp(value: Long?): Date? = ...
}

@Database(entities = [User::class], version = 1)
@ColumnTypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase()

val db = Room.databaseBuilder<AppDatabase>(...)
  .addColumnTypeConverter(convertersInstance)
  .build()

İşlevleri askıya almak için geri çağırmaları güncelleyin

Room 3.0'da veritabanı geri çağırmaları ve taşımaları SQLiteConnection kullanır ve suspend işlevleridir.

  • Manuel Migration sınıflarınızı güncelleyin:
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.async.executeSQL

val MIGRATION_1_2 = object : Migration(1, 2) {
    override suspend fun migrate(connection: SQLiteConnection) {
        connection.executeSQL(
            "ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL"
        )
    }
}
  • RoomDatabase.Callback uygulamalarınızı güncelleyin:
val callback = object : RoomDatabase.Callback() {
    override suspend fun onCreate(connection: SQLiteConnection) {
        // ...
    }
}

DAO dönüş türü dönüştürücülerini kaydetme

Room 3.0'da RxJava, LiveData, Guava ve Paging gibi reaktif dönüş türleri, @DaoReturnTypeConverters kullanarak DAO dönüş türü dönüştürücülerini kaydetmenizi gerektirir.

import androidx.room3.paging.PagingSourceDaoReturnTypeConverter

@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface UserDao {
    @Query("SELECT * FROM User")
    fun getAllPaginated(): PagingSource<Int, User>
}
  • Sayfalama (PagingSource): androidx.room3:room3-paging yapısından PagingSourceDaoReturnTypeConverter kaydettirin.
  • RxJava (Observable, Flowable, Single, Maybe, Completable): androidx.room3:room3-rxjava3 yapıtından RxDaoReturnTypeConverters öğesini kaydedin.
  • Guava (ListenableFuture): androidx.room3:room3-guava yapısından GuavaDaoReturnTypeConverter öğesini kaydedin.
  • LiveData (LiveData): androidx.room3:room3-livedata yapısından LiveDataDaoReturnTypeConverter öğesini kaydedin.

InvalidationTracker Observer'ın kaldırıldığını doğrulama

Room 3.0, InvalidationTracker.Observer ve addObserver ile removeObserver gibi ilgili kayıt yöntemlerini tamamen kaldırır.

1. aşamada henüz coroutine akışlarına geçiş yapmadıysanız tüm Observer kullanımlarını createFlow'e taşımanız gerekir:

val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
    userDao.getAllUsers()
}