Room 3.0 เป็นการอัปเดตเวอร์ชันหลักที่เปลี่ยนไลบรารีให้เป็น Kotlin-first โดยรองรับ Kotlin Multiplatform (KMP), ต้องใช้ Kotlin Symbol Processing (KSP) และบังคับใช้ Coroutine สำหรับการดำเนินการแบบอะซิงโครนัส
Room 3.0 อยู่ในแพ็กเกจใหม่ androidx.room3 เพื่อป้องกันปัญหาความเข้ากันได้กับแอป Room 2.x ที่มีอยู่และการขึ้นต่อกันแบบถ่ายทอด
คู่มือนี้จะอธิบายขั้นตอนที่จำเป็นในการย้ายข้อมูลการติดตั้งใช้งาน Room 2.x ที่มีอยู่ไปยัง Room 3.0
การเปลี่ยนแปลงที่สำคัญใน Room 3.0
ก่อนเริ่มการย้ายข้อมูล โปรดทำความคุ้นเคยกับความแตกต่างที่สำคัญดังนี้
- แพ็กเกจและอาร์ติแฟกต์ใหม่: คลาสทั้งหมดอยู่ใน
androidx.room3อาร์ติแฟกต์ใช้คำนำหน้าroom3เช่นandroidx.room3:room3-runtime - Kotlin และ KSP เท่านั้น: Room 3.0 ไม่รองรับการสร้างโค้ด Java ให้ใช้ KSP แทน KAPT หรือโปรเซสเซอร์คำอธิบายประกอบ Java Room 3.0 ยังคงรองรับแหล่งที่มาของ Java เป็นอินพุต
- Coroutine เป็นอันดับแรก: ฟังก์ชัน DAO ต้องเป็นฟังก์ชัน
suspendยกเว้นประเภทที่ สังเกตได้CoroutineContextจะแทนที่ Executor - ไม่รองรับ SQLite:
SQLiteDriverAPI จะสำรองข้อมูล Room Room จะนำSupportSQLiteDatabaseออกจาก API หลัก - การเปลี่ยนแปลง API: การย้ายข้อมูลและการเรียกกลับของฐานข้อมูลใช้
SQLiteConnectionแทนSupportSQLiteDatabase - ตัวแปลงประเภทสำหรับประเภทรีแอกทีฟ: ประเภทการคืนค่า RxJava, LiveData, Guava และ Paging
กำหนดให้คุณต้องลงทะเบียน
@DaoReturnTypeConverters
เราขอแนะนำให้ย้ายข้อมูลเป็น 2 ระยะที่แตกต่างกัน โดยระยะแรกคือการเตรียมและปรับฐานของโค้ดให้ทันสมัยใน Room 2.x จากนั้นจึงเปลี่ยนไปใช้ Room 3.0
เตรียมและปรับให้ทันสมัยใน Room 2.x
ก่อนย้ายข้อมูลไปยัง Room 3.0 คุณสามารถทำงานปรับให้ทันสมัยส่วนใหญ่ได้โดยการอัปเดตเป็น Room 2.x เวอร์ชันปัจจุบัน เช่น Room 2.8 Room 2.8 รองรับ Kotlin Multiplatform หรือ KMP และมี API ของไดรเวอร์มากมายที่ Room 3.0 ใช้
อัปเดตเป็น Room 2.8 ขึ้นไป
อัปเดตการกำหนดค่าบิลด์เพื่อใช้ Room 2.x เวอร์ชันปัจจุบัน
[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 ไปยัง KSP
Room 3.0 ไม่รองรับโปรเซสเซอร์คำอธิบายประกอบ Java หรือ KAPT คุณต้องใช้ Kotlin Symbol Processing (KSP) คุณสามารถทำการเปลี่ยนนี้ในขณะที่ยังใช้ Room 2.x อยู่
ใน
build.gradle.ktsของโมดูล ให้ใช้ปลั๊กอิน KSP ดังนี้plugins { id("com.google.devtools.ksp") version "<ksp_version>" }ตรวจสอบว่าเวอร์ชัน KSP เข้ากันได้กับเวอร์ชัน Kotlin
แทนที่
kaptหรือannotationProcessorด้วยkspสำหรับทรัพยากร Dependency ของคอมไพเลอร์ Room ดังนี้dependencies { implementation(libs.androidx.room.runtime) ksp(libs.androidx.room.compiler) }
ใช้ Coroutine
Room 3.0 กำหนดให้ต้องใช้ Coroutine สำหรับการดำเนินการแบบอะซิงโครนัส
- อัปเดต DAO: ฟังก์ชัน DAO ทั้งหมดต้องเป็นฟังก์ชัน
suspendยกเว้นฟังก์ชันที่แสดงผลประเภทรีแอกทีฟที่สังเกตได้ เช่น ประเภทFlowหรือ RxJava
// 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ด้วยExecutorที่กำหนดเองเพื่อดำเนินการ ฐานข้อมูล ให้ย้ายข้อมูลไปยังCoroutineContextโดยใช้setQueryCoroutineContextใน Builder ดังนี้
Room.databaseBuilder<AppDatabase>(context, "db")
.setQueryCoroutineContext(Dispatchers.IO)
.build()
ใช้ API ของไดรเวอร์และหลีกเลี่ยง Support SQLite
Room 3.0 ได้รับการสนับสนุนอย่างเต็มที่จาก SQLiteDriver และไม่รองรับ SupportSQLiteDatabase ใน API หลักอีกต่อไป
หากคุณไม่ได้เรียก setDriver เพื่อตั้งค่า SQLiteDriver ใน Builder ของฐานข้อมูล Room 2.8 จะทำงานในโหมดความเข้ากันได้ที่ทั้ง Support SQLite และ API ของไดรเวอร์ทำงานได้ โหมดความเข้ากันได้นี้ช่วยให้คุณแปลงฐานของโค้ดได้ทีละน้อยก่อนที่จะเปิดใช้ไดรเวอร์
- แปลงการย้ายข้อมูล: ย้ายข้อมูล
MigrationและAutoMigrationSpecคลาสย่อยเพื่อใช้SQLiteConnectionแทนSupportSQLiteDatabase
// 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"
)
}
}
- แปลงการเรียกกลับของฐานข้อมูล: อัปเดต
RoomDatabase.Callbackการติดตั้งใช้งานเพื่อใช้SQLiteConnectionดังนี้
// 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) {
// ...
}
}
- แปลงฟังก์ชัน DAO ของ
@RawQuery: สำหรับฟังก์ชันที่มีคำอธิบายประกอบด้วย@RawQueryให้ใช้RoomRawQueryแทนSupportSQLiteQueryดังนี้
// Before (SupportSQLiteQuery)
@Dao
interface UserDao {
@RawQuery
fun getUser(query: SupportSQLiteQuery): User
}
// After (RoomRawQuery)
@Dao
interface UserDao {
@RawQuery
suspend fun getUser(query: RoomRawQuery): User
}
คุณสามารถสร้าง RoomRawQuery ได้ในรันไทม์ ดังนี้
val query = RoomRawQuery(
sql = "SELECT * FROM User WHERE id = ?",
onBindStatement = { statement ->
statement.bindInt(1, userId)
}
)
- แปลง API ของธุรกรรม: แทนที่บล็อก
withTransactionและrunInTransactionที่ใช้ได้เฉพาะใน Android ด้วยwithWriteTransactionหรือwithReadTransactionดังนี้
// Before (withTransaction)
db.withTransaction {
// perform database operations
}
// After (withWriteTransaction - Room 2.8)
import androidx.room.withWriteTransaction
db.withWriteTransaction {
// perform database operations
}
หากต้องการเข้าถึงการเชื่อมต่อธุรกรรมระดับต่ำโดยตรง คุณยังใช้ useWriterConnection กับ immediateTransaction ได้ด้วย
- หลีกเลี่ยงการใช้
SupportSQLiteDatabaseโดยตรง: หากคุณมีโค้ดเดิมจำนวนมากที่ยังต้องใช้SupportSQLiteDatabaseและยังย้ายข้อมูลไม่ได้ ให้ใช้อาร์ติแฟกต์ความเข้ากันได้androidx.room:room-sqlite-wrapperดังนี้
dependencies {
implementation("androidx.room:room-sqlite-wrapper:$roomVersion")
}
จากนั้นใช้ getSupportWrapper เพื่อรับ SupportSQLiteDatabase จากอินสแตนซ์ฐานข้อมูล Room ดังนี้
import androidx.room.support.getSupportWrapper
val legacyDb: SupportSQLiteDatabase = roomDatabase.getSupportWrapper()
- ตั้งค่าไดรเวอร์ SQLite: หลังจากย้ายข้อมูลการใช้งาน Room API ทั้งหมดไปยัง API ของไดรเวอร์แล้ว ให้กำหนดค่าไดรเวอร์ เช่น
BundledSQLiteDriverหรือAndroidSQLiteDriverโดยเรียกsetDriverใน Builder ของRoomDatabaseดังนี้
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
val db = Room.databaseBuilder<AppDatabase>(context, "db")
.setDriver(BundledSQLiteDriver())
.build()
ใช้การติดตามการระบุว่าไม่ถูกต้องตาม Flow
Room 2.8 ขอแนะนำ API InvalidationTracker.createFlow ใช้ API นี้เพื่อย้ายข้อมูลจากการติดตั้งใช้งาน InvalidationTracker.Observer เดิมในขณะที่ยังใช้ Room 2.x อยู่ ซึ่งจะเตรียมโค้ดเบสสำหรับ Room 3.0 ที่นำ Observer ออกอย่างสมบูรณ์
// 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
เมื่อปรับแอปพลิเคชันให้ทันสมัยใน Room 2.x แล้ว การเปลี่ยนไปใช้ Room 3.0 จะเกี่ยวข้องกับการอัปเดตการขึ้นต่อกัน การนำเข้าแพ็กเกจ และการเรียกกลับของฐานข้อมูล
อัปเดตการขึ้นต่อกันและการนำเข้าแพ็กเกจ
- ในการกำหนดค่าบิลด์ ให้แทนที่การขึ้นต่อกัน
androidx.roomด้วยandroidx.room3ดังนี้
[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" }
- อัปเดตบล็อกการขึ้นต่อกัน ดังนี้
dependencies {
implementation(libs.androidx.room3.runtime)
ksp(libs.androidx.room3.compiler)
}
- อัปเดตการนำเข้าแพ็กเกจ แทนที่
import androidx.room.*ด้วยimport androidx.room3.*
อัปเดต API ของตัวแปลงประเภท
Room 3.0 เปลี่ยนชื่อ API ของตัวแปลงประเภทเพื่อชี้แจงการใช้งานสำหรับการแปลงค่าคอลัมน์และเพื่อหลีกเลี่ยงความสับสนกับตัวแปลงประเภทการคืนค่า DAO
อัปเดตคำอธิบายประกอบและฟังก์ชันต่อไปนี้ในฐานของโค้ด
- เปลี่ยนชื่อ
@TypeConverterเป็น@ColumnTypeConverter - เปลี่ยนชื่อ
@TypeConvertersเป็น@ColumnTypeConverters - เปลี่ยนชื่อ
@ProvidedTypeConverterเป็น@ProvidedColumnTypeConverter - เปลี่ยนชื่อ
RoomDatabase.Builder.addTypeConverterเป็นaddColumnTypeConverter
ตัวอย่าง
// 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()
อัปเดตการเรียกกลับเป็นฟังก์ชัน suspend
ใน Room 3.0 การเรียกกลับของฐานข้อมูลและการย้ายข้อมูลใช้ SQLiteConnection และเป็นฟังก์ชัน suspend
- อัปเดตคลาส
Migrationด้วยตนเอง ดังนี้
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ดังนี้
val callback = object : RoomDatabase.Callback() {
override suspend fun onCreate(connection: SQLiteConnection) {
// ...
}
}
ลงทะเบียนตัวแปลงประเภทการแสดงผล DAO
ใน Room 3.0 ประเภทการคืนค่ารีแอกทีฟ เช่น RxJava, LiveData, Guava และ เพจจิ้ง กำหนดให้คุณต้องลงทะเบียนตัวแปลงประเภทการแสดงผล DAO โดยใช้ @DaoReturnTypeConverters
import androidx.room3.paging.PagingSourceDaoReturnTypeConverter
@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface UserDao {
@Query("SELECT * FROM User")
fun getAllPaginated(): PagingSource<Int, User>
}
- เพจจิ้ง (
PagingSource): รีจิสเตอร์PagingSourceDaoReturnTypeConverterจากอาร์ติแฟกต์androidx.room3:room3-paging - RxJava (
Observable,Flowable,Single,Maybe,Completable): ลงทะเบียนRxDaoReturnTypeConvertersจากอาร์ติแฟกต์androidx.room3:room3-rxjava3 - Guava (
ListenableFuture): ลงทะเบียนGuavaDaoReturnTypeConverterจากอาร์ติแฟกต์androidx.room3:room3-guava - LiveData (
LiveData): ลงทะเบียนLiveDataDaoReturnTypeConverterจากอาร์ติแฟกต์androidx.room3:room3-livedata
ยืนยันการนำ InvalidationTracker Observer ออก
Room 3.0 นำ InvalidationTracker.Observer และเมธอดการลงทะเบียนที่เกี่ยวข้อง เช่น addObserver และ removeObserver ออกอย่างสมบูรณ์
หากคุณยังไม่ได้เปลี่ยนไปใช้ Flow ของ Coroutine ใน
ระยะที่ 1 คุณต้องย้ายข้อมูลการใช้งาน Observer ทั้งหมดไปยัง
createFlow ดังนี้
val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
userDao.getAllUsers()
}