ارجاع دادن به داده‌های پیچیده بااستفاده از Room

‫Room می‌تواند انواع اولیه و جعبه‌ای را تبدیل کند، اما ارجاع‌های شیء بین نهادها را مجاز نمی‌داند. با نحوه استفاده از تبدیل‌کننده‌های نوع و دلیل پشتیبانی نکردن Room از ارجاع‌های شیء آشنا شوید.

استفاده از تبدیل‌کننده‌های نوع

گاهی اوقات، لازم است برنامه‌تان نوع داده سفارشی را در ستون پایگاه داده‌ای واحد ذخیره کند. با ارائه تبدیل‌کننده‌های نوع از انواع سفارشی پشتیبانی می‌کنید. این‌ها توابعی هستند که به Room می‌گویند چگونه انواع سفارشی را به انواع شناخته‌شده‌ای که Room می‌تواند ماندگار کند تبدیل کند و از آن‌ها تبدیل کند. بااستفاده از گزارمان @ColumnTypeConverter، مبدل‌های نوع را شناسایی می‌کنید.

فرض کنید باید نمونه‌های Date را در پایگاه داده Room خود ماندگار کنید. ‫Room نمی‌تواند به‌طور بومی Date شیء را ماندگار کند، بنابراین باید تبدیل‌کننده‌های نوع را تعریف کنید:

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

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

این مثال دو تابع تبدیل نوع را تعریف می‌کند: یکی که Date شیء را به شیء Long تبدیل می‌کند و دیگری که شیء Long را به شیء Date تبدیل می‌کند. ازآنجایی‌که Room می‌تواند Long شیء را ماندگار کند، می‌تواند از این تبدیل‌کننده‌ها برای ماندگار کردن Date شیء استفاده کند.

سپس @ColumnTypeConverters گزارمان را به کلاس AppDatabase اضافه می‌کنید تا Room بتواند از کلاس تبدیل‌کننده‌ای که تعریف کرده‌اید استفاده کند:

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

با تعریف این تبدیل‌کننده‌های نوع، می‌توانید از نوع سفارشی خود در نهادها و DAOs درست مثل استفاده از انواع اولیه استفاده کنید:

@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>
}

چون AppDatabase را با @ColumnTypeConverters در این مثال حاشیه‌نویسی کردید، Room می‌تواند از مبدل نوع تعریف‌شده در همه‌جا استفاده کند. برای تبدیل نوع محدوده به نهادهای خاص یا DAO به‌جای آن، کلاس‌های @Entity یا @Dao را با @ColumnTypeConverters حاشیه‌نویسی کنید.

راه‌اندازی تبدیل‌کننده نوع کنترل

معمولاً Room مبدل‌های نوع را برای شما نمونه‌سازی می‌کند. بااین‌حال، اگر نیاز دارید وابستگی‌های بیشتری را به کلاس‌های تبدیل نوع خود منتقل کنید، برنامه شما باید راه‌اندازی آن‌ها را مستقیماً کنترل کند. در این صورت، کلاس مبدل خود را با @ProvidedColumnTypeConverter حاشیه‌نویسی کنید:

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

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

علاوه‌بر تعریف کردن کلاس تبدیل‌کننده در @ColumnTypeConverters، از تابع RoomDatabase.Builder.addColumnTypeConverter برای انتقال نمونه‌ای از کلاس تبدیل‌کننده به سازنده RoomDatabase استفاده کنید:

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

دریابید چرا Room اجازه ارجاع به شیء را نمی‌دهد

نکته کلیدی: ‫Room ارجاع‌های شیء بین کلاس‌های نهاد را مجاز نمی‌داند. درعوض، باید داده‌هایی را که برنامه‌تان نیاز دارد به‌طور صریح درخواست کنید.

نگاشت روابط از پایگاه داده به مدل شیء مربوطه یک رویه رایج است و در سمت سرور بسیار خوب کار می‌کند. حتی وقتی برنامه دارایی‌ها را هنگام دسترسی بار می‌کند، سرور همچنان عملکرد خوبی دارد.

بااین‌حال، در سمت کارخواه، این نوع بارگیری تنبل امکان‌پذیر نیست زیرا معمولاً در رشته واسط کاربر رخ می‌دهد، و پُرسمان اطلاعات در دیسک در رشته واسط کاربر مشکلات عملکرد قابل‌توجهی ایجاد می‌کند. رشته واسط کاربر معمولاً حدود ۱۶ میلی‌ثانیه زمان دارد تا طرح‌بندی به‌روزشده فعالیت را محاسبه و رسم کند، بنابراین حتی اگر پُرسمانی فقط ۵ میلی‌ثانیه طول بکشد، باز هم احتمال دارد برنامه شما برای رسم کردن قاب زمان کافی نداشته باشد و این امر باعث ایجاد اشکالات بصری قابل‌توجه می‌شود. اگر تراکنش جداگانه‌ای به‌صورت موازی درحال اجرا باشد، یا اگر دستگاه درحال اجرای وظایف دیگری باشد که از دیسک زیاد استفاده می‌کنند، تکمیل پُرسمان ممکن است حتی بیشتر طول بکشد. بااین‌حال، اگر از بارگیری تنبلانه استفاده نکنید، برنامه شما داده‌های بیشتری از آنچه نیاز دارد واکشی می‌کند و مشکلاتی در مصرف حافظه ایجاد می‌کند.

نگاشت‌های رابطه‌ای شیء معمولاً این تصمیم را به توسعه‌دهندگان واگذار می‌کنند تا بتوانند هر کاری را که برای موارد استفاده برنامه آن‌ها بهتر است انجام دهند. توسعه‌دهندگان معمولاً تصمیم می‌گیرند مدل را بین برنامه و واسط کاربر هم‌رسانی کنند. بااین‌حال، این راه‌حل به‌خوبی مقیاس‌پذیر نیست، زیرا با تغییر واسط کاربر در طول زمان، مدل مشترک مشکلاتی ایجاد می‌کند که پیش‌بینی و اشکال‌زدایی آن‌ها برای توسعه‌دهندگان دشوار است.

برای مثال، رابط کاربری‌ای را درنظر بگیرید که فهرست Book شیء را بار می‌کند و هر کتاب دارای شیء Author است. ممکن است در ابتدا پُرسمان‌هایتان را طوری طراحی کنید که از بارگیری تنبل استفاده کنند تا نمونه‌های Book نویسنده را بازیابی کنند. اولین بازیابی دارایی author پایگاه داده را پُرسمان می‌کند. کمی بعد متوجه می‌شوید که باید نام نویسنده را نیز در واسط کاربر برنامه‌تان نمایش دهید. همان‌طور که در تکه‌کد زیر نشان داده شده است، می‌توانید به این نام دسترسی داشته باشید:

Text(text = book.author.name)

بااین‌حال، این تغییر به‌ظاهر بی‌ضرر باعث می‌شود جدول Author در رشته اصلی پُرسمان شود.

اگر اطلاعات نویسنده را ازقبل پُرسمان کنید اما به آن نیاز نداشته باشید، تغییر دادن نحوه بارگیری داده‌ها دشوار است. برای مثال، اگر میانای کاربر برنامه شما دیگر نیازی به نمایش اطلاعات Author نداشته باشد، برنامه شما عملاً داده‌هایی را بار می‌کند که نمایش نمی‌دهد و فضای حافظه ارزشمند را هدر می‌دهد. اگر کلاس Author به جدول دیگری مثل Books ارجاع دهد، کارایی برنامه شما حتی بیشتر از این هم کاهش می‌یابد.

برای ارجاع دادن به چند نهاد به‌طور هم‌زمان بااستفاده از Room، یک شیء داده ایجاد کنید که حاوی هر نهاد باشد، و سپس پُرسمانی بنویسید که جدول‌های مربوطه را به هم می‌پیوندد. این مدل خوش‌ساختار، همراه با قابلیت‌های اعتبارسنجی پُرقدرت پُرسمان Room، به برنامه شما امکان می‌دهد هنگام بار کردن داده‌ها از منابع کمتری استفاده کند و عملکرد برنامه و تجربه کاربر را بهبود دهد.