مكتبة Room للتخزين الدائم
مكتبة Room للتخزين الدائم
في الدرس السابق رأيت كيف تعمل مع SQLite مباشرةً باستخدام SQLiteOpenHelper. هذا الأسلوب يؤدي الغرض، لكنه يجبرك على كتابة كود نمطي متكرر: إنشاء الجداول بـ SQL خام، وكتابة كود حزم وفك حزم ContentValues، وتحويل الأعمدة يدويًا. Room هو طبقة التجريد الرسمية من Google فوق SQLite. فهو يُزيل معظم هذا الكود النمطي ويمنحك التحقق من صحة SQL وقت الترجمة، مع تكامل سلس مع بقية نظام Jetpack.
.db معتاد على الجهاز؛ Room يوفر عليك فحسب الكتابة اليدوية للأجزاء المتكررة.
الركائز الثلاث لـ Room
يتضمن كل إعداد لـ Room ثلاثة أنواع بالضبط من الفئات:
- الكيان (Entity) — فئة Java مزيّنة بـ
@Entity. كل نسخة منها تُعيَّن إلى صف واحد في جدول قاعدة البيانات. - كائن الوصول للبيانات (DAO) — واجهة أو فئة مجردة مزيّنة بـ
@Dao. تُصرّح فيها بالعمليات وتولّد Room تنفيذ SQL لها. - قاعدة البيانات (Database) — فئة مجردة مزيّنة بـ
@Databaseتمتد منRoomDatabase. وهي نقطة الدخول الرئيسية التي تجمع كل شيء معًا.
إضافة Room إلى مشروعك
افتح ملف build.gradle الخاص بالوحدة وأضف التبعيات. يأتي Room عبر ثلاثة مكوّنات منفصلة:
annotationProcessor وليس kapt. kapt هو معالج التعليقات التوضيحية الخاص بـ Kotlin. في مشروع Android خالص بـ Java يجب استخدام annotationProcessor؛ وإلا لن يعمل مولّد كود Room وستحصل على أخطاء "cannot find symbol" غامضة وقت البناء.
تعريف الكيان
الكيان هو كائن Java عادي (POJO) مزيّن بتعليقات Room التوضيحية. تقرأ Room هذه التعليقات وقت الترجمة وتولّد عبارة CREATE TABLE نيابةً عنك.
تفاصيل التعليقات الرئيسية:
@Entity(tableName = "tasks")— اسم الجدول المولَّد. إن حذفته استخدمت Room اسم الفئة بأحرف صغيرة.@PrimaryKey(autoGenerate = true)— يخبر Room باستخدام ما يعادلAUTOINCREMENTفي SQLite. يجب أن يحتوي كل كيان على مفتاح أساسي واحد بالضبط.@ColumnInfo(name = "...")— يعيّن حقل Java إلى اسم عمود محدد. اختياري لكن موصى به بشدة حتى لا يؤدي تغيير اسم الحقل إلى كسر استعلاماتك بصمت.
String أو أنواعًا لها @TypeConverter مسجّل. إن حاولت تخزين List أو فئة مخصصة مباشرةً سترفض Room الترجمة برسالة مثل: "Cannot figure out how to save this field into database."
تعريف كائن DAO
في الـ DAO تُصرّح بالعمليات التي يحتاجها تطبيقك، وتولّد Room التنفيذ. تكتب توقيعات الدوال وSQL (أو تدع Room تستنتج SQL لعمليات CRUD الشائعة).
لاحظ كيف يأخذ التعليق @Query سلسلة SQL عادية. تُحلّل Room هذه الجملة وقت الترجمة وتُبلّغ عن الأخطاء — فاسم عمود أو جدول مكتوب بشكل خاطئ سيتسبب في فشل البناء لا في تعطّل وقت التشغيل. هذا أحد أقيم ضمانات السلامة في Room.
إنشاء فئة قاعدة البيانات
فئة قاعدة البيانات هي الغراء الذي يربط كل شيء. تُصرّح فيها بالكيانات التي ينتمي إليها المخطط والإصدار، وتعرض دوال مصنع للحصول على نسخ الـ DAO.
قراران مهمان اتُّخذا هنا:
- نمط Singleton مع القفل المزدوج الفحص — إنشاء
RoomDatabaseعملية مكلفة (تفتح الملف وتُجمّع عمليات الترحيل). تريد نسخة واحدة بالضبط لكل عملية. context.getApplicationContext()— تمرير سياق Activity سيُسرّب هذا الـ Activity. يعيش سياق التطبيق طوال عمر العملية وهو آمن للاحتفاظ به.
استخدام Room على خيط الخلفية
تفرض Room قاعدة صارمة: لا يجوز تشغيل عمليات قاعدة البيانات على الخيط الرئيسي (UI). فعل ذلك سيحجب واجهة المستخدم ويتسبب في أخطاء ANR (التطبيق لا يستجيب). أبسط طريقة للامتثال هي استخدام خيط خلفية صريح:
LiveData<List<Task>> بدلًا من List<Task> العادية. حينئذٍ تُشغّل Room الاستعلام على خيط خلفية تلقائيًا وتُرسل التحديثات إلى المراقبين على الخيط الرئيسي كلما تغيّرت البيانات. هذا النمط الموصى به في الإنتاج. في الوقت الحالي استخدام خيوط صريحة يُوضّح ما تفعله Room فعليًا تحت الغطاء.
ترحيل المخطط
عند إضافة عمود أو جدول يجب زيادة version في @Database وتوفير كائن Migration. بدون ترحيل ستطرح Room IllegalStateException حين تكتشف عدم تطابق إصدار المخطط.
أثناء التطوير يمكنك استدعاء .fallbackToDestructiveMigration() بدلًا من ذلك لحذف قاعدة البيانات وإعادة إنشائها عند عدم تطابق الإصدار. لا تستخدم هذا في الإنتاج أبدًا — فهو يُدمّر بيانات المستخدم بصفة دائمة.
الخلاصة
تُقلّص Room عمل قواعد بيانات Android إلى ثلاث فئات مزيّنة بتعليقات: @Entity يصف صف الجدول، و@Dao الذي تُصرّح فيه بالاستعلامات (مع التحقق منها وقت الترجمة)، و@Database كـ Singleton يربط كل شيء. شغّل Room دائمًا على خيط خلفية، واستخدم getApplicationContext() للـ Singleton، وعرّف كائنات Migration صريحة في كل مرة يتغير فيها المخطط. في الدرس القادم ستتعمق في الخيوط وستتعلم الخيارات الحديثة — ExecutorService وWorkManager — التي تتزاوج مع Room بشكل طبيعي في تطبيقات الإنتاج.