Mereferensikan data kompleks menggunakan Room

Room dapat mengonversi jenis primitif dan jenis yang dapat ditetapkan pada objek, tetapi tidak mengizinkan referensi objek antar-entity. Pelajari cara menggunakan konverter jenis dan alasan mengapa Room tidak mendukung referensi objek.

Menggunakan konverter jenis

Terkadang, aplikasi Anda perlu menyimpan jenis data kustom dalam satu kolom database. Anda mendukung jenis kustom dengan menyediakan pengonversi jenis. Fungsi ini memberi tahu Room cara mengonversi jenis kustom ke dan dari jenis umum yang dapat dipertahankan Room. Anda dapat mengidentifikasi pengonversi jenis menggunakan anotasi @ColumnTypeConverter.

Misalnya, Anda perlu mempertahankan instance Date di database Room. Room tidak dapat mempertahankan objek Date secara native, sehingga Anda perlu menentukan pengonversi jenis:

object Converters {
    @ColumnTypeConverter
    fun fromTimestamp(value: Long?): Date? {
        return value?.let { Date(it) }
    }

    @ColumnTypeConverter
    fun dateToTimestamp(date: Date?): Long? {
        return date?.time
    }
}

Contoh ini menentukan dua fungsi konverter jenis: fungsi yang mengonversi objek Date menjadi objek Long, dan fungsi yang mengonversi objek Long kembali menjadi objek Date. Karena dapat mempertahankan objek Long, Room dapat menggunakan konverter ini untuk mempertahankan objek Date.

Selanjutnya, tambahkan anotasi @ColumnTypeConverters ke class AppDatabase agar Room dapat menggunakan class pengonversi yang telah Anda tentukan:

@Database(entities = [User::class], version = 1)
@ColumnTypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

Dengan konverter jenis yang ditentukan ini, Anda dapat menggunakan jenis kustom di entity dan DAO seperti Anda menggunakan jenis sederhana:

@Entity
data class User(
    @PrimaryKey val id: Long,
    val name: String,
    val birthday: Date?
)

@Dao
interface UserDao {
    @Query("SELECT * FROM user WHERE birthday = :targetDate")
    suspend fun findUsersBornOnDate(targetDate: Date): List<User>
}

Karena Anda menganotasi AppDatabase dengan @ColumnTypeConverters dalam contoh ini, Room dapat menggunakan pengonversi jenis yang ditentukan di mana saja. Untuk mencakup pengonversi jenis ke entity atau DAO tertentu, anotasikan class @Entity atau @Dao dengan @ColumnTypeConverters.

Menginisialisasi pengonversi jenis kontrol

Biasanya, Room membuat instance pengonversi jenis untuk Anda. Namun, jika Anda perlu meneruskan dependensi tambahan ke class konverter jenis, aplikasi Anda harus mengontrol inisialisasinya secara langsung. Dalam hal ini, anotasikan class pengonversi dengan @ProvidedColumnTypeConverter:

@ProvidedColumnTypeConverter
class ExampleConverter {
    @ColumnTypeConverter
    fun stringToExample(string: String?): ExampleType? {
        return string?.let { ExampleType() }
    }

    @ColumnTypeConverter
    fun exampleToString(example: ExampleType?): String? {
        return example?.toString()
    }
}

Selain mendeklarasikan class pengonversi di @ColumnTypeConverters, gunakan fungsi RoomDatabase.Builder.addColumnTypeConverter untuk meneruskan instance class pengonversi ke builder RoomDatabase:

val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name")
    .addColumnTypeConverter(exampleConverterInstance)
    .build()

Memahami mengapa Room tidak mengizinkan referensi objek

Poin utama: Room tidak mengizinkan referensi objek antar-class entity. Sebagai gantinya, Anda harus secara eksplisit meminta data yang dibutuhkan oleh aplikasi Anda.

Memetakan hubungan dari database ke masing-masing model objek adalah praktik yang umum dilakukan dan bekerja dengan sangat baik di sisi server. Bahkan, saat program memuat properti ketika sedang diakses, performa server akan tetap baik.

Namun, di sisi klien, jenis pemuatan lambat ini akan menyulitkan karena biasanya akan terjadi di UI thread, dan meminta informasi tentang disk di UI thread akan menimbulkan masalah performa yang signifikan. UI thread biasanya membutuhkan waktu sekitar 16 milidetik untuk menghitung dan menggambar tata letak aktivitas yang diperbarui. Jadi, meskipun kueri hanya memerlukan waktu 5 milidetik, aplikasi Anda kemungkinan akan kehabisan waktu untuk menggambar frame sehingga gangguan visual yang cukup terlihat akan muncul. Kueri dapat memerlukan lebih banyak waktu untuk diselesaikan jika ada transaksi terpisah yang berjalan secara paralel, atau jika perangkat sedang menjalankan tugas lain yang membutuhkan banyak disk. Namun, jika Anda tidak menggunakan pemuatan lambat, aplikasi akan mengambil lebih banyak data daripada yang dibutuhkan sehingga akan timbul masalah pada penggunaan memori.

Pemetaan terkait objek biasanya menyerahkan keputusan ini kepada developer agar mereka dapat melakukan yang terbaik sesuai kasus penggunaan aplikasinya. Developer biasanya memutuskan untuk menggunakan model yang sama antara aplikasi mereka dan UI-nya. Namun, solusi ini kurang sesuai karena seiring dengan berubahnya UI dari waktu ke waktu, model bersama akan menimbulkan masalah yang sulit diantisipasi dan di-debug oleh developer.

Misalnya, terdapat UI yang memuat daftar objek Book, yang masing-masing memiliki objek Author. Awalnya, Anda mungkin mendesain kueri untuk menggunakan pemuatan lambat agar instance Book mengambil penulisnya. Pengambilan pertama properti author akan membuat kueri pada database. Beberapa waktu kemudian, Anda menyadari bahwa nama penulis juga perlu ditampilkan dalam UI aplikasi. Anda dapat mengakses nama ini, seperti yang ditunjukkan dalam cuplikan kode berikut:

Text(text = book.author.name)

Namun, perubahan yang tampaknya tidak berbahaya ini menyebabkan tabel Author dikueri pada thread utama.

Jika Anda meminta informasi penulis lebih awal, tetapi tidak membutuhkannya, akan sulit untuk mengubah cara data dimuat. Misalnya, jika UI aplikasi Anda tidak perlu lagi menampilkan informasi Author, aplikasi Anda akan secara efektif memuat data yang tidak ditampilkan, dan menghabiskan ruang memori yang berharga. Efisiensi aplikasi Anda akan terus menurun jika class Author mereferensikan tabel lain, seperti Books.

Untuk mereferensikan beberapa entitas sekaligus menggunakan Room, buat objek data yang berisi setiap entitas, lalu tulis kueri yang menggabungkan tabel terkait. Model yang terstruktur dengan baik ini, bersama kemampuan validasi kueri Room yang canggih, memungkinkan aplikasi Anda menggunakan lebih sedikit resource saat memuat data sehingga performa aplikasi dan pengalaman pengguna pun akan meningkat.