Menulis kueri DAO asinkron

Untuk mencegah kueri memblokir UI, Room tidak mendukung akses database pada thread utama. Batasan ini berarti Anda harus membuat kueri DAO secara asinkron. Library Room menyertakan integrasi dengan beberapa framework untuk menyediakan eksekusi kueri asinkron.

Kueri DAO dibagi menjadi tiga kategori:

  • Kueri tindakan tulis satu kali yang menyisipkan, memperbarui, atau menghapus data dalam database.
  • Kueri tindakan baca satu kali yang membaca data dari database Anda hanya sekali dan menampilkan hasil beserta rekaman data dari database pada saat itu.
  • Kueri tindakan baca yang dapat diamati yang membaca data dari database Anda setiap kali tabel database pokok berubah dan memunculkan nilai baru untuk mencerminkan perubahan tersebut.

Opsi bahasa dan framework

Room memberikan dukungan integrasi untuk interoperabilitas dengan library dan fitur bahasa tertentu. Tabel berikut menampilkan jenis nilai yang ditampilkan, yang dapat diterapkan berdasarkan jenis kueri dan framework:

Jenis kueri Fitur bahasa Kotlin (Native) RxJava Guava Jetpack Lifecycle*
Tindakan tulis satu kali Coroutine (suspend) Single<T>, Maybe<T>, Completable ListenableFuture<T> T/A
Tindakan baca satu kali Coroutine (suspend) Single<T>, Maybe<T> ListenableFuture<T> T/A
Tindakan baca yang dapat diamati Flow<T> Flowable<T>, Publisher<T>, Observable<T> T/A LiveData<T>

Panduan ini menunjukkan tiga cara menggunakan integrasi ini untuk mengimplementasikan kueri asinkron di DAO.

Kotlin dengan Flow dan coroutine

Kotlin menyediakan fitur bahasa bawaan yang memungkinkan Anda menulis kueri asinkron tanpa framework pihak ketiga:

  • Room secara langsung mendukung Flow Kotlin untuk menulis kueri yang dapat diamati.
  • Room memerlukan kata kunci suspend untuk membuat kueri DAO satu kali menjadi asinkron dengan coroutine Kotlin.

Dukungan Coroutine dan Flow dibangun langsung ke runtime Room inti, sehingga tidak diperlukan artefak tambahan.

RxJava untuk Kotlin dan Java

Room 3.0 mendukung jenis nilai yang ditampilkan RxJava 3. Untuk menggunakan jenis nilai yang ditampilkan RxJava, Anda harus mendaftarkan konverter jenis nilai yang ditampilkan RxJava di database atau DAO Anda:

  1. Sertakan artefak androidx.room3:room3-rxjava3 dalam konfigurasi build Anda.
  2. Anotasikan deklarasi @Database atau @Dao Anda dengan @DaoReturnTypeConverters(RxDaoReturnTypeConverters::class).

Room mendukung jenis nilai yang ditampilkan RxJava 3 berikut:

LiveData dan Guava

Room 3.0 mendukung jenis nilai yang ditampilkan LiveData dan Guava ListenableFuture menggunakan konverter:

  • LiveData: Sertakan artefak androidx.room3:room3-livedata dan anotasi database atau DAO Anda dengan @DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class).
  • Guava: Sertakan artefak androidx.room3:room3-guava dan anotasi database atau DAO Anda dengan @DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class).

Menulis kueri satu kali asinkron

Kueri tindakan satu kali adalah operasi database yang hanya berjalan sekali dan mengambil rekaman data pada saat eksekusi. Berikut beberapa contoh kueri satu kali asinkron:

@Dao
interface UserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    suspend fun loadUserById(id: Int): User

    @Query("SELECT * from user WHERE region IN (:regions)")
    suspend fun loadUsersByRegion(regions: List<String>): List<User>
}

Menulis kueri yang dapat diamati

Kueri yang dapat diamati adalah operasi baca yang memunculkan nilai baru setiap kali tabel yang dirujuk berubah. Misalnya, Anda dapat menggunakan perilaku ini untuk terus memperbarui daftar item yang ditampilkan saat database berubah. Berikut beberapa contoh kueri yang dapat diamati:

@Dao
interface ObservableUserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    fun loadUserById(id: Int): Flow<User>

    @Query("SELECT * from user WHERE region IN (:regions)")
    fun loadUsersByRegion(regions: List<String>): Flow<List<User>>
}

Melacak pembatalan database secara manual

Saat perlu membuat operasi database yang dapat diamati secara manual, Anda dapat menggunakan API createFlow dari InvalidationTracker. Dengan API ini, Anda dapat membuat Flow yang melacak modifikasi pada tabel tertentu dan mengirimkan notifikasi setiap kali tabel tersebut berubah.

fun getArtistTours(db: RoomDatabase, from: Date, to: Date): Flow<Map<Artist, TourState>> {
    return db.invalidationTracker.createFlow("Artist").map { _ ->
        val artists = artistsDao.getAllArtists()
        val tours = tourService.fetchStates(artists.map { it.id })
        associateTours(artists, tours, from, to)
    }
}

Secara default, Flow yang ditampilkan memancarkan nilai awal yang berisi semua tabel terdaftar untuk memulai streaming. Anda dapat menonaktifkan perilaku ini dengan menetapkan parameter emitInitialState ke false.

Konverter jenis nilai yang ditampilkan DAO kustom

Untuk jenis yang tidak didukung secara langsung oleh Room atau library ekstensinya, Anda dapat menentukan konverter jenis nilai yang ditampilkan DAO kustom untuk mendukung jenis nilai yang ditampilkan tambahan. Untuk mengubah hasil fungsi DAO menjadi jenis kustom Anda, anotasikan fungsi pengonversi dengan @DaoReturnTypeConverter.

Misalnya, Anda dapat menentukan pengonversi yang menggunakan androidx.tracing untuk menambahkan bagian rekaman aktivitas di sekitar eksekusi kueri untuk memantau kueri yang sensitif terhadap performa dengan membungkus eksekusi dalam jenis TracedQuery kustom:

class TracedQuery<T>(val result: T)

object TracingDaoReturnTypeConverter {
    @DaoReturnTypeConverter([OperationType.READ])
    suspend fun <T> convert(
        rawQuery: RoomRawQuery,
        executeAndConvert: suspend () -> T
    ): TracedQuery<T> {
        val result = trace("TracedQuery: ${rawQuery.sql}") {
            executeAndConvert()
        }
        return TracedQuery(result)
    }
}

Untuk menggunakan pengonversi, anotasikan database atau DAO Anda dengan @DaoReturnTypeConverters:

@Dao
@DaoReturnTypeConverters(TracingDaoReturnTypeConverter::class)
interface MusicDao {
    @Query("SELECT * FROM Song")
    suspend fun getAllSongs(): TracedQuery<List<Song>>
}

Menginisialisasi pengonversi jenis nilai yang ditampilkan DAO kontrol

Biasanya, Room menangani pembuatan instance pengonversi jenis nilai yang ditampilkan DAO. Namun, jika Anda harus meneruskan dependensi tambahan ke class konverter, aplikasi Anda harus mengontrol inisialisasinya secara langsung. Jika demikian, anotasikan class pengonversi dengan @ProvidedDaoReturnTypeConverter:

@ProvidedDaoReturnTypeConverter
class TracingDaoReturnTypeConverter(val tracer: Tracer) {
    @DaoReturnTypeConverter([OperationType.READ])
    suspend fun <T> convert(
        rawQuery: RoomRawQuery,
        executeAndConvert: suspend () -> T
    ): TracedQuery<T> {
        val result = tracer.trace("TracedQuery: ${rawQuery.sql}") {
            executeAndConvert()
        }
        return TracedQuery(result)
    }
}

Kemudian, selain mendeklarasikan class pengonversi di @DaoReturnTypeConverters, gunakan fungsi RoomDatabase.Builder.addDaoReturnTypeConverter untuk meneruskan instance class pengonversi ke builder RoomDatabase:

val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name")
    .addDaoReturnTypeConverter(TracingDaoReturnTypeConverter(myLoggerInstance))
    .build()

Persyaratan fungsi konverter

Fungsi @DaoReturnTypeConverter harus memenuhi beberapa persyaratan:

  • Fungsi ini harus memiliki parameter fungsional sebagai argumen terakhirnya, biasanya bernama executeAndConvert. Parameter ini adalah lambda suspend yang dihasilkan Room untuk menjalankan kueri dan mengurai hasilnya.
    • Jika konverter perlu mengubah kueri, seperti Penomoran Halaman, lambda dapat menggunakan parameter RoomRawQuery.
  • Secara opsional, fungsi ini dapat menerima parameter berikut sebelum lambda:
    • db: RoomDatabase: Mengakses instance database, yang berguna untuk mendapatkan cakupan coroutine atau melakukan operasi tambahan.
    • tableNames: Array<String> atau List<String>: Memberikan nama tabel yang diakses oleh kueri, yang berguna untuk jenis yang dapat diamati.
    • rawQuery: RoomRawQuery: Menyediakan instance runtime kueri.
    • inTransaction: Boolean: Menunjukkan apakah kueri sedang dieksekusi dalam transaksi.