الغرض من مكوّنات البنية هو تقديم إرشادات بشأن بنية التطبيق، مع توفير مكتبات للمهام الشائعة، مثل إدارة مراحل النشاط واستمرار البيانات. تساعدك "مكوّنات البنية" في تنظيم تطبيقك بطريقة قوية وقابلة للاختبار والصيانة مع استخدام قدر أقل من الرموز النموذجية. تُعدّ مكتبات "مكوّنات الهندسة المتوافقة مع Android" جزءًا من Android Jetpack.
هذا هو إصدار Kotlin من الدرس العملي. يمكنك الاطّلاع على الإصدار بلغة البرمجة Java هنا.
إذا واجهت أي مشاكل (مثل أخطاء في الرمز أو أخطاء نحوية أو صياغة غير واضحة أو غير ذلك) أثناء العمل على هذا الدرس العملي، يُرجى الإبلاغ عن المشكلة من خلال الرابط الإبلاغ عن خطأ في أسفل يمين الدرس العملي.
المتطلبات الأساسية
يجب أن تكون على دراية بلغة Kotlin ومفاهيم التصميم المستند إلى الكائنات وأساسيات تطوير تطبيقات Android، وخاصةً ما يلي:
RecyclerView
والمحوّلات- قاعدة بيانات SQLite ولغة استعلام SQLite
- الروتينات الفرعية الأساسية (إذا لم تكن على دراية بالروتينات الفرعية، يمكنك الاطّلاع على استخدام الروتينات الفرعية في Kotlin في تطبيق Android)
من المفيد أيضًا التعرّف على أنماط تصميم البرامج التي تفصل البيانات عن واجهة المستخدم، مثل MVP أو MVC. يطبِّق هذا الدرس التطبيقي حول الترميز البنية المحدّدة في دليل بنية التطبيق.
يركّز هذا الدرس التطبيقي حول الترميز على "مكوّنات الهندسة المتوافقة مع Android". يتم توفير المفاهيم والرموز البرمجية غير ذات الصلة لتتمكّن من نسخها ولصقها ببساطة.
إذا لم تكن على دراية بلغة Kotlin، يتوفّر إصدار من هذا الدرس التعليمي البرمجي بلغة Java هنا.
المهام التي ستنفذها
في هذا الدرس التطبيقي حول الترميز، ستتعلّم كيفية تصميم تطبيق وإنشائه باستخدام "مكوّنات البنية" Room وViewModel وLiveData، وإنشاء تطبيق ينفّذ ما يلي:
- تنفِّذ البنية المقترَحة باستخدام "مكوّنات الهندسة المتوافقة مع Android".
- تعمل مع قاعدة بيانات للحصول على البيانات وحفظها، وتعبئة قاعدة البيانات مسبقًا ببعض الكلمات.
- تعرِض هذه السمة كل الكلمات في
RecyclerView
فيMainActivity
. - يفتح نشاطًا ثانيًا عندما ينقر المستخدم على الزر +. عندما يُدخل المستخدم كلمة، تتم إضافتها إلى قاعدة البيانات والقائمة.
التطبيق بسيط، ولكنه معقّد بما يكفي لاستخدامه كنموذج يمكنك البناء عليه. إليك معاينة:
المتطلبات
- الإصدار 3.0 من "استوديو Android" أو إصدار أحدث ومعرفة كيفية استخدامه تأكَّد من تحديث "استوديو Android" وحزمة تطوير البرامج (SDK) وGradle.
- جهاز Android أو محاكي Android
يوفّر لك هذا الدرس التطبيقي حول الترميز كل الرموز البرمجية التي تحتاج إليها لإنشاء التطبيق الكامل.
هناك العديد من الخطوات لاستخدام "مكوّنات البنية" وتنفيذ البنية المقترَحة. الأمر الأكثر أهمية هو إنشاء نموذج ذهني لما يحدث، وفهم كيفية تناسب الأجزاء معًا وكيفية تدفق البيانات. أثناء العمل على هذا الدرس التطبيقي، لا تكتفِ بنسخ الرمز البرمجي ولصقه، بل حاوِل فهمه جيدًا.
ما هي "مكوّنات البنية" المقترَحة؟
للتعريف بالمصطلحات، إليك مقدّمة قصيرة عن "مكوّنات البنية" وكيفية عملها معًا. يُرجى العِلم أنّ هذا الدرس العملي يركّز على مجموعة فرعية من المكوّنات، وهي LiveData وViewModel وRoom. يتم شرح كل مكوّن بشكل أكبر أثناء استخدامه.
يعرض هذا الرسم التخطيطي نموذجًا أساسيًا للبنية:
الكيان: فئة مشروحة تصف جدول قاعدة بيانات عند استخدام Room
قاعدة بيانات SQLite: يتم تخزينها على الجهاز. تنشئ مكتبة Room للبيانات الثابتة قاعدة البيانات هذه وتحتفظ بها نيابةً عنك.
كائن الوصول إلى البيانات (DAO): هو كائن يوفّر واجهة بين التطبيق وقاعدة البيانات. تتضمّن هذه السمة عملية ربط بين استعلامات SQL والدوال. عند استخدام كائن الوصول إلى البيانات، يمكنك استدعاء الطرق، ويتولّى Room بقية المهام.
قاعدة بيانات Room: تبسّط عملية استخدام قاعدة البيانات وتعمل كنقطة وصول إلى قاعدة بيانات SQLite الأساسية (تخفي SQLiteOpenHelper)
). تستخدم قاعدة بيانات Room كائن الوصول إلى البيانات (DAO) لإصدار طلبات بحث إلى قاعدة بيانات SQLite.
المستودع: هو فئة تنشئها وتُستخدم بشكل أساسي لإدارة مصادر بيانات متعددة.
ViewModel: يعمل كمركز اتصال بين المستودع (البيانات) وواجهة المستخدم. لم يعُد على واجهة المستخدم القلق بشأن مصدر البيانات. تستمر مثيلات ViewModel بعد إعادة إنشاء النشاط/الجزء.
LiveData: فئة حاوية بيانات يمكن مراقبتها. يحتفظ دائمًا بأحدث إصدار من البيانات ويخزّنه مؤقتًا، ويُرسل إشعارات إلى المراقبين عند تغيُّر البيانات. LiveData
يراعي مراحل النشاط. تراقب مكوّنات واجهة المستخدم البيانات ذات الصلة فقط ولا تتوقف عن المراقبة أو تستأنفها. تتولّى LiveData إدارة كل ذلك تلقائيًا لأنّها على دراية بتغييرات حالة مراحل النشاط ذات الصلة أثناء المراقبة.
نظرة عامة على بنية RoomWordSample
يوضّح المخطّط التالي جميع أجزاء التطبيق. يمثّل كل مربع من المربعات المحيطة (باستثناء قاعدة بيانات SQLite) فئة ستنشئها.
- افتح "استوديو Android" وانقر على بدء مشروع جديد في "استوديو Android".
- في نافذة "إنشاء مشروع جديد"، اختَر نشاط فارغ وانقر على التالي.
- في الشاشة التالية، أطلِق على التطبيق اسم RoomWordSample، ثم انقر على إنهاء.
بعد ذلك، عليك إضافة مكتبات المكوّنات إلى ملفات Gradle.
- في "استوديو Android"، انقر على علامة التبويب "المشاريع" (Projects) ووسِّع مجلد "برامج Gradle النصية" (Gradle Scripts).
افتح build.gradle
(الوحدة: app).
- طبِّق المكوّن الإضافي لمعالج التعليقات التوضيحية
kapt
في Kotlin عن طريق إضافته بعد المكوّنات الإضافية الأخرى المحدّدة في أعلى ملفbuild.gradle
(الوحدة: التطبيق).
apply plugin: 'kotlin-kapt'
- أضِف كتلة
packagingOptions
داخل كتلةandroid
لاستبعاد وحدة الدوال الذرية من الحزمة ومنع ظهور التحذيرات.
android {
// other configuration (buildTypes, defaultConfig, etc.)
packagingOptions {
exclude 'META-INF/atomicfu.kotlin_module'
}
}
- أضِف الرمز التالي في نهاية الحظر
dependencies
.
// Room components
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
kapt "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
kapt "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.archLifecycleVersion"
// Kotlin components
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$rootProject.coroutines"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines"
// Material design
implementation "com.google.android.material:material:$rootProject.materialVersion"
// Testing
testImplementation 'junit:junit:4.12'
androidTestImplementation "androidx.arch.core:core-testing:$rootProject.coreTestingVersion"
- في ملف
build.gradle
(Project: RoomWordsSample)، أضِف أرقام الإصدارات إلى نهاية الملف، كما هو موضّح في الرمز البرمجي أدناه.
ext {
roomVersion = '2.2.5'
archLifecycleVersion = '2.2.0'
coreTestingVersion = '2.1.0'
materialVersion = '1.1.0'
coroutines = '1.3.4'
}
بيانات هذا التطبيق هي كلمات، وستحتاج إلى جدول بسيط لتخزين هذه القيم:
تتيح لك Room إنشاء جداول من خلال كيان. لنبدأ الآن.
- أنشئ ملف فئة Kotlin جديدًا باسم
Word
يحتوي علىWord
فئة البيانات.
سيصف هذا الصف الكيان (الذي يمثّل جدول SQLite) الخاص بكلماتك. يمثّل كل سمة في الفئة عمودًا في الجدول. سيستخدم Room في النهاية هذه الخصائص لإنشاء الجدول وإنشاء مثيلات للكائنات من الصفوف في قاعدة البيانات.
إليك الرمز:
data class Word(val word: String)
لجعل فئة Word
مفيدة لقاعدة بيانات Room، عليك إضافة تعليق توضيحي إليها. تحدّد التعليقات التوضيحية كيفية ارتباط كل جزء من هذه الفئة بإدخال في قاعدة البيانات. يستخدم Room هذه المعلومات لإنشاء الرمز.
إذا كتبت التعليقات التوضيحية بنفسك (بدلاً من لصقها)، سيستورد Android Studio تلقائيًا فئات التعليقات التوضيحية.
- عدِّل فئة
Word
بإضافة التعليقات التوضيحية كما هو موضّح في الرمز التالي:
@Entity(tableName = "word_table")
class Word(@PrimaryKey @ColumnInfo(name = "word") val word: String)
في ما يلي شرح لوظيفة هذه التعليقات التوضيحية:
@Entity(tableName =
"word_table"
)
يمثّل كل فئة@Entity
جدول SQLite. أضِف تعليقًا توضيحيًا إلى تعريف الفئة للإشارة إلى أنّها كيان. يمكنك تحديد اسم الجدول إذا أردت أن يكون مختلفًا عن اسم الفئة. يؤدي ذلك إلى تسمية الجدول "word_table".@PrimaryKey
يحتاج كل عنصر إلى مفتاح أساسي. لتبسيط الأمور، تعمل كل كلمة كمفتاح أساسي خاص بها.@ColumnInfo(name =
"word"
)
تحدّد هذه السمة اسم العمود في الجدول إذا أردت أن يكون مختلفًا عن اسم المتغيّر التابع. يؤدي ذلك إلى تسمية العمود "كلمة".- يجب أن يكون كل موقع يتم تخزينه في قاعدة البيانات مرئيًا للجميع، وهو الإعداد التلقائي في Kotlin.
يمكنك العثور على قائمة كاملة بالتعليقات التوضيحية في مرجع ملخّص حزمة Room.
ما هي المنظمة المستقلة اللامركزية؟
في DAO (كائن الوصول إلى البيانات)، يمكنك تحديد استعلامات SQL وربطها باستدعاءات الطرق. يتحقّق المحوّل البرمجي من SQL وينشئ طلبات بحث من التعليقات التوضيحية المريحة لطلبات البحث الشائعة، مثل @Insert
. تستخدم Room كائن الوصول إلى البيانات لإنشاء واجهة برمجة تطبيقات نظيفة للرمز البرمجي.
يجب أن تكون DAO واجهة أو فئة مجرّدة.
يجب تنفيذ جميع طلبات البحث تلقائيًا في سلسلة محادثات منفصلة.
تتيح مكتبة Room استخدام الروتينات المشتركة، ما يسمح بإضافة التعليقات التوضيحية إلى طلبات البحث باستخدام المعدِّل suspend
ثم استدعاؤها من روتين مشترك أو من دالة تعليق أخرى.
تنفيذ DAO
لنكتب DAO يوفّر طلبات بحث عن:
- الحصول على جميع الكلمات مرتّبة أبجديًا
- إدراج كلمة
- حذف كل الكلمات
- أنشئ ملف فئة Kotlin جديدًا باسم
WordDao
. - انسخ الرمز التالي والصِقه في
WordDao
وعدِّل عمليات الاستيراد حسب الحاجة لكي يتم تجميعه.
@Dao
interface WordDao {
@Query("SELECT * from word_table ORDER BY word ASC")
fun getAlphabetizedWords(): List<Word>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(word: Word)
@Query("DELETE FROM word_table")
suspend fun deleteAll()
}
لنستعرض الخطوات:
-
WordDao
هي واجهة، ويجب أن تكون كائنات الوصول إلى البيانات إما واجهات أو فئات مجرّدة. - تحدّد التعليق التوضيحي
@Dao
أنّها فئة DAO في Room. -
suspend fun insert(word: Word)
: تعرِّف دالة تعليق لإدراج كلمة واحدة. @Insert
التعليق التوضيحي هو تعليق توضيحي خاص بطريقة DAO لا تحتاج فيه إلى تقديم أي SQL. (هناك أيضًا التعليقان التوضيحيان@Delete
و@Update
لحذف الصفوف وتعديلها، ولكنك لا تستخدمهما في هذا التطبيق).onConflict = OnConflictStrategy.IGNORE
: تتجاهل استراتيجية onConflict المحدّدة كلمة جديدة إذا كانت مطابقة تمامًا لكلمة موجودة في القائمة. لمزيد من المعلومات عن استراتيجيات التعارض المتاحة، اطّلِع على المستندات.-
suspend fun deleteAll()
: تعرّف هذه السمة دالة تعليق لحذف جميع الكلمات. - لا تتوفّر تعليقات توضيحية سهلة لحذف عناصر متعدّدة، لذا يتمّ استخدام التعليق التوضيحي العام
@Query
. -
@Query
("DELETE FROM word_table")
: يتطلّب@Query
منك تقديم طلب بحث SQL كمعلَمة سلسلة إلى التعليق التوضيحي، ما يتيح طلبات بحث معقّدة للقراءة وعمليات أخرى. fun getAlphabetizedWords(): List<Word>
: طريقة للحصول على جميع الكلمات وإرجاعList
منWords
.-
@Query(
"SELECT * from word_table ORDER BY word ASC"
)
: طلب بحث يعرض قائمة بالكلمات مرتّبة ترتيبًا تصاعديًا.
عندما تتغيّر البيانات، عليك عادةً اتّخاذ بعض الإجراءات، مثل عرض البيانات المعدَّلة في واجهة المستخدم. وهذا يعني أنّه عليك مراقبة البيانات حتى تتمكّن من التفاعل عند تغيُّرها.
قد يكون ذلك صعبًا حسب طريقة تخزين البيانات. يمكن أن يؤدي رصد التغييرات في البيانات على مستوى عدّة مكوّنات في تطبيقك إلى إنشاء مسارات تبعية صريحة وثابتة بين المكوّنات. ويؤدي ذلك إلى صعوبة الاختبار وتصحيح الأخطاء، وغير ذلك.
LiveData
، وهو فئة مكتبة دورة الحياة لمراقبة البيانات، يحلّ هذه المشكلة. استخدِم قيمة إرجاع من النوع LiveData
في وصف طريقتك، وسينشئ Room كل الرموز اللازمة لتعديل LiveData
عند تعديل قاعدة البيانات.
في WordDao
، غيِّر توقيع طريقة getAlphabetizedWords()
بحيث يتم تضمين List<Word>
الذي تم إرجاعه في LiveData
.
@Query("SELECT * from word_table ORDER BY word ASC")
fun getAlphabetizedWords(): LiveData<List<Word>>
في وقت لاحق من هذا الدرس العملي، ستتتبّع تغييرات البيانات من خلال Observer
في MainActivity
.
ما هي قاعدة بيانات Room؟
- Room هي طبقة قاعدة بيانات تستند إلى قاعدة بيانات SQLite.
- تتولّى Room المهام الروتينية التي كنت تنفّذها باستخدام
SQLiteOpenHelper
. - يستخدم Room كائن الوصول إلى البيانات (DAO) لإصدار طلبات بحث إلى قاعدة البيانات.
- بشكلٍ تلقائي، لتجنُّب ضعف أداء واجهة المستخدم، لا يسمح لك Room بإصدار طلبات بحث في سلسلة التعليمات الرئيسية. عندما تعرض طلبات بحث Room
LiveData
، يتم تنفيذ طلبات البحث تلقائيًا بشكل غير متزامن في سلسلة محادثات في الخلفية. - توفّر Room عمليات تحقّق في وقت التجميع لعبارات SQLite.
تنفيذ قاعدة بيانات Room
يجب أن يكون صف قاعدة بيانات Room مجرّدًا وأن يمتد إلى RoomDatabase
. عادةً، تحتاج إلى نسخة واحدة فقط من قاعدة بيانات Room للتطبيق بأكمله.
لننشئ واحدًا الآن.
- أنشئ ملف فئة Kotlin باسم
WordRoomDatabase
وأضِف الرمز التالي إليه:
// Annotates class to be a Room Database with a table (entity) of the Word class
@Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
public abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
@Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context): WordRoomDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WordRoomDatabase::class.java,
"word_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
لنتعرّف على التعليمات البرمجية:
- يجب أن يكون فئة قاعدة البيانات في Room هي
abstract
وأن يتم توسيعها لتشملRoomDatabase
- يمكنك إضافة التعليق التوضيحي
@Database
إلى الفئة لتصبح قاعدة بيانات Room، واستخدام مَعلمات التعليق التوضيحي للإعلان عن الكيانات التي تنتمي إلى قاعدة البيانات وتحديد رقم الإصدار. يتوافق كل عنصر مع جدول سيتم إنشاؤه في قاعدة البيانات. لا تتناول هذه السلسلة التعليمية عمليات نقل البيانات في قاعدة البيانات، لذا سنضبط قيمةexportSchema
على false هنا لتجنُّب ظهور تحذير أثناء الإنشاء. في تطبيق حقيقي، عليك التفكير في ضبط دليل لاستخدامه في Room لتصدير المخطط حتى تتمكّن من التحقّق من المخطط الحالي في نظام التحكّم في الإصدار. - تعرض قاعدة البيانات عناصر DAO من خلال طريقة "getter" مجرّدة لكل @Dao.
- لقد حدّدنا نمط تصميم singleton،
WordRoomDatabase,
لمنع فتح عدة مثيلات من قاعدة البيانات في الوقت نفسه. - تعرض الدالة
getDatabase
العنصر الفردي. سيتم إنشاء قاعدة البيانات عند الوصول إليها لأول مرة، وذلك باستخدام أداة إنشاء قاعدة البيانات في Room لإنشاء عنصرRoomDatabase
في سياق التطبيق من الفئةWordRoomDatabase
وتسميته"word_database"
.
ما هو المستودع؟
تجرِّد فئة المستودع إمكانية الوصول إلى مصادر بيانات متعددة. لا يشكّل المستودع جزءًا من مكتبات "مكوّنات البنية"، ولكنّه يُعدّ من أفضل الممارسات المقترَحة لفصل الرموز البرمجية والبنية. يوفّر فئة Repository واجهة برمجة تطبيقات واضحة للوصول إلى البيانات لبقية التطبيق.
لماذا يجب استخدام مستودع؟
يدير المستودع طلبات البحث ويسمح لك باستخدام عدة أنظمة خلفية. في المثال الأكثر شيوعًا، ينفّذ مستودع البيانات منطق تحديد ما إذا كان سيتم جلب البيانات من شبكة أو استخدام النتائج المخزّنة مؤقتًا في قاعدة بيانات محلية.
تنفيذ المستودع
أنشئ ملف فئة Kotlin باسم WordRepository
والصِق الرمز التالي فيه:
// Declares the DAO as a private property in the constructor. Pass in the DAO
// instead of the whole database, because you only need access to the DAO
class WordRepository(private val wordDao: WordDao) {
// Room executes all queries on a separate thread.
// Observed LiveData will notify the observer when the data has changed.
val allWords: LiveData<List<Word>> = wordDao.getAlphabetizedWords()
suspend fun insert(word: Word) {
wordDao.insert(word)
}
}
النقاط الرئيسية:
- يتم تمرير DAO إلى أداة إنشاء المستودع بدلاً من قاعدة البيانات بأكملها. ويرجع ذلك إلى أنّها تحتاج فقط إلى الوصول إلى DAO، لأنّ DAO يحتوي على جميع طرق القراءة والكتابة لقاعدة البيانات. ليست هناك حاجة إلى عرض قاعدة البيانات بأكملها على المستودع.
- قائمة الكلمات هي سمة عامة. يتم تهيئته من خلال الحصول على قائمة
LiveData
بالكلمات من Room، ويمكننا إجراء ذلك بسبب طريقة تحديدنا للطريقةgetAlphabetizedWords
لعرضLiveData
في خطوة "فئة LiveData". ينفّذ Room جميع طلبات البحث في سلسلة محادثات منفصلة. بعد ذلك، سيُرسِلLiveData
الذي تم رصده إشعارًا إلى المراقب في سلسلة التعليمات الرئيسية عند تغيير البيانات. - يُعلم المعدِّل
suspend
المترجم بأنّه يجب استدعاء هذا المعدِّل من روتين فرعي أو دالة تعليق أخرى.
ما هي ViewModel؟
دور ViewModel
هو توفير البيانات لواجهة المستخدم والحفاظ عليها عند إجراء تغييرات في الإعداد. يعمل ViewModel
كمركز اتصال بين المستودع وواجهة المستخدم. يمكنك أيضًا استخدام ViewModel
لمشاركة البيانات بين الأجزاء. ViewModel هو جزء من مكتبة دورة الحياة.
للحصول على دليل تمهيدي حول هذا الموضوع، اطّلِع على ViewModel Overview
أو مشاركة المدوّنة ViewModels: A Simple Example.
لماذا يجب استخدام ViewModel؟
تحتفظ ViewModel
ببيانات واجهة المستخدم لتطبيقك بطريقة تراعي مراحل النشاط وتتجاوز تغييرات الإعدادات. يسمح لك فصل بيانات واجهة المستخدم لتطبيقك عن الفئتَين Activity
وFragment
باتّباع مبدأ المسؤولية الفردية بشكل أفضل: تكون الأنشطة واللقطات مسؤولة عن عرض البيانات على الشاشة، بينما يمكن أن تتولّى ViewModel
مهمة الاحتفاظ بجميع البيانات اللازمة لواجهة المستخدم ومعالجتها.
في ViewModel
، استخدِم LiveData
للبيانات القابلة للتغيير التي ستستخدمها واجهة المستخدم أو تعرضها. يوفّر استخدام LiveData
العديد من المزايا:
- يمكنك وضع مراقب على البيانات (بدلاً من طلب التغييرات بشكل متكرّر) وتعديل
واجهة المستخدم فقط عند حدوث تغيير في البيانات. - يتم فصل المستودع وواجهة المستخدم تمامًا باستخدام
ViewModel
. - لا يتم إجراء أي طلبات من قاعدة البيانات من
ViewModel
(يتم التعامل مع كل ذلك في المستودع)، ما يجعل الرمز البرمجي قابلاً للاختبار بشكل أكبر.
viewModelScope
في Kotlin، يتم تشغيل جميع الروتينات الفرعية داخل CoroutineScope
. يتحكّم النطاق في مدة بقاء الروتينات الفرعية من خلال مهمتها. عند إلغاء مهمة نطاق، يتم إلغاء جميع الروتينات الفرعية التي تم بدءها في هذا النطاق.
تضيف مكتبة AndroidX lifecycle-viewmodel-ktx
viewModelScope
كدالة إضافية لفئة ViewModel
، ما يتيح لك استخدام النطاقات.
لمزيد من المعلومات حول استخدام الكوروتينات في ViewModel، يمكنك الاطّلاع على الخطوة 5 من الدرس التطبيقي حول الترميز استخدام الكوروتينات في Kotlin في تطبيق Android أو مشاركة منشور المدونة "الكوروتينات السهلة في Android: viewModelScope".
تنفيذ ViewModel
أنشئ ملف فئة Kotlin لـ WordViewModel
وأضِف الرمز التالي إليه:
class WordViewModel(application: Application) : AndroidViewModel(application) {
private val repository: WordRepository
// Using LiveData and caching what getAlphabetizedWords returns has several benefits:
// - We can put an observer on the data (instead of polling for changes) and only update the
// the UI when the data actually changes.
// - Repository is completely separated from the UI through the ViewModel.
val allWords: LiveData<List<Word>>
init {
val wordsDao = WordRoomDatabase.getDatabase(application).wordDao()
repository = WordRepository(wordsDao)
allWords = repository.allWords
}
/**
* Launching a new coroutine to insert the data in a non-blocking way
*/
fun insert(word: Word) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(word)
}
}
في ما يلي:
- تم إنشاء فئة باسم
WordViewModel
تتلقّىApplication
كمَعلمة وتوسّعAndroidViewModel
. - تمت إضافة متغيّر عضو خاص للاحتفاظ بمرجع إلى المستودع.
- تمت إضافة متغيّر عضو
LiveData
عام لتخزين قائمة الكلمات مؤقتًا. - تم إنشاء حظر
init
يحصل على إشارة إلىWordDao
منWordRoomDatabase
. - في المربّع
init
، تم إنشاءWordRepository
استنادًا إلىWordRoomDatabase
. - في الحزمة
init
، تمّت تهيئةallWords
LiveData باستخدام المستودع. - تم إنشاء طريقة
insert()
لتغليف البيانات تستدعي طريقةinsert()
في المستودع. بهذه الطريقة، يتم تغليف تنفيذinsert()
من واجهة المستخدم. لا نريد أن تحظر عملية الإدراج سلسلة التعليمات الرئيسية، لذلك سنطلق روتينًا فرعيًا جديدًا ونستدعي عملية الإدراج في المستودع، وهي دالة تعليق. كما ذكرنا، تحتوي ViewModels على نطاق روتين فرعي يستند إلى دورة حياتها ويُسمىviewModelScope
، وهو ما نستخدمه هنا.
بعد ذلك، عليك إضافة تنسيق XML للقائمة والعناصر.
يفترض هذا الدرس العملي أنّك على دراية بإنشاء التصاميم في XML، لذا سنقدّم لك الرمز البرمجي فقط.
اجعل مظهر تطبيقك متوافقًا مع Material Design من خلال ضبط العنصر الرئيسي AppTheme
على Theme.MaterialComponents.Light.DarkActionBar
. أضِف نمطًا لعناصر القائمة في values/styles.xml
:
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- The default font for RecyclerView items is too small.
The margin is a simple delimiter between the words. -->
<style name="word_title">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_marginBottom">8dp</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:background">@android:color/holo_orange_light</item>
<item name="android:textAppearance">@android:style/TextAppearance.Large</item>
</style>
</resources>
إضافة تصميم layout/recyclerview_item.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView"
style="@style/word_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_orange_light" />
</LinearLayout>
في layout/activity_main.xml
، استبدِل TextView
بـ RecyclerView
وأضِف زر إجراء عائمًا (FAB). يجب أن يبدو التنسيق الآن على النحو التالي:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="0dp"
android:layout_height="0dp"
tools:listitem="@layout/recyclerview_item"
android:padding="@dimen/big_padding"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:contentDescription="@string/add_word"/>
</androidx.constraintlayout.widget.ConstraintLayout>
يجب أن يتوافق مظهر زر الإجراء العائم مع الإجراء المتاح، لذا سنستبدل الرمز بعلامة "+".
أولاً، علينا إضافة Vector Asset جديد:
- اختَر ملف > جديد > عنصر متّجه.
- انقر على رمز روبوت Android في الحقل قصاصة فنية: .
- ابحث عن "إضافة" واختَر مادة العرض "+". انقر على حسنًا
.
- بعد ذلك، انقر على التالي.
- أكِّد مسار الرمز على أنّه
main > drawable
وانقر على إنهاء لإضافة مادة العرض. - في
layout/activity_main.xml
، عدِّل الزر العائم لإضافة العنصر الجديد القابل للرسم:
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:contentDescription="@string/add_word"
android:src="@drawable/ic_add_black_24dp"/>
سنعرض البيانات في RecyclerView
، وهو أفضل من عرض البيانات في TextView
. يفترض هذا الدرس العملي أنّك تعرف طريقة عمل RecyclerView
وRecyclerView.LayoutManager
وRecyclerView.ViewHolder
وRecyclerView.Adapter
.
يُرجى العِلم أنّ المتغيّر words
في أداة الربط يخزّن البيانات مؤقتًا. في المهمة التالية، ستضيف الرمز الذي يعدّل البيانات تلقائيًا.
أنشئ ملف فئة Kotlin لـ WordListAdapter
يوسّع RecyclerView.Adapter
. إليك الرمز:
class WordListAdapter internal constructor(
context: Context
) : RecyclerView.Adapter<WordListAdapter.WordViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var words = emptyList<Word>() // Cached copy of words
inner class WordViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val wordItemView: TextView = itemView.findViewById(R.id.textView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder {
val itemView = inflater.inflate(R.layout.recyclerview_item, parent, false)
return WordViewHolder(itemView)
}
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val current = words[position]
holder.wordItemView.text = current.word
}
internal fun setWords(words: List<Word>) {
this.words = words
notifyDataSetChanged()
}
override fun getItemCount() = words.size
}
أضِف RecyclerView
في طريقة onCreate()
الخاصة بـ MainActivity
.
في الطريقة onCreate()
بعد setContentView
:
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
val adapter = WordListAdapter(this)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
شغِّل تطبيقك للتأكّد من أنّ كل شيء يعمل. لا تتوفّر أي عناصر لأنّك لم تربط البيانات بعد.
لا تتضمّن قاعدة البيانات أي بيانات. ستضيف البيانات بطريقتَين: إضافة بعض البيانات عند فتح قاعدة البيانات، وإضافة Activity
لإضافة الكلمات.
لحذف كل المحتوى وإعادة ملء قاعدة البيانات كلما تم تشغيل التطبيق، عليك إنشاء RoomDatabase.Callback
وتجاوز onOpen()
. بما أنّه لا يمكنك تنفيذ عمليات قاعدة بيانات Room على سلسلة التعليمات الخاصة بواجهة المستخدم، فإنّ onOpen()
تُطلق روتينًا فرعيًا على IO Dispatcher.
لتشغيل روتين فرعي، نحتاج إلى CoroutineScope
. عدِّل طريقة getDatabase
في الفئة WordRoomDatabase
للحصول أيضًا على نطاق روتين فرعي كمعلَمة:
fun getDatabase(
context: Context,
scope: CoroutineScope
): WordRoomDatabase {
...
}
عدِّل أداة تهيئة استرجاع قاعدة البيانات في الحظر init
من WordViewModel
لتمرير النطاق أيضًا:
val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()
في WordRoomDatabase
، ننشئ عملية تنفيذ مخصّصة لـ RoomDatabase.Callback()
، والتي تحصل أيضًا على CoroutineScope
كمعلّمة للدالة الإنشائية. بعد ذلك، نتجاوز طريقة onOpen
لتعبئة قاعدة البيانات.
في ما يلي الرمز البرمجي لإنشاء دالة رد الاتصال داخل الفئة WordRoomDatabase
:
private class WordDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.wordDao())
}
}
}
suspend fun populateDatabase(wordDao: WordDao) {
// Delete all content here.
wordDao.deleteAll()
// Add sample words.
var word = Word("Hello")
wordDao.insert(word)
word = Word("World!")
wordDao.insert(word)
// TODO: Add your own words!
}
}
أخيرًا، أضِف دالة معاودة الاتصال إلى تسلسل إنشاء قاعدة البيانات قبل استدعاء .build()
مباشرةً في Room.databaseBuilder()
:
.addCallback(WordDatabaseCallback(scope))
في ما يلي الشكل الذي يجب أن يبدو عليه الرمز النهائي:
@Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
private class WordDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let { database ->
scope.launch {
var wordDao = database.wordDao()
// Delete all content here.
wordDao.deleteAll()
// Add sample words.
var word = Word("Hello")
wordDao.insert(word)
word = Word("World!")
wordDao.insert(word)
// TODO: Add your own words!
word = Word("TODO!")
wordDao.insert(word)
}
}
}
}
companion object {
@Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(
context: Context,
scope: CoroutineScope
): WordRoomDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WordRoomDatabase::class.java,
"word_database"
)
.addCallback(WordDatabaseCallback(scope))
.build()
INSTANCE = instance
// return instance
instance
}
}
}
}
أضِف موارد السلسلة هذه في values/strings.xml
:
<string name="hint_word">Word...</string>
<string name="button_save">Save</string>
<string name="empty_not_saved">Word not saved because it is empty.</string>
أضِف مرجع اللون هذا في value/colors.xml
:
<color name="buttonLabel">#FFFFFF</color>
أنشئ ملف موارد جديدًا للأبعاد:
- انقر على وحدة التطبيق في نافذة المشروع.
- اختَر ملف > جديد > ملف موارد Android
- من "المؤهّلات المتاحة"، اختَر السمة .
- ضبط اسم الملف: dimens
أضِف مراجع السمات هذه في values/dimens.xml
:
<dimen name="small_padding">8dp</dimen>
<dimen name="big_padding">16dp</dimen>
أنشئ مشروع Android Activity
فارغًا باستخدام نموذج Empty Activity:
- اختَر ملف > جديد > نشاط > نشاط فارغ
- أدخِل
NewWordActivity
في حقل "اسم النشاط". - تأكَّد من إضافة النشاط الجديد إلى ملف AndroidManifest.xml.
<activity android:name=".NewWordActivity"></activity>
عدِّل ملف activity_new_word.xml
في مجلد التصميم باستخدام الرمز التالي:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/edit_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/min_height"
android:fontFamily="sans-serif-light"
android:hint="@string/hint_word"
android:inputType="textAutoComplete"
android:layout_margin="@dimen/big_padding"
android:textSize="18sp" />
<Button
android:id="@+id/button_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:text="@string/button_save"
android:layout_margin="@dimen/big_padding"
android:textColor="@color/buttonLabel" />
</LinearLayout>
عدِّل رمز النشاط:
class NewWordActivity : AppCompatActivity() {
private lateinit var editWordView: EditText
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_word)
editWordView = findViewById(R.id.edit_word)
val button = findViewById<Button>(R.id.button_save)
button.setOnClickListener {
val replyIntent = Intent()
if (TextUtils.isEmpty(editWordView.text)) {
setResult(Activity.RESULT_CANCELED, replyIntent)
} else {
val word = editWordView.text.toString()
replyIntent.putExtra(EXTRA_REPLY, word)
setResult(Activity.RESULT_OK, replyIntent)
}
finish()
}
}
companion object {
const val EXTRA_REPLY = "com.example.android.wordlistsql.REPLY"
}
}
الخطوة الأخيرة هي ربط واجهة المستخدم بقاعدة البيانات من خلال حفظ الكلمات الجديدة التي يدخلها المستخدم وعرض المحتوى الحالي لقاعدة بيانات الكلمات في RecyclerView
.
لعرض المحتوى الحالي لقاعدة البيانات، أضِف مراقبًا يراقب LiveData
في ViewModel
.
عندما تتغيّر البيانات، يتم استدعاء معاودة الاتصال onChanged()
، ما يؤدي إلى استدعاء طريقة setWords()
الخاصة بالمحوّل لتعديل البيانات المخزّنة مؤقتًا في المحوّل وإعادة تحميل القائمة المعروضة.
في MainActivity
، أنشئ متغيّر عضو لـ ViewModel
:
private lateinit var wordViewModel: WordViewModel
استخدِم ViewModelProvider
لربط ViewModel
بموقعك الإلكتروني Activity
.
عندما يبدأ Activity
للمرة الأولى، سيُنشئ ViewModelProviders
ViewModel
. عند إيقاف النشاط، مثلاً من خلال تغيير الإعداد، يستمر ViewModel
. عند إعادة إنشاء النشاط، ستعرض ViewModelProviders
ViewModel
الحالي. لمزيد من المعلومات، يُرجى الاطّلاع على ViewModel
.
في onCreate()
أسفل مجموعة الرموز RecyclerView
، احصل على ViewModel
من ViewModelProvider
:
wordViewModel = ViewModelProvider(this).get(WordViewModel::class.java)
في onCreate()
أيضًا، أضِف مراقبًا للسمة allWords LiveData
من WordViewModel
.
.
يتم تشغيل طريقة onChanged()
(الطريقة التلقائية لـ Lambda) عند تغيُّر البيانات المرصودة ويكون النشاط في المقدّمة:
wordViewModel.allWords.observe(this, Observer { words ->
// Update the cached copy of the words in the adapter.
words?.let { adapter.setWords(it) }
})
نريد فتح NewWordActivity
عند النقر على زر الإجراء العائم، وعند العودة إلى MainActivity
، نريد إما إدراج الكلمة الجديدة في قاعدة البيانات أو عرض Toast
. لتحقيق ذلك، لنبدأ بتحديد رمز الطلب:
private val newWordActivityRequestCode = 1
في MainActivity
، أضِف الرمز onActivityResult()
الخاص بـ NewWordActivity
.
إذا عاد النشاط مع RESULT_OK
، أدرِج الكلمة التي تم إرجاعها في قاعدة البيانات عن طريق استدعاء طريقة insert()
الخاصة بـ WordViewModel
:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK) {
data?.getStringExtra(NewWordActivity.EXTRA_REPLY)?.let {
val word = Word(it)
wordViewModel.insert(word)
}
} else {
Toast.makeText(
applicationContext,
R.string.empty_not_saved,
Toast.LENGTH_LONG).show()
}
}
في MainActivity,
البداية NewWordActivity
عندما ينقر المستخدم على زر الإجراء العائم في MainActivity
onCreate
، ابحث عن زر الإجراء العائم وأضِف onClickListener
باستخدام الرمز التالي:
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
val intent = Intent(this@MainActivity, NewWordActivity::class.java)
startActivityForResult(intent, newWordActivityRequestCode)
}
يجب أن تبدو التعليمات البرمجية المكتملة على النحو التالي:
class MainActivity : AppCompatActivity() {
private const val newWordActivityRequestCode = 1
private lateinit var wordViewModel: WordViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
val adapter = WordListAdapter(this)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
wordViewModel = ViewModelProvider(this).get(WordViewModel::class.java)
wordViewModel.allWords.observe(this, Observer { words ->
// Update the cached copy of the words in the adapter.
words?.let { adapter.setWords(it) }
})
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
val intent = Intent(this@MainActivity, NewWordActivity::class.java)
startActivityForResult(intent, newWordActivityRequestCode)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK) {
data?.getStringExtra(NewWordActivity.EXTRA_REPLY)?.let {
val word = Word(it)
wordViewModel.insert(word)
}
} else {
Toast.makeText(
applicationContext,
R.string.empty_not_saved,
Toast.LENGTH_LONG).show()
}
}
}
شغِّل تطبيقك الآن. عند إضافة كلمة إلى قاعدة البيانات في NewWordActivity
، سيتم تعديل واجهة المستخدِم تلقائيًا.
بعد أن أصبح لديك تطبيق يعمل، لنلخّص ما أنشأته. في ما يلي بنية التطبيق مرة أخرى:
مكوّنات التطبيق هي:
MainActivity
: تعرض الكلمات في قائمة باستخدامRecyclerView
وWordListAdapter
. فيMainActivity
، هناكObserver
يراقب الكلمات LiveData من قاعدة البيانات ويتم إعلامه عند تغييرها.- تضيف
NewWordActivity:
كلمة جديدة إلى القائمة. -
WordViewModel
: يوفّر طرقًا للوصول إلى طبقة البيانات، ويعرض LiveData حتى تتمكّن MainActivity من إعداد علاقة المراقبة.* -
LiveData<List<Word>>
: يتيح إجراء التحديثات التلقائية في مكوّنات واجهة المستخدم. فيMainActivity
، هناكObserver
يراقب كلمات LiveData من قاعدة البيانات ويتم إعلامه عند تغييرها. - يدير حساب
Repository:
مصدر بيانات واحدًا أو أكثر. تعرضRepository
طرقًا لتتفاعل ViewModel مع مقدّم البيانات الأساسي. في هذا التطبيق، تكون الخلفية عبارة عن قاعدة بيانات Room. -
Room
: هو برنامج تضمين لقاعدة بيانات SQLite وينفّذها. يوفّر لك Room الكثير من العمل الذي كان عليك إنجازه بنفسك. - كائن الوصول إلى البيانات: يربط استدعاءات الطرق بطلبات البحث في قاعدة البيانات، وبالتالي عندما يستدعي المستودع طريقة مثل
getAlphabetizedWords()
، يمكن لـ Room تنفيذSELECT * from word_table ORDER BY word ASC
. -
Word
: هي فئة الكيان التي تحتوي على كلمة واحدة.
* تتفاعل Views
وActivities
(وFragments
) فقط مع البيانات من خلال ViewModel
. وبالتالي، لا يهم مصدر البيانات.
تدفّق البيانات لتحديثات واجهة المستخدم التلقائية (واجهة المستخدم التفاعلية)
يمكن إجراء التحديث التلقائي لأنّنا نستخدم LiveData. في MainActivity
، هناك Observer
يراقب كلمات LiveData من قاعدة البيانات ويتم إعلامه عند تغييرها. عند حدوث تغيير، يتم تنفيذ طريقة onChange()
الخاصة بالمراقب ويتم تعديل mWords
في WordListAdapter
.
يمكن ملاحظة البيانات لأنّها LiveData
. والقيمة التي يتم رصدها هي LiveData<List<Word>>
التي تعرضها السمة WordViewModel
allWords
.
يخفي WordViewModel
كل ما يتعلق الخلفية عن طبقة واجهة المستخدم. توفّر هذه السمة طرقًا للوصول إلى طبقة البيانات، وتعرض القيمة LiveData
حتى تتمكّن السمة MainActivity
من إعداد علاقة المراقبة. تتفاعل Views
وActivities
(وFragments
) فقط مع البيانات من خلال ViewModel
. وبالتالي، لا يهم مصدر البيانات.
في هذه الحالة، يكون مصدر البيانات Repository
. لا يحتاج ViewModel
إلى معرفة ما يتفاعل معه هذا المستودع. كل ما يحتاج إليه هو معرفة كيفية التفاعل مع Repository
، وذلك من خلال الطرق التي يعرضها Repository
.
يدير المستودع مصدر بيانات واحدًا أو أكثر. في تطبيق WordListSample
، تكون قاعدة البيانات الخلفية هي قاعدة بيانات Room. Room هي أداة تغليف لقاعدة بيانات SQLite وتنفّذها. يوفّر لك Room الكثير من العمل الذي كان عليك إنجازه بنفسك. على سبيل المثال، يتيح لك Room تنفيذ كل ما كنت تفعله باستخدام SQLiteOpenHelper
صف.
تُجري DAO عمليات ربط بين طلبات استدعاء الدوال واستعلامات قاعدة البيانات، وبالتالي عندما يستدعي المستودع دالة مثل getAllWords()
، يمكن لـ Room تنفيذ SELECT * from word_table ORDER BY word ASC
.
بما أنّ النتيجة التي يتم عرضها من طلب البحث هي LiveData
، في كل مرة تتغيّر فيها البيانات في Room، يتم تنفيذ طريقة onChanged()
في واجهة Observer
ويتم تعديل واجهة المستخدم.
[اختياري] تنزيل رمز الحلّ
إذا لم يسبق لك ذلك، يمكنك إلقاء نظرة على رمز الحلّ الخاص بدرس البرمجة. يمكنك الاطّلاع على مستودع github أو تنزيل الرمز البرمجي هنا:
فكّ ضغط ملف ZIP الذي تم تنزيله. سيؤدي ذلك إلى فك حزمة مجلد جذر باسم android-room-with-a-view-kotlin
يحتوي على التطبيق الكامل.