‫Android Kotlin Fundamentals 06.1: إنشاء قاعدة بيانات Room

هذا الدرس التطبيقي حول الترميز هو جزء من دورة "أساسيات Android Kotlin". يمكنك تحقيق أقصى استفادة من هذه الدورة التدريبية إذا اتبعت ترتيب الخطوات في دروس البرمجة. يتم إدراج جميع الدروس التطبيقية حول الترميز الخاصة بالدورة التدريبية في الصفحة المقصودة للدروس التطبيقية حول الترميز في دورة Android Kotlin Fundamentals.

مقدمة

تحتوي معظم التطبيقات على بيانات يجب الاحتفاظ بها، حتى بعد أن يغلق المستخدم التطبيق. على سبيل المثال، قد يخزّن التطبيق قائمة تشغيل أو مستودعًا لعناصر اللعبة أو سجلات النفقات والدخل أو فهرسًا للأبراج أو بيانات النوم بمرور الوقت. عادةً، يمكنك استخدام قاعدة بيانات لتخزين البيانات الثابتة.

Room هي مكتبة قواعد بيانات تشكّل جزءًا من Jetpack. تتولّى Room العديد من المهام الروتينية المتعلقة بإعداد قاعدة البيانات وضبطها، وتتيح لتطبيقك التفاعل مع قاعدة البيانات باستخدام طلبات عادية للدوال. في الخلفية، Room هي طبقة تجريدية فوق قاعدة بيانات SQLite. تتّبع مصطلحات Room وبنية الاستعلامات الأكثر تعقيدًا نموذج SQLite.

توضّح الصورة أدناه كيف يتناسب قاعدة بيانات Room مع البنية العامة المقترَحة في هذه الدورة التدريبية.

ما يجب معرفته

يجب أن تكون على دراية بما يلي:

  • إنشاء واجهة مستخدم أساسية لتطبيق Android
  • استخدام الأنشطة واللقطات وطرق العرض
  • التنقّل بين الأجزاء واستخدام Safe Args (وهي إضافة Gradle) لنقل البيانات بين الأجزاء
  • عرض النماذج ونماذج العرض ومصانع نماذج العرض وLiveData ومراقبيه تم تناول مواضيع "مكوّنات البنية" هذه في درس تطبيقي سابق في هذه الدورة التدريبية.
  • فهم أساسي لقواعد بيانات SQL ولغة SQLite يمكنك الاطّلاع على مقدمة عن SQLite للحصول على نظرة عامة سريعة أو تنشيط ذاكرتك.

أهداف الدورة التعليمية

  • كيفية إنشاء قاعدة بيانات Room والتفاعل معها للاحتفاظ بالبيانات
  • كيفية إنشاء فئة بيانات تحدّد جدولاً في قاعدة البيانات
  • كيفية استخدام عنصر الوصول إلى البيانات (DAO) لربط دوال Kotlin باستعلامات SQL
  • كيفية اختبار ما إذا كانت قاعدة البيانات تعمل بشكل سليم

الإجراءات التي ستنفذّها

  • أنشئ قاعدة بيانات Room تتضمّن واجهة لبيانات النوم الليلية.
  • اختبِر قاعدة البيانات باستخدام الاختبارات المتوفّرة.

في هذا الدرس التطبيقي حول الترميز، ستنشئ جزء قاعدة البيانات من تطبيق يتتبّع جودة النوم. يستخدم التطبيق قاعدة بيانات لتخزين بيانات النوم بمرور الوقت.

يحتوي التطبيق على شاشتَين، يتم تمثيلهما بلقطات، كما هو موضّح في الشكل أدناه.

تحتوي الشاشة الأولى، المعروضة على اليمين، على أزرار لبدء التتبُّع وإيقافه. تعرِض الشاشة جميع بيانات نوم المستخدم. يؤدي النقر على الزر محو إلى حذف جميع البيانات التي جمعها التطبيق للمستخدم نهائيًا.

الشاشة الثانية، المعروضة على اليسار، مخصّصة لاختيار تقييم لجودة النوم. في التطبيق، يتم تمثيل التقييم رقميًا. لأغراض التطوير، يعرض التطبيق رموز الوجوه وما يعادلها من أرقام.

يكون تدفّق المستخدم على النحو التالي:

  • يفتح المستخدم التطبيق وتظهر له شاشة تتبُّع النوم.
  • ينقر المستخدم على الزر بدء. تسجّل هذه السمة وقت البدء وتعرضه. يكون زر بدء غير مفعَّل، بينما يكون زر إيقاف مفعَّلاً.
  • ينقر المستخدم على الزر إيقاف. يتم تسجيل وقت الانتهاء وفتح شاشة جودة النوم.
  • يختار المستخدم رمزًا لجودة النوم. تنغلق الشاشة، وتعرض شاشة التتبُّع وقت انتهاء النوم وجودته. يكون زر إيقاف غير مفعَّل ويكون زر بدء مفعَّلاً. التطبيق جاهز للاستخدام في ليلة أخرى.
  • يتم تفعيل الزر محو عندما تكون هناك بيانات في قاعدة البيانات. عندما ينقر المستخدم على الزر محو، يتم محو جميع بياناته بدون رجعة، ولن تظهر الرسالة "هل أنت متأكد؟".

يستخدم هذا التطبيق بنية مبسطة، كما هو موضّح أدناه في سياق البنية الكاملة. يستخدم التطبيق المكوّنات التالية فقط:

  • وحدة التحكّم في واجهة المستخدم
  • عرض النموذج وLiveData
  • قاعدة بيانات Room

الخطوة 1: تنزيل تطبيق البداية وتشغيله

  1. نزِّل تطبيق TrackMySleepQuality-Starter من GitHub.
  2. أنشئ التطبيق وشغِّله. يعرض التطبيق واجهة المستخدم للجزء SleepTrackerFragment، ولكن بدون بيانات. لا تستجيب الأزرار للنقرات.

الخطوة 2: فحص تطبيق البداية

  1. ألقِ نظرة على ملفات Gradle:
  • ملف Gradle الخاص بالمشروع
    في ملف build.gradle على مستوى المشروع، لاحظ المتغيرات التي تحدّد إصدارات المكتبة. تتكامل الإصدارات المستخدَمة في تطبيق المبتدئين بشكل جيد، وتعمل بشكل جيد مع هذا التطبيق. وعند الانتهاء من هذا الدرس البرمجي، قد يطلب منك "استوديو Android" تحديث بعض الإصدارات. يعود إليك قرار التحديث أو البقاء على الإصدارات المتوفّرة في التطبيق. إذا واجهت أخطاء "غريبة" في التجميع، جرِّب استخدام مجموعة إصدارات المكتبة التي يستخدمها تطبيق الحل النهائي.
  • ملف Gradle الخاص بالوحدة: لاحظ التبعيات المتوفّرة لجميع مكتبات Android Jetpack، بما في ذلك Room، والتبعيات الخاصة بالروتينات الفرعية.
  1. اطّلِع على الحِزم وواجهة المستخدم. يتم تنظيم التطبيق حسب الوظائف. تحتوي الحزمة على ملفات عناصر نائبة ستضيف إليها الرمز البرمجي خلال هذه السلسلة من دروس البرمجة.
  • حزمة database، لجميع الرموز البرمجية المتعلقة بقاعدة بيانات Room
  • تحتوي حزم sleepquality وsleeptracker على الجزء ونموذج العرض ومصنع نموذج العرض لكل شاشة.
  1. اطّلِع على ملف Util.kt الذي يحتوي على دوال للمساعدة في عرض بيانات جودة النوم. تمت إضافة تعليقات إلى بعض الرموز لأنّها تشير إلى نموذج عرض ستنشئه لاحقًا.
  2. ألقِ نظرة على مجلد androidTest (SleepDatabaseTest.kt). ستستخدم هذا الاختبار للتأكّد من أنّ قاعدة البيانات تعمل على النحو المطلوب.

في Android، يتم تمثيل البيانات في فئات البيانات، ويتم الوصول إلى البيانات وتعديلها باستخدام استدعاءات الدوال. ومع ذلك، في عالم قواعد البيانات، تحتاج إلى كيانات وطلبات بحث.

  • يمثّل الكيان عنصرًا أو مفهومًا وخصائصه التي سيتم تخزينها في قاعدة البيانات. تحدّد فئة الكيان جدولاً، ويمثّل كل مثيل من هذه الفئة صفًا في الجدول. تحدّد كل سمة عمودًا. في تطبيقك، سيحتوي العنصر على معلومات حول ليلة نوم.
  • الاستعلام هو طلب للحصول على بيانات أو معلومات من جدول قاعدة بيانات أو مجموعة من الجداول، أو طلب لتنفيذ إجراء على البيانات. الاستعلامات الشائعة هي تلك التي يتم إجراؤها للحصول على الكيانات وإدراجها وتعديلها. على سبيل المثال، يمكنك طلب البحث عن جميع ليالي النوم المسجّلة، مع ترتيبها حسب وقت البدء.

تتولّى Room جميع المهام الصعبة نيابةً عنك للانتقال من فئات بيانات Kotlin إلى عناصر يمكن تخزينها في جداول SQLite، ومن تعريفات الدوال إلى طلبات بحث SQL.

يجب تحديد كل عنصر كفئة بيانات مزوّدة بتعليقات توضيحية، والتفاعلات كواجهة مزوّدة بتعليقات توضيحية، أي كائن الوصول إلى البيانات (DAO). تستخدم Room هذه الفئات المشروحة لإنشاء جداول في قاعدة البيانات، والاستعلامات التي تعمل على قاعدة البيانات.

الخطوة 1: إنشاء كيان SleepNight

في هذه المهمة، ستعرّف ليلة نوم واحدة على أنّها فئة بيانات مشروحة.

لتسجيل ليلة نوم واحدة، عليك تسجيل وقت البدء ووقت الانتهاء وتقييم الجودة.

ويجب أن يكون لديك معرّف لتحديد الليلة بشكل فريد.

  1. في حزمة database، ابحث عن الملف SleepNight.kt وافتحه.
  2. أنشئ فئة البيانات SleepNight مع مَعلمات لمعرّف ووقت بدء (بالملّي ثانية) ووقت انتهاء (بالملّي ثانية) وتقييم رقمي لجودة النوم.
  • عليك تهيئة sleepQuality، لذا اضبطه على -1، ما يشير إلى أنّه لم يتم جمع أي بيانات جيدة.
  • عليك أيضًا ضبط وقت الانتهاء. اضبطها على وقت البدء للإشارة إلى أنّه لم يتم تسجيل وقت انتهاء بعد.
data class SleepNight(
       var nightId: Long = 0L,
       val startTimeMilli: Long = System.currentTimeMillis(),
       var endTimeMilli: Long = startTimeMilli,
       var sleepQuality: Int = -1
)
  1. قبل تعريف الفئة، أضِف التعليق التوضيحي @Entity إلى فئة البيانات. أدخِل اسمًا للجدول daily_sleep_quality_table. الوسيطة الخاصة بـ tableName اختيارية، ولكن يُنصح باستخدامها. يمكنك البحث عن وسيطات أخرى في المستندات.

    إذا طُلب منك ذلك، استورِد Entity وجميع التعليقات التوضيحية الأخرى من مكتبة androidx.
@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(...)
  1. لتحديد nightId كمفتاح أساسي، أضِف التعليق التوضيحي @PrimaryKey إلى السمة nightId. اضبط المَعلمة autoGenerate على true لكي ينشئ Room المعرّف لكل كيان. ويضمن ذلك أن يكون المعرّف لكل ليلة فريدًا.
@PrimaryKey(autoGenerate = true)
var nightId: Long = 0L,...
  1. أضِف التعليقات التوضيحية إلى السمات المتبقية باستخدام @ColumnInfo. خصِّص أسماء المواقع باستخدام المَعلمات كما هو موضّح أدناه.
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(
       @PrimaryKey(autoGenerate = true)
       var nightId: Long = 0L,

       @ColumnInfo(name = "start_time_milli")
       val startTimeMilli: Long = System.currentTimeMillis(),

       @ColumnInfo(name = "end_time_milli")
       var endTimeMilli: Long = startTimeMilli,

       @ColumnInfo(name = "quality_rating")
       var sleepQuality: Int = -1
)
  1. أنشئ التعليمات البرمجية وشغِّلها للتأكّد من عدم وجود أخطاء.

في هذه المهمة، عليك تحديد عنصر الوصول إلى البيانات (DAO). على Android، توفّر DAO طرقًا سهلة لإدراج البيانات وحذفها وتعديلها في قاعدة البيانات.

عند استخدام قاعدة بيانات Room، يمكنك الاستعلام عن قاعدة البيانات من خلال تحديد دوال Kotlin واستدعائها في الرمز البرمجي. يتم ربط دوال Kotlin هذه باستعلامات SQL. يمكنك تحديد عمليات الربط هذه في DAO باستخدام التعليقات التوضيحية، وتنشئ Room الرمز اللازم.

يمكنك اعتبار DAO على أنّه يحدّد واجهة مخصّصة للوصول إلى قاعدة البيانات.

بالنسبة إلى عمليات قاعدة البيانات الشائعة، توفّر مكتبة Room تعليقات توضيحية سهلة الاستخدام، مثل @Insert و@Delete و@Update. بالنسبة إلى كل ما عدا ذلك، هناك التعليق التوضيحي @Query. يمكنك كتابة أي طلب بحث متوافق مع SQLite.

بالإضافة إلى ذلك، أثناء إنشاء طلبات البحث في "استوديو Android"، يتحقّق المحوّل البرمجي من طلبات بحث SQL بحثًا عن أخطاء في البنية.

بالنسبة إلى قاعدة بيانات أداة تتبُّع النوم التي تتضمّن ليالي النوم، يجب أن تتمكّن من إجراء ما يلي:

  • أدرِج ليالٍ جديدة.
  • تعديل ليلة حالية لتعديل وقت الانتهاء وتقييم الجودة
  • الحصول على ليلة معيّنة استنادًا إلى مفتاحها
  • الحصول على جميع الليالي، حتى تتمكّن من عرضها
  • الحصول على أحدث ليلة
  • حذف جميع الإدخالات في قاعدة البيانات

الخطوة 1: إنشاء SleepDatabase DAO

  1. في حزمة database، افتح SleepDatabaseDao.kt.
  2. لاحظ أنّ interface SleepDatabaseDao تمّت إضافة التعليق التوضيحي @Dao إليه. يجب إضافة التعليق التوضيحي @Dao إلى جميع عناصر DAO.
@Dao
interface SleepDatabaseDao {}
  1. داخل نص الواجهة، أضِف تعليقًا توضيحيًا @Insert. أسفل @Insert، أضِف الدالة insert() التي تأخذ مثيلاً من الفئة Entity SleepNight كوسيطة.

    هذا كل ما في الأمر. سيُنشئ Room كل الرموز اللازمة لإدراج SleepNight في قاعدة البيانات. عندما تستدعي insert() من رمز Kotlin، ينفِّذ Room طلب بحث SQL لإدراج العنصر في قاعدة البيانات. (ملاحظة: يمكنك تسمية الدالة بأي اسم تريده).
@Insert
fun insert(night: SleepNight)
  1. أضِف تعليقًا توضيحيًا @Update باستخدام الدالة update() لـ SleepNight واحد. الكيان الذي يتم تعديله هو الكيان الذي يتضمّن المفتاح نفسه الذي تم تمريره. يمكنك تعديل بعض أو كل الخصائص الأخرى للعنصر.
@Update
fun update(night: SleepNight)

لا تتوفّر تعليقات توضيحية سهلة الاستخدام للوظائف المتبقية، لذا عليك استخدام التعليق التوضيحي @Query وتقديم طلبات بحث SQLite.

  1. أضِف تعليقًا توضيحيًا @Query مع دالة get() تأخذ وسيطة Long key وتعرض قيمة SleepNight تقبل القيم الخالية. سيظهر لك خطأ بسبب عدم توفّر مَعلمة.
@Query
fun get(key: Long): SleepNight?
  1. يتم تقديم طلب البحث كمعلَمة سلسلة إلى التعليق التوضيحي. أضِف مَعلمة إلى @Query. اجعلها String وهي عبارة عن طلب بحث SQLite.
  • اختَر جميع الأعمدة من daily_sleep_quality_table
  • WHERE يطابق nightId الوسيطة :key.

    لاحظ :key. يمكنك استخدام صيغة النقطتين في طلب البحث للإشارة إلى الوسيطات في الدالة.
("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
  1. أضِف @Query آخر باستخدام الدالة clear() واستعلام SQLite لـ DELETE كل شيء من daily_sleep_quality_table. لا يؤدي طلب البحث هذا إلى حذف الجدول نفسه.

    تحذف التعليق التوضيحي @Delete عنصرًا واحدًا، ويمكنك استخدام @Delete وتقديم قائمة بالليالي المطلوب حذفها. العيب هو أنّك تحتاج إلى استرداد البيانات أو معرفة ما هو موجود في الجدول. التعليق التوضيحي @Delete مناسب لحذف إدخالات معيّنة، ولكنّه غير فعّال لمحو جميع الإدخالات من جدول.
@Query("DELETE FROM daily_sleep_quality_table")
fun clear()
  1. أضِف @Query باستخدام الدالة getTonight(). اجعل SleepNight الذي تعرضه getTonight() قابلاً للقيم الخالية، حتى تتمكّن الدالة من التعامل مع الحالة التي يكون فيها الجدول فارغًا. (يكون الجدول فارغًا في البداية وبعد محو البيانات).

    للحصول على "الليلة" من قاعدة البيانات، اكتب طلب بحث SQLite يعرض العنصر الأول من قائمة النتائج مرتّبة حسب nightId بترتيب تنازلي. استخدِم LIMIT 1 لعرض عنصر واحد فقط.
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
fun getTonight(): SleepNight?
  1. أضِف @Query باستخدام الدالة getAllNights():
  • اطلب من استعلام SQLite عرض جميع الأعمدة من daily_sleep_quality_table، مرتّبة بترتيب تنازلي.
  • أريد من getAllNights() أن تعرض قائمة بكيانات SleepNight على شكل LiveData. يحرص Room على تعديل LiveData باستمرار، ما يعني أنّه عليك الحصول على البيانات مرة واحدة فقط.
  • قد تحتاج إلى استيراد LiveData من androidx.lifecycle.LiveData.
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
fun getAllNights(): LiveData<List<SleepNight>>
  1. على الرغم من أنّك لن ترى أي تغييرات ظاهرة، شغِّل تطبيقك للتأكّد من عدم حدوث أي أخطاء.

في هذه المهمة، ستنشئ قاعدة بيانات Room تستخدم Entity وDAO اللذين أنشأتهما في المهمة السابقة.

عليك إنشاء فئة مجرّدة لحامل قاعدة البيانات، مع إضافة التعليق التوضيحي @Database. تتضمّن هذه الفئة طريقة واحدة تنشئ إما مثيلاً لقاعدة البيانات إذا لم تكن قاعدة البيانات متوفّرة، أو تعرض مرجعًا لقاعدة بيانات حالية.

يتطلّب الحصول على قاعدة بيانات Room بعض الخطوات، لذا إليك العملية العامة قبل البدء في استخدام الرمز:

  • أنشئ public abstract صفًا extends RoomDatabase. هذه الفئة هي بمثابة حامل قاعدة البيانات. الفئة مجرّدة، لأنّ Room تنشئ التنفيذ نيابةً عنك.
  • إضافة تعليقات توضيحية إلى الصف باستخدام @Database في الوسيطات، حدِّد العناصر لقاعدة البيانات واضبط رقم الإصدار.
  • داخل عنصر companion، حدِّد طريقة أو سمة مجرّدة تعرض SleepDatabaseDao. سينشئ Room نص الرسالة نيابةً عنك.
  • تحتاج إلى نسخة واحدة فقط من قاعدة بيانات Room للتطبيق بأكمله، لذا اجعل RoomDatabase عنصرًا فرديًا.
  • استخدِم أداة إنشاء قواعد البيانات في Room لإنشاء قاعدة البيانات فقط إذا لم تكن موجودة. بخلاف ذلك، أعِد قاعدة البيانات الحالية.

الخطوة 1: إنشاء قاعدة البيانات

  1. في حزمة database، افتح SleepDatabase.kt.
  2. في الملف، أنشئ فئة abstract باسم SleepDatabase تتضمّن RoomDatabase.

    أضِف تعليقًا توضيحيًا إلى الفئة باستخدام @Database.
@Database()
abstract class SleepDatabase : RoomDatabase() {}
  1. سيظهر لك خطأ بشأن الكيانات ومعلَمات الإصدار غير المتوفّرة. تتطلّب التعليق التوضيحي @Database عدة وسيطات، حتى يتمكّن Room من إنشاء قاعدة البيانات.
  • قدِّم SleepNight كعنصر واحد فقط مع قائمة entities.
  • اضبط قيمة version على 1. عند تغيير المخطط، عليك زيادة رقم الإصدار.
  • اضبط exportSchema على false، وذلك لكي لا يتم الاحتفاظ بنسخ احتياطية من سجلّ إصدار المخطط.
entities = [SleepNight::class], version = 1, exportSchema = false
  1. يجب أن تكون قاعدة البيانات على دراية بالمنظمة اللامركزية المستقلة. داخل نص الفئة، عرِّف قيمة مجرّدة تعرض SleepDatabaseDao. يمكنك امتلاك عدة منظمات مستقلة لامركزية.
abstract val sleepDatabaseDao: SleepDatabaseDao
  1. أسفل ذلك، حدِّد عنصر companion. يسمح العنصر المرافق للعملاء بالوصول إلى طرق إنشاء قاعدة البيانات أو الحصول عليها بدون إنشاء مثيل للفئة. بما أنّ الغرض الوحيد من هذه الفئة هو توفير قاعدة بيانات، ليس هناك أي سبب لإنشائها.
 companion object {}
  1. داخل العنصر companion، عرِّف المتغيّر الخاص القابل للتصغير INSTANCE لقاعدة البيانات واضبط قيمته الأولية على null. سيحتفظ المتغيّر INSTANCE بإشارة إلى قاعدة البيانات بعد إنشائها. يساعدك ذلك في تجنُّب فتح اتصالات متكررة بقاعدة البيانات، وهو أمر مكلف.

إضافة تعليق توضيحي إلى INSTANCE باستخدام @Volatile لن يتم تخزين قيمة متغيّر متطاير مؤقتًا أبدًا، وسيتم إجراء جميع عمليات الكتابة والقراءة من الذاكرة الرئيسية وإليها. يساعد ذلك في التأكّد من أنّ قيمة INSTANCE تكون دائمًا محدّثة ومتطابقة مع جميع سلاسل التنفيذ. وهذا يعني أنّ التغييرات التي يتم إجراؤها بواسطة سلسلة محادثات واحدة على INSTANCE تكون مرئية لجميع سلاسل المحادثات الأخرى على الفور، ولا يحدث أن تقوم سلسلتا محادثات مثلاً بتعديل الكيان نفسه في ذاكرة التخزين المؤقت، ما قد يؤدي إلى حدوث مشكلة.

@Volatile
private var INSTANCE: SleepDatabase? = null
  1. ضمن INSTANCE، وداخل العنصر companion، حدِّد طريقة getInstance() تتضمّن المَعلمة Context التي يحتاجها منشئ قاعدة البيانات. إرجاع نوع SleepDatabase سيظهر لك خطأ لأنّ getInstance() لم يعرض أي نتائج بعد.
fun getInstance(context: Context): SleepDatabase {}
  1. داخل getInstance()، أضِف مربّع synchronized{}. مرِّر this حتى تتمكّن من الوصول إلى السياق.

    يمكن أن تطلب سلاسل متعددة مثيلاً لقاعدة البيانات في الوقت نفسه، ما يؤدي إلى إنشاء قاعدتَي بيانات بدلاً من واحدة. من غير المحتمل حدوث هذه المشكلة في هذا التطبيق النموذجي، ولكن من المحتمل حدوثها في تطبيق أكثر تعقيدًا. يؤدي تضمين الرمز للحصول على قاعدة البيانات في synchronized إلى إمكانية دخول سلسلة تنفيذ واحدة فقط في كل مرة إلى مجموعة الرموز هذه، ما يضمن عدم تهيئة قاعدة البيانات إلا مرة واحدة.
synchronized(this) {}
  1. داخل الكتلة المتزامنة، انسخ القيمة الحالية INSTANCE إلى المتغيّر المحلي instance. يتم ذلك للاستفادة من التحويل الذكي، الذي لا يتوفّر إلا للمتغيرات المحلية.
var instance = INSTANCE
  1. داخل كتلة synchronized، return instance في نهاية كتلة synchronized تجاهل خطأ عدم تطابق نوع الإرجاع، فلن يتم إرجاع قيمة فارغة بعد الانتهاء.
return instance
  1. أضِف عبارة if فوق عبارة return للتحقّق مما إذا كانت instance فارغة، أي أنّه لم يتم إنشاء قاعدة بيانات بعد.
if (instance == null) {}
  1. إذا كانت قيمة instance هي null، استخدِم أداة إنشاء قاعدة البيانات للحصول على قاعدة بيانات. في نص عبارة if، استدعِ Room.databaseBuilder وقدِّم السياق الذي مرّرته وفئة قاعدة البيانات واسمًا لقاعدة البيانات، sleep_history_database. لإزالة الخطأ، عليك إضافة استراتيجية نقل بيانات وbuild() في الخطوات التالية.
instance = Room.databaseBuilder(
                           context.applicationContext,
                           SleepDatabase::class.java,
                           "sleep_history_database")
  1. أضِف استراتيجية الترحيل المطلوبة إلى أداة الإنشاء. استخدِم .fallbackToDestructiveMigration().

    في العادة، عليك تقديم عنصر نقل بيانات مع استراتيجية نقل بيانات عند تغيير المخطط. عنصر النقل هو عنصر يحدّد كيفية نقل جميع الصفوف التي تتضمّن المخطط القديم وتحويلها إلى صفوف في المخطط الجديد، وذلك لضمان عدم فقدان أي بيانات. نقل البيانات ليس موضوع هذا الدرس التطبيقي. الحلّ البسيط هو إتلاف قاعدة البيانات وإعادة إنشائها، ما يعني فقدان البيانات.
.fallbackToDestructiveMigration()
  1. أخيرًا، اتّصِل بالرقم .build().
.build()
  1. عيِّن INSTANCE = instance كخطوة نهائية داخل عبارة if.
INSTANCE = instance
  1. يجب أن تبدو التعليمات البرمجية النهائية على النحو التالي:
@Database(entities = [SleepNight::class], version = 1, exportSchema = false)
abstract class SleepDatabase : RoomDatabase() {

   abstract val sleepDatabaseDao: SleepDatabaseDao

   companion object {

       @Volatile
       private var INSTANCE: SleepDatabase? = null

       fun getInstance(context: Context): SleepDatabase {
           synchronized(this) {
               var instance = INSTANCE

               if (instance == null) {
                   instance = Room.databaseBuilder(
                           context.applicationContext,
                           SleepDatabase::class.java,
                           "sleep_history_database"
                   )
                           .fallbackToDestructiveMigration()
                           .build()
                   INSTANCE = instance
               }
               return instance
           }
       }
   }
}
  1. إنشاء التعليمات البرمجية وتشغيلها

أصبح لديك الآن جميع اللبنات الأساسية للعمل مع قاعدة بيانات Room. يتم تجميع هذا الرمز وتشغيله، ولكن ليس لديك طريقة لمعرفة ما إذا كان يعمل بالفعل. لذا، هذا هو الوقت المناسب لإضافة بعض الاختبارات الأساسية.

الخطوة 2: اختبار SleepDatabase

في هذه الخطوة، يمكنك إجراء الاختبارات المتوفّرة للتحقّق من أنّ قاعدة البيانات تعمل. يساعد ذلك في ضمان عمل قاعدة البيانات قبل إنشاء أي عناصر عليها. الاختبارات المقدَّمة أساسية. بالنسبة إلى تطبيق جاهز للنشر، عليك استخدام جميع الدوال وطلبات البحث في جميع عناصر الوصول إلى البيانات.

يحتوي التطبيق الأوّلي على مجلد androidTest. يحتوي مجلد androidTest هذا على اختبارات وحدة تتضمّن أدوات Android، وهي طريقة رائعة للقول إنّ الاختبارات تحتاج إلى إطار عمل Android، لذا عليك تشغيل الاختبارات على جهاز فعلي أو افتراضي. بالطبع، يمكنك أيضًا إنشاء اختبارات وحدات خالصة وتنفيذها بدون استخدام إطار عمل Android.

  1. في "استوديو Android"، افتح الملف SleepDatabaseTest في المجلد androidTest.
  2. لإزالة التعليق من الرمز، حدِّد كل الرمز الذي تمّت إضافة تعليق إليه واضغط على اختصار لوحة المفاتيح Cmd+/ أو Control+/.
  3. ألقِ نظرة على الملف.

إليك شرح سريع لرمز الاختبار، لأنّه جزء آخر من الرمز يمكنك إعادة استخدامه:

  • SleepDabaseTest هو صف تجريبي.
  • تحدّد التعليق التوضيحي @RunWith مشغّل الاختبار، وهو البرنامج الذي يضبط الاختبارات وينفّذها.
  • أثناء عملية الإعداد، يتم تنفيذ الدالة التي تمّت إضافة التعليق التوضيحي @Before إليها، وتنشئ SleepDatabase في الذاكرة مع SleepDatabaseDao. يشير مصطلح "في الذاكرة" إلى أنّ قاعدة البيانات هذه لا يتم حفظها في نظام الملفات وسيتم حذفها بعد إجراء الاختبارات.
  • عند إنشاء قاعدة البيانات في الذاكرة، يستدعي الرمز أيضًا طريقة أخرى خاصة بالاختبار، وهي allowMainThreadQueries. يظهر لك خطأ تلقائيًا إذا حاولت تنفيذ طلبات بحث على سلسلة التعليمات الرئيسية. تتيح لك هذه الطريقة إجراء اختبارات على سلسلة التعليمات الرئيسية، وهو ما يجب أن تفعله أثناء الاختبار فقط.
  • في طريقة اختبار تمّت إضافة التعليق التوضيحي @Test إليها، يمكنك إنشاء SleepNight وإدراجه واسترداده، والتأكّد من أنّها متطابقة. في حال حدوث أي خطأ، يجب طرح استثناء. في اختبار حقيقي، ستتوفّر لك طرق @Test متعددة.
  • عند الانتهاء من الاختبار، يتم تنفيذ الدالة التي تمّت إضافة التعليق التوضيحي @After إليها لإغلاق قاعدة البيانات.
  1. انقر بزر الماوس الأيمن على ملف الاختبار في لوحة المشروع (Project) واختَر تشغيل SleepDatabaseTest (Run 'SleepDatabaseTest').
  2. بعد تشغيل الاختبارات، تحقَّق في لوحة SleepDatabaseTest من اجتياز جميع الاختبارات.

بما أنّ جميع الاختبارات قد اجتازت، يمكنك الآن معرفة عدة أمور:

  • يتم إنشاء قاعدة البيانات بشكل صحيح.
  • يمكنك إدراج SleepNight في قاعدة البيانات.
  • يمكنك استعادة SleepNight.
  • يحتوي SleepNight على القيمة الصحيحة للجودة.

مشروع "استوديو Android": TrackMySleepQualityRoomAndTesting

عند اختبار قاعدة بيانات، عليك استخدام جميع الطرق المحدّدة في DAO. لإكمال الاختبار، أضِف الاختبارات ونفِّذها لتجربة طرق DAO الأخرى.

  • عرِّف جداولك كفئات بيانات مزوّدة بالتعليق التوضيحي @Entity. حدِّد السمات التي تمّت إضافة التعليقات التوضيحية إليها باستخدام @ColumnInfo كأعمدة في الجداول.
  • حدِّد كائن الوصول إلى البيانات (DAO) كواجهة مزوّدة بالتعليق التوضيحي @Dao. تعمل فئة DAO على ربط دوال Kotlin باستعلامات قاعدة البيانات.
  • استخدِم التعليقات التوضيحية لتحديد وظائف @Insert و@Delete و@Update.
  • استخدِم التعليق التوضيحي @Query مع سلسلة طلب بحث SQLite كمعلَمة لأي طلبات بحث أخرى.
  • أنشئ فئة مجرّدة تحتوي على دالة getInstance() تعرض قاعدة بيانات.
  • استخدِم الاختبارات المزوّدة بأدوات لتأكيد عمل قاعدة البيانات وكائن الوصول إلى البيانات (DAO) على النحو المتوقّع. يمكنك استخدام الاختبارات المتوفّرة كنموذج.

دورة Udacity التدريبية:

مستندات مطوّري تطبيقات Android:

المستندات والمقالات الأخرى:

يسرد هذا القسم مهامًا منزلية محتملة للطلاب الذين يعملون على هذا الدرس التطبيقي العملي كجزء من دورة تدريبية يقودها مدرّب. على المعلّم تنفيذ ما يلي:

  • حدِّد واجبًا منزليًا إذا لزم الأمر.
  • توضيح كيفية إرسال الواجبات المنزلية للطلاب
  • وضع درجات للواجبات المنزلية

يمكن للمدرّبين استخدام هذه الاقتراحات بالقدر الذي يريدونه، ويجب ألا يترددوا في تكليف الطلاب بأي واجبات منزلية أخرى يرونها مناسبة.

إذا كنت تعمل على هذا الدرس العملي بنفسك، يمكنك استخدام مهام الواجب المنزلي هذه لاختبار معلوماتك.

الإجابة عن هذه الأسئلة

السؤال 1

كيف تشير إلى أنّ الفئة تمثّل كيانًا سيتم تخزينه في قاعدة بيانات Room؟

  • اجعل الصف يمتد DatabaseEntity.
  • إضافة تعليقات توضيحية إلى الصف باستخدام @Entity
  • إضافة تعليقات توضيحية إلى الصف باستخدام @Database
  • اجعل الفئة توسّع RoomEntity وأضِف أيضًا تعليقًا توضيحيًا إلى الفئة باستخدام @Room.

السؤال 2

‫DAO (كائن الوصول إلى البيانات) هي واجهة تستخدمها Room لربط دوال Kotlin بطلبات البحث في قاعدة البيانات.

كيف يمكنك الإشارة إلى أنّ الواجهة تمثّل كائن الوصول إلى البيانات (DAO) لقاعدة بيانات Room؟

  • اجعل الواجهة قابلة للتوسيع RoomDAO.
  • اجعل الواجهة توسّع EntityDao، ثم نفِّذ الطريقة DaoConnection().
  • أضِف تعليقات توضيحية إلى الواجهة باستخدام @Dao.
  • أضِف تعليقات توضيحية إلى الواجهة باستخدام @RoomConnection.

السؤال 3

أيّ من العبارات التالية صحيحة بشأن قاعدة بيانات Room؟ يُرجى اختيار كل ما ينطبق.

  • يمكنك تحديد جداول لقاعدة بيانات Room كفئات بيانات مشروحة.
  • إذا عرضت LiveData من طلب بحث، سيحافظ Room على تحديث LiveData لك في حال تغيّر.LiveData
  • يجب أن تحتوي كل قاعدة بيانات Room على عنصر وصول إلى البيانات واحد فقط.
  • لتحديد فئة كقاعدة بيانات Room، اجعلها فئة فرعية من RoomDatabase وأضِف إليها التعليق التوضيحي @Database.

السؤال 4

أيّ من التعليقات التوضيحية التالية يمكنك استخدامها في واجهة @Dao؟ يُرجى اختيار كل ما ينطبق.

  • @Get
  • @Update
  • @Insert
  • @Query

السؤال 5

كيف يمكنك التأكّد من أنّ قاعدة البيانات تعمل؟ يُرجى اختيار جميع الإجابات المناسبة.

  • كتابة اختبارات مزوّدة بأدوات
  • واصِل كتابة التطبيق وتشغيله إلى أن يعرض البيانات.
  • استبدِل استدعاءات الطرق في واجهة DAO باستدعاءات الطرق المكافئة في الفئة Entity.
  • نفِّذ الدالة verifyDatabase() التي توفّرها المكتبة Room.

الانتقال إلى الدرس التالي: 6.2 الروتينات المشتركة وRoom

للحصول على روابط تؤدي إلى دروس تطبيقية أخرى في هذه الدورة التدريبية، اطّلِع على الصفحة المقصودة الخاصة بالدروس التطبيقية حول أساسيات Android Kotlin.