نوشتن پُرسمان‌های غیرهم‌زمان DAO

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

پُرسمان‌های «اشیاء دسترسی به داده» در سه دسته قرار می‌گیرند:

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

گزینه‌های زبان و چارچوب

‫Room از قابلیت تعامل‌پذیری با ویژگی‌ها و کتابخانه‌های زبان خاص پشتیبانی یکپارچه ارائه می‌دهد. جدول زیر انواع برگشتی قابل‌اعمال را براساس نوع پُرسمان و چارچوب نشان می‌دهد:

نوع پُرسمان ویژگی‌های زبان Kotlin (بومی) RxJava گلبهی چرخه زندگی Jetpack*
نوشتن یک‌باره روال‌های همکار (suspend) ‫Single<T>،‏ Maybe<T>، Completable ListenableFuture<T> موجود نیست
خواندن یک‌باره روال‌های همکار (suspend) Single<T>، Maybe<T> ListenableFuture<T> موجود نیست
خواندن قابل‌مشاهده Flow<T> ‫Flowable<T>،‏ Publisher<T>، Observable<T> موجود نیست LiveData<T>

این راهنما سه روش استفاده از این ادغام‌ها را برای پیاده‌سازی پُرسمان‌های ناهم‌زمان در DAOs نشان می‌دهد.

‫Kotlin با Flow و روال‌های مشترک

‫Kotlin ویژگی‌های زبان داخلی‌ای ارائه می‌دهد که به شما امکان می‌دهد پُرسمان‌های ناهم‌زمان را بدون چارچوب‌های طرف سوم بنویسید:

  • ‫Room مستقیماً از جاری‌سازی زبان برنامه‌نویسی Kotlin برای نوشتن پُرسمان‌های قابل‌مشاهده پشتیبانی می‌کند.
  • ‫Room برای اینکه پُرسمان‌های یک‌باره DAO شما را با روال‌های مشترک Kotlin ناهم‌زمان کند به کلیدواژه suspend نیاز دارد.

پشتیبانی از «روال‌های هم‌زمان» و «جریان» مستقیماً در زمان اجرای اصلی Room تعبیه شده است، بنابراین هیچ آرتیفکت اضافه‌ای لازم نیست.

‫RxJava برای Kotlin و Java

‫Room 3.0 از انواع برگشتی RxJava 3 پشتیبانی می‌کند. برای استفاده از انواع برگشتی RxJava، باید مبدل‌های نوع برگشتی RxJava را در پایگاه داده یا DAO خود ثبت کنید:

  1. عنصر androidx.room3:room3-rxjava3 را در پیکربندی ساخت خود بگنجانید.
  2. اعلان @Database یا @Dao خود را با @DaoReturnTypeConverters(RxDaoReturnTypeConverters::class) حاشیه‌نویسی کنید.

‫Room از انواع برگشتی RxJava 3 زیر پشتیبانی می‌کند:

‫LiveData و Guava

‫Room 3.0 از انواع برگشتی LiveData و Guava ListenableFuture بااستفاده از تبدیل‌کننده‌ها پشتیبانی می‌کند:

  • LiveData: عنصر androidx.room3:room3-livedata را اضافه کنید و پایگاه داده یا DAO خود را با @DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class) حاشیه‌نویسی کنید.
  • Guava: مصنوع androidx.room3:room3-guava را اضافه کنید و پایگاه داده یا DAO خود را با @DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class) حاشیه‌نویسی کنید.

نوشتن پُرسمان‌های یک‌باره غیرهم‌زمان

پُرسمان‌های یک‌باره عملیات پایگاه داده‌ای هستند که فقط یک‌بار اجرا می‌شوند و در زمان اجرا یک نمای آنی از داده‌ها می‌گیرند. در اینجا چند نمونه از پُرسمان‌های یک‌باره غیرهم‌زمان آورده شده است:

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

نوشتن پُرسمان‌های قابل‌مشاهده

پُرسمان‌های مشاهده‌کردنی عملیات خواندن هستند که هرگاه جدول‌های مرجع تغییر کنند مقادیر جدیدی منتشر می‌کنند. برای مثال، می‌توانید از این رفتار برای به‌روز نگه داشتن فهرست نمایش‌داده‌شده موارد با تغییر پایگاه داده استفاده کنید. در اینجا چند نمونه از پرسش‌های قابل‌مشاهده آورده شده است:

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

ردیابی نامعتبرسازی پایگاه داده به‌صورت دستی

وقتی نیاز دارید عملیات پایگاه داده قابل‌مشاهده را به‌صورت دستی بسازید، می‌توانید از createFlow میانای برنامه‌سازی کاربردی InvalidationTracker استفاده کنید. این API به شما امکان می‌دهد Flow ایجاد کنید که تغییرات در جدول‌های خاص را پیگیری می‌کند و هرگاه این جدول‌ها تغییر کند اعلانی منتشر می‌کند.

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

به‌طور پیش‌فرض، Flow برگشتی مقدار اولیه‌ای را که حاوی همه جدول‌های ثبت‌شده است منتشر می‌کند تا جاری‌سازی را شروع کند. با تنظیم پارامتر emitInitialState روی false می‌توانید این رفتار را غیرفعال کنید.

تبدیل‌کننده‌های نوع برگشتی «شیء دسترسی به داده» سفارشی

برای انواع داده‌ای که مستقیماً توسط Room یا کتابخانه‌های افزونه آن پشتیبانی نمی‌شوند، می‌توانید تبدیل‌کننده‌های نوع برگشتی سفارشی DAO تعریف کنید تا از انواع برگشتی اضافی پشتیبانی کنید. برای تبدیل نتیجه تابع DAO به نوع سفارشی خودتان، تابع تبدیل‌کننده را با @DaoReturnTypeConverter حاشیه‌نویسی کنید.

برای مثال، می‌توانید مبدلی تعریف کنید که از androidx.tracing برای افزودن بخش‌های ردیابی در اطراف اجرای پُرسمان استفاده می‌کند تا پُرسمان‌های حساس به عملکرد را با پیچیدن اجرا در نوع سفارشی TracedQuery پایش کند:

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

برای استفاده از تبدیل‌کننده، پایگاه داده یا DAO خود را با @DaoReturnTypeConverters حاشیه‌نویسی کنید:

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

کنترل مقدار اولیه تبدیل نوع برگشتی DAO

معمولاً، Room نمونه‌سازی تبدیل‌کننده‌های نوع برگشتی DAO را مدیریت می‌کند. بااین‌حال، اگر باید وابستگی‌های اضافی را به کلاس‌های تبدیل‌کننده خود منتقل کنید، برنامه شما باید مستقیماً مقداردهی اولیه آن‌ها را کنترل کند. اگر این‌گونه است، کلاس تبدیل‌کننده خود را با @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)
    }
}

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

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

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

تابع @DaoReturnTypeConverter باید چندین شرط را برآورده کند:

  • باید پارامتری کارکردی به‌عنوان آخرین متغیر مستقل خود داشته باشد که معمولاً نام آن executeAndConvert است. این پارامتر یک لامبدای suspend است که Room برای اجرای پُرسمان و تجزیه نتیجه تولید می‌کند.
    • اگر تبدیل‌کننده نیاز به تبدیل پُرسمان داشته باشد، مثلاً صفحه‌بندی، لامبدا می‌تواند پارامتر RoomRawQuery را بگیرد.
  • می‌تواند به‌صورت اختیاری پارامترهای زیر را قبل‌از لامبدا بپذیرد:
    • db: RoomDatabase: به نمونه پایگاه داده دسترسی پیدا می‌کند که برای دریافت حوزه روال همکار یا انجام عملیات اضافی مفید است.
    • tableNames: Array<String> یا List<String>: نام جدول‌هایی را که پُرسمان به آن‌ها دسترسی دارد ارائه می‌دهد، که برای انواع قابل‌مشاهده مفید است.
    • rawQuery: RoomRawQuery: نمونه زمان اجرای پُرسمان را ارائه می‌دهد.
    • inTransaction: Boolean: نشان می‌دهد که پُرسمان درحال اجرای درون تراکنش است یا نه.