Tài liệu này mô tả cách di chuyển mô-đun triển khai Room hiện tại sang một sử dụng Kotlin Multiplatform (KMP).
Di chuyển việc sử dụng Room trong cơ sở mã Android hiện có sang một KMP dùng chung chung mô-đun có thể khác nhau nhiều về độ khó tuỳ thuộc vào các API Room được sử dụng hoặc việc cơ sở mã đã sử dụng Coroutine. Phần này cung cấp một số hướng dẫn và mẹo khi cố gắng di chuyển hoạt động sử dụng Room sang một mô-đun chung.
Trước tiên, bạn cần làm quen với những điểm khác biệt và
giữa phiên bản Android của Room và phiên bản KMP cùng với
quá trình thiết lập liên quan. Về bản chất, quá trình di chuyển thành công bao gồm việc tái cấu trúc
việc sử dụng API SupportSQLite*
và thay thế chúng bằng API Trình điều khiển SQLite
cùng với việc di chuyển các phần khai báo Room (lớp có chú giải @Database
, DAO,
thực thể, v.v.) vào mã chung.
Kiểm tra lại thông tin sau đây trước khi tiếp tục:
Các phần tiếp theo mô tả các bước khác nhau cần thực hiện để thành công di chuyển.
Di chuyển từ Hỗ trợ SQLite sang Trình điều khiển SQLite
Các API trong androidx.sqlite.db
chỉ dành cho Android và cần phải
được tái cấu trúc bằng API Trình điều khiển SQLite. Để có khả năng tương thích ngược và miễn là
RoomDatabase
được định cấu hình bằng SupportSQLiteOpenHelper.Factory
(tức là
không có SQLiteDriver
nào được đặt), thì Room sẽ hoạt động ở "chế độ tương thích" ở đâu
cả API hỗ trợ SQLite và API Trình điều khiển SQLite đều hoạt động như dự kiến. Điều này cho phép
di chuyển dần dần để bạn không cần chuyển đổi tất cả SQLite hỗ trợ
vào Trình điều khiển SQLite trong một thay đổi duy nhất.
Sau đây là các ví dụ về cách sử dụng phổ biến của SQLite hỗ trợ và SQLite của các ngôn ngữ đó Bản sao của tài xế:
Hỗ trợ SQLite (từ)
Thực thi truy vấn mà không có kết quả
val database: SupportSQLiteDatabase = ...
database.execSQL("ALTER TABLE ...")
Thực thi truy vấn có kết quả nhưng không có đối số
val database: SupportSQLiteDatabase = ...
database.query("SELECT * FROM Pet").use { cursor ->
while (cusor.moveToNext()) {
// read columns
cursor.getInt(0)
cursor.getString(1)
}
}
Thực thi truy vấn có kết quả và đối số
database.query("SELECT * FROM Pet WHERE id = ?", id).use { cursor ->
if (cursor.moveToNext()) {
// row found, read columns
} else {
// row not found
}
}
Trình điều khiển SQLite (đến)
Thực thi truy vấn mà không có kết quả
val connection: SQLiteConnection = ...
connection.execSQL("ALTER TABLE ...")
Thực thi truy vấn có kết quả nhưng không có đối số
val connection: SQLiteConnection = ...
connection.prepare("SELECT * FROM Pet").use { statement ->
while (statement.step()) {
// read columns
statement.getInt(0)
statement.getText(1)
}
}
Thực thi truy vấn có kết quả và đối số
connection.prepare("SELECT * FROM Pet WHERE id = ?").use { statement ->
statement.bindInt(1, id)
if (statement.step()) {
// row found, read columns
} else {
// row not found
}
}
Các API giao dịch cơ sở dữ liệu có sẵn trực tiếp trong SupportSQLiteDatabase
nhờ
beginTransaction()
, setTransactionSuccessful()
và endTransaction()
.
Bạn cũng có thể dùng runInTransaction()
thông qua Room. Di chuyển các mã này
cho API Trình điều khiển SQLite.
Hỗ trợ SQLite (từ)
Thực hiện giao dịch (sử dụng RoomDatabase
)
val database: RoomDatabase = ...
database.runInTransaction {
// perform database operations in transaction
}
Thực hiện giao dịch (sử dụng SupportSQLiteDatabase
)
val database: SupportSQLiteDatabase = ...
database.beginTransaction()
try {
// perform database operations in transaction
database.setTransactionSuccessful()
} finally {
database.endTransaction()
}
Trình điều khiển SQLite (đến)
Thực hiện giao dịch (sử dụng RoomDatabase
)
val database: RoomDatabase = ...
database.useWriterConnection { transactor ->
transactor.immediateTransaction {
// perform database operations in transaction
}
}
Thực hiện giao dịch (sử dụng 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")
}
Bạn cũng cần di chuyển nhiều chế độ ghi đè lệnh gọi lại sang các trình điều khiển tương ứng:
Hỗ trợ SQLite (từ)
Lớp con trong quá trình di chuyển
object Migration_1_2 : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
// ...
}
}
Lớp con thông số kỹ thuật tự động di chuyển
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
// ...
}
}
Lớp con của lệnh gọi lại cơ sở dữ liệu
object MyRoomCallback : RoomDatabase.Callback {
override fun onCreate(db: SupportSQLiteDatabase) {
// ...
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
// ...
}
override fun onOpen(db: SupportSQLiteDatabase) {
// ...
}
}
Trình điều khiển SQLite (đến)
Lớp con trong quá trình di chuyển
object Migration_1_2 : Migration(1, 2) {
override fun migrate(connection: SQLiteConnection) {
// ...
}
}
Lớp con thông số kỹ thuật tự động di chuyển
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(connection: SQLiteConnection) {
// ...
}
}
Lớp con của lệnh gọi lại cơ sở dữ liệu
object MyRoomCallback : RoomDatabase.Callback {
override fun onCreate(connection: SQLiteConnection) {
// ...
}
override fun onDestructiveMigration(connection: SQLiteConnection) {
// ...
}
override fun onOpen(connection: SQLiteConnection) {
// ...
}
}
Tóm lại, hãy thay thế các cách sử dụng SQLiteDatabase
bằng SQLiteConnection
khi một
RoomDatabase
không có sẵn, chẳng hạn như trong ghi đè lệnh gọi lại (onMigrate
,
onCreate
, v.v.). Nếu có RoomDatabase
, hãy truy cập vào cơ sở
kết nối cơ sở dữ liệu bằng cách sử dụng RoomDatabase.useReaderConnection
và
RoomDatabase.useWriterConnection
thay vì
RoomDatabase.openHelper.writtableDatabase
.
Chuyển đổi các hàm DAO chặn thành tạm ngưng hàm tạm ngưng
Phiên bản KMP của Room dựa vào coroutine để thực hiện I/O
thao tác trên CoroutineContext
đã định cấu hình. Điều này có nghĩa là bạn
cần di chuyển mọi hàm DAO chặn để tạm ngưng các hàm đó.
Chặn hàm DAO (từ)
@Query("SELECT * FROM Todo")
fun getAllTodos(): List<Todo>
Hàm tạm ngưng DAO (to)
@Query("SELECT * FROM Todo")
suspend fun getAllTodos(): List<Todo>
Bạn có thể di chuyển các hàm chặn DAO hiện có sang các hàm tạm ngưng phức tạp nếu cơ sở mã hiện tại chưa tích hợp coroutine. Tham khảo Coroutine trong Android để bắt đầu sử dụng coroutine trong cơ sở mã của bạn.
Chuyển đổi các loại dữ liệu trả về phản ứng thành Flow
Không phải hàm DAO nào cũng cần phải là hàm tạm ngưng. Các hàm DAO trả về
không nên chuyển đổi các loại phản ứng như LiveData
hoặc Flowable
của RxJava
để tạm ngưng các hàm. Tuy nhiên, một số loại, chẳng hạn như LiveData
không phải là KMP
tương thích. Các hàm DAO có loại dữ liệu trả về phản ứng phải được di chuyển sang
luồng coroutine.
Loại KMP không tương thích (từ)
@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>
Loại KMP tương thích (đến)
@Query("SELECT * FROM Todo")
fun getTodosFlow(): Flow<List<Todo>>
Hãy tham khảo bài viết Flow trong Android để bắt đầu sử dụng Flow trong cơ sở mã.
Thiết lập ngữ cảnh coroutine (Không bắt buộc)
Bạn có thể tuỳ ý định cấu hình RoomDatabase
bằng ứng dụng dùng chung
bộ thực thi sử dụng RoomDatabase.Builder.setQueryExecutor()
để thực hiện cơ sở dữ liệu
các toán tử. Vì người thực thi không tương thích với KMP, setQueryExecutor()
của Room
API không có sẵn cho các nguồn phổ biến. Thay vào đó, RoomDatabase
phải
được định cấu hình bằng CoroutineContext
. Ngữ cảnh có thể được đặt bằng
RoomDatabase.Builder.setCoroutineContext()
, nếu bạn không đặt giá trị nào thì hàm
Theo mặc định, RoomDatabase
sẽ sử dụng Dispatchers.IO
.
Đặt trình điều khiển SQLite
Sau khi các trường hợp sử dụng SQLite hỗ trợ được di chuyển sang API trình điều khiển SQLite,
trình điều khiển phải được định cấu hình bằng RoomDatabase.Builder.setDriver
. Chiến lược phát hành đĩa đơn
trình điều khiển được đề xuất là BundledSQLiteDriver
. Xem bài viết Triển khai trình điều khiển để
mô tả về các trình điều khiển hiện có.
SupportSQLiteOpenHelper.Factory
tuỳ chỉnh được định cấu hình bằng
RoomDatabase.Builder.openHelperFactory()
không được hỗ trợ trong KMP,
các tính năng do trình trợ giúp mở tuỳ chỉnh cung cấp sẽ cần được triển khai lại bằng
Giao diện Trình điều khiển SQLite.
Di chuyển nội dung khai báo về phòng
Sau khi hoàn tất hầu hết các bước di chuyển, người dùng có thể di chuyển Room
định nghĩa cho một nhóm tài nguyên chung. Xin lưu ý rằng các chiến lược expect
/ actual
có thể
được dùng để di chuyển dần các định nghĩa liên quan đến Room. Ví dụ: nếu không phải tất cả
có thể di chuyển các hàm DAO chặn sang các hàm tạm ngưng, có thể
khai báo giao diện có chú giải expect
@Dao
trống trong mã chung, nhưng
chứa các hàm chặn trong 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
}