‫Android Kotlin Fundamentals 05.2: LiveData وLive Data observers

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

مقدمة

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

لاستخدام الفئة LiveData، عليك إعداد "مراقبين" (مثل الأنشطة أو الأجزاء) يراقبون التغييرات في بيانات التطبيق. LiveData مدرِك لدورة الحياة، لذا لا يحدّث سوى مراقبي مكونات التطبيق الذين يكونون في حالة نشطة من دورة الحياة.

ما يجب معرفته

  • كيفية إنشاء تطبيقات Android أساسية في Kotlin
  • كيفية التنقّل بين وجهات تطبيقك
  • مراحل نشاط التطبيق وأجزائه
  • كيفية استخدام عناصر ViewModel في تطبيقك
  • كيفية إنشاء عناصر ViewModel باستخدام واجهة ViewModelProvider.Factory

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

  • ما الذي يجعل عناصر LiveData مفيدة؟
  • كيفية إضافة LiveData إلى البيانات المخزّنة في ViewModel
  • كيفية استخدام MutableLiveData والحالات التي يُستخدَم فيها
  • كيفية إضافة طرق مراقبة لتتبُّع التغييرات في LiveData.
  • كيفية تغليف LiveData باستخدام سمة احتياطية
  • كيفية التواصل بين وحدة التحكّم في واجهة المستخدم وViewModel المقابل لها

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

  • استخدِم LiveData للكلمة والنتيجة في تطبيق GuessTheWord.
  • أضِف مراقبين يلاحظون متى تتغير الكلمة أو النتيجة.
  • تعديل طرق عرض النصوص التي تعرض القيم المتغيرة
  • استخدِم نمط LiveData المراقب لإضافة حدث انتهاء اللعبة.
  • تنفيذ الزر تشغيل مرة أخرى

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

ينظر اللاعب الأول إلى الكلمات في التطبيق ويمثّل كل كلمة بالتناوب، مع الحرص على عدم إظهار الكلمة للاعب الثاني. يحاول اللاعب الثاني تخمين الكلمة.

لبدء اللعبة، يفتح اللاعب الأول التطبيق على الجهاز ويظهر له كلمة، مثل "غيتار"، كما هو موضّح في لقطة الشاشة أدناه.

يؤدي اللاعب الأول الكلمة، مع الحرص على عدم قول الكلمة نفسها.

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

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

شاشة العنوان

شاشة اللعبة

شاشة النتيجة

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

  1. (اختياري) إذا كنت لا تستخدم الرمز من الدرس التطبيقي السابق حول الترميز، يمكنك تنزيل رمز البداية لهذا الدرس التطبيقي حول الترميز. فك ضغط الرمز البرمجي، وافتح المشروع في "استوديو Android".
  2. شغِّل التطبيق واللعبة.
  3. يُرجى العِلم أنّ الزر تخطّي يعرض الكلمة التالية ويخفض النتيجة بمقدار واحد، بينما يعرض الزر فهمت الكلمة التالية ويزيد النتيجة بمقدار واحد. يؤدي النقر على الزر إنهاء اللعبة إلى إنهاء اللعبة.

LiveData هي فئة حاوية بيانات قابلة للمراقبة ومدرِكة لدورة الحياة. على سبيل المثال، يمكنك تضمين LiveData حول النتيجة الحالية في تطبيق GuessTheWord. في هذا الدرس العملي، ستتعرّف على العديد من خصائص LiveData:

  • يمكن مراقبة LiveData، ما يعني أنّه يتم إرسال إشعار إلى المراقب عند تغيير البيانات التي يحتوي عليها عنصر LiveData.
  • LiveData تحتفظ بالبيانات، وLiveData هي أداة تغليف يمكن استخدامها مع أي بيانات
  • LiveData هي مكوّن يراعي مراحل النشاط، ما يعني أنّه لا يرسل إشعارات إلا إلى المراقبين الذين يكونون في حالة نشطة من مراحل النشاط، مثل STARTED أو RESUMED.

في هذه المهمة، ستتعرّف على كيفية تضمين أي نوع بيانات في عناصر LiveData عن طريق تحويل بيانات النتيجة الحالية والكلمة الحالية في GameViewModel إلى LiveData. في مهمة لاحقة، ستضيف مراقبًا إلى عناصر LiveData هذه وتتعرّف على كيفية مراقبة LiveData.

الخطوة 1: تغيير النتيجة والكلمة لاستخدام LiveData

  1. ضمن حزمة screens/game، افتح ملف GameViewModel.
  2. غيِّر نوع المتغيّرين score وword إلى MutableLiveData.

    MutableLiveData هو LiveData يمكن تغيير قيمته. ‫MutableLiveData هي فئة عامة، لذا عليك تحديد نوع البيانات التي تحتوي عليها.
// The current word
val word = MutableLiveData<String>()
// The current score
val score = MutableLiveData<Int>()
  1. في GameViewModel، داخل كتلة init، ابدأ score وword. لتغيير قيمة متغيّر LiveData، يمكنك استخدام طريقة setValue() على المتغيّر. في Kotlin، يمكنك استدعاء setValue() باستخدام السمة value.
init {

   word.value = ""
   score.value = 0
  ...
}

الخطوة 2: تعديل مرجع عنصر LiveData

أصبح نوع المتغيّرين score وword الآن LiveData. في هذه الخطوة، يمكنك تغيير مراجع هذه المتغيرات باستخدام السمة value.

  1. في GameViewModel، في طريقة onSkip()، غيِّر score إلى score.value. لاحظ الخطأ بشأن احتمال أن يكون score هو null. عليك إصلاح هذا الخطأ أولاً.
  2. لحلّ الخطأ، أضِف علامة null إلى score.value في onSkip(). بعد ذلك، استدعِ الدالة minus() على score، والتي تجري عملية الطرح مع null-safety.
fun onSkip() {
   if (!wordList.isEmpty()) {
       score.value = (score.value)?.minus(1)
   }
   nextWord()
}
  1. عدِّل طريقة onCorrect() بالطريقة نفسها: أضِف عملية التحقّق null إلى المتغيّر score واستخدِم الدالة plus().
fun onCorrect() {
   if (!wordList.isEmpty()) {
       score.value = (score.value)?.plus(1)
   }
   nextWord()
}
  1. في GameViewModel، داخل طريقة nextWord()، غيِّر مرجع word إلى word.value.
private fun nextWord() {
   if (!wordList.isEmpty()) {
       //Select and remove a word from the list
       word.value = wordList.removeAt(0)
   }
}
  1. في GameFragment، داخل طريقة updateWordText()، غيِّر المرجع إلى viewModel.word إلى viewModel.word.value.
/** Methods for updating the UI **/
private fun updateWordText() {
   binding.wordText.text = viewModel.word.value
}
  1. في GameFragment، داخل طريقة updateScoreText()، غيِّر المرجع إلى viewModel.score إلى viewModel.score.value.
private fun updateScoreText() {
   binding.scoreText.text = viewModel.score.value.toString()
}
  1. في GameFragment، داخل طريقة gameFinished()، غيِّر المرجع إلى viewModel.score إلى viewModel.score.value. أضِف null-فحص السلامة المطلوب.
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score.value?:0
   NavHostFragment.findNavController(this).navigate(action)
}
  1. تأكَّد من عدم وجود أي أخطاء في الرمز. جمِّع تطبيقك وشغِّله، ويجب أن تكون وظائف التطبيق هي نفسها كما كانت من قبل.

ترتبط هذه المهمة ارتباطًا وثيقًا بالمهمة السابقة التي تم فيها تحويل بيانات الدرجات والكلمات إلى عناصر LiveData. في هذه المهمة، ستربط عناصر Observer بعناصر LiveData هذه.

  1. في GameFragment, داخل طريقة onCreateView()، أرفِق عنصر Observer بالعنصر LiveData للنتيجة الحالية، viewModel.score. استخدِم طريقة observe()، وضَع الرمز بعد تهيئة viewModel. استخدِم تعبير lambda لتبسيط الرمز. (تعبير lambda هو دالة مجهولة الاسم لم يتم الإعلان عنها، ولكن يتم تمريرها على الفور كتعبير).
viewModel.score.observe(this, Observer { newScore ->
})

حلّ المرجع إلى Observer لإجراء ذلك، انقر على Observer، ثم اضغط على Alt+Enter (Option+Enter على جهاز Mac)، واستورِد androidx.lifecycle.Observer.

  1. يتلقّى المراقب الذي أنشأته للتو حدثًا عندما تتغيّر البيانات التي يحتوي عليها العنصر LiveData الذي تتم مراقبته. داخل المراقب، عدِّل النتيجة TextView بالنتيجة الجديدة.
/** Setting up LiveData observation relationship **/
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. أرفِق عنصر Observer بعنصر الكلمة الحالية LiveData. يمكنك إجراء ذلك بالطريقة نفسها التي أرفقت بها العنصر Observer بالنتيجة الحالية.
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
   binding.wordText.text = newWord
})

عندما تتغيّر قيمة score أو word، يتم تعديل score أو word المعروضَين على الشاشة تلقائيًا.

  1. في GameFragment، احذف الطريقتَين updateWordText() وupdateScoreText() وجميع الإشارات إليهما. لم تعُد بحاجة إليها، لأنّ طرق المراقبة LiveData تعدّل طرق عرض النصوص.
  2. شغِّل تطبيقك. من المفترض أن يعمل تطبيق الألعاب كما كان من قبل، ولكنّه يستخدم الآن أدوات مراقبة LiveData وLiveData.

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

في الرمز الحالي، يمكن لأي فئة خارجية تعديل المتغيّرَين score وword باستخدام السمة value، مثلاً باستخدام viewModel.score.value. قد لا يكون ذلك مهمًا في التطبيق الذي تعمل على تطويره في هذا الدرس البرمجي، ولكن في تطبيق متاح للجميع، من المهم أن يكون لديك تحكّم في البيانات الموجودة في عناصر ViewModel.

يجب أن يكون ViewModel هو الجهة الوحيدة التي تعدّل البيانات في تطبيقك، ولكن يجب أن تتمكّن عناصر التحكّم في واجهة المستخدم من قراءة البيانات، لذا لا يمكن أن تكون حقول البيانات خاصّة تمامًا. لتغليف بيانات تطبيقك، يمكنك استخدام كلّ من العنصرَين MutableLiveData وLiveData.

MutableLiveData مقارنةً بـ LiveData:

  • يمكن تغيير البيانات في كائن MutableLiveData، كما يشير الاسم. داخل ViewModel، يجب أن تكون البيانات قابلة للتعديل، لذا يتم استخدام MutableLiveData.
  • يمكن قراءة البيانات في عنصر LiveData، ولكن لا يمكن تغييرها. من خارج ViewModel، يجب أن تكون البيانات قابلة للقراءة، ولكن ليس قابلة للتعديل، لذا يجب عرض البيانات على أنّها LiveData.

لتنفيذ هذه الاستراتيجية، يمكنك استخدام سمة احتياطية في Kotlin. تتيح لك السمة الاحتياطية عرض قيمة من دالة جلب غير الكائن المحدد. في هذه المهمة، ستنفّذ خاصية احتياطية للكائنَين score وword في تطبيق GuessTheWord.

إضافة سمة داعمة إلى النتيجة والكلمة

  1. في GameViewModel، اجعل كائن score الحالي private.
  2. للتوافق مع اصطلاح التسمية المستخدَم في الخصائص الداعمة، غيِّر score إلى _score. أصبحت السمة _score الآن هي النسخة القابلة للتغيير من نتيجة المباراة، ويجب استخدامها داخليًا.
  3. أنشئ نسخة علنية من النوع LiveData باسم score.
// The current score
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
  1. تظهر لك رسالة خطأ في الإعداد. يحدث هذا الخطأ لأنّ score داخل GameFragment هو مرجع LiveData، ولم يعُد بإمكان score الوصول إلى الدالة الضابطة. لمزيد من المعلومات حول دوال الجلب والتعديل في Kotlin، يمكنك الاطّلاع على دوال الجلب والتعديل.

    لحلّ الخطأ، عليك إلغاء طريقة get() للكائن score في GameViewModel وعرض الخاصية الاحتياطية _score.
val score: LiveData<Int>
   get() = _score
  1. في GameViewModel، غيِّر مراجع score إلى نسخته الداخلية القابلة للتغيير، _score.
init {
   ...
   _score.value = 0
   ...
}

...
fun onSkip() {
   if (!wordList.isEmpty()) {
       _score.value = (score.value)?.minus(1)
   }
  ...
}

fun onCorrect() {
   if (!wordList.isEmpty()) {
       _score.value = (score.value)?.plus(1)
   }
   ...
}
  1. أعِد تسمية العنصر word إلى _word وأضِف له خاصية احتياطية، كما فعلت مع العنصر score.
// The current word
private val _word = MutableLiveData<String>()
val word: LiveData<String>
   get() = _word
...
init {
   _word.value = ""
   ...
}
...
private fun nextWord() {
   if (!wordList.isEmpty()) {
       //Select and remove a word from the list
       _word.value = wordList.removeAt(0)
   }
}

أحسنت، لقد غلّفت العنصرَين LiveData وword.score

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

لتنفيذ هذه الوظيفة، تحتاج إلى حدث يتم تشغيله وإرساله إلى الجزء من ViewModel عندما يتم عرض جميع الكلمات. لإجراء ذلك، يمكنك استخدام نمط المراقب LiveData لإنشاء نموذج لحدث انتهاء اللعبة.

نمط المراقب

نمط المراقب هو نمط تصميم برامج. تحدّد هذه السمة التواصل بين الكائنات: كائن قابل للمراقبة (وهو "موضوع" المراقبة) ومراقبون. الكائن القابل للملاحظة هو كائن يُعلم المراقبين بالتغييرات في حالته.

في حالة LiveData في هذا التطبيق، يكون العنصر القابل للملاحظة (الموضوع) هو العنصر LiveData، وتكون العناصر المراقِبة هي الطرق في وحدات التحكّم في واجهة المستخدم، مثل الأجزاء. يحدث تغيير في الحالة كلما تغيّرت البيانات المضمّنة في LiveData. تُعدّ فئات LiveData ضرورية للتواصل من ViewModel إلى الجزء.

الخطوة 1: استخدام LiveData لرصد حدث انتهاء اللعبة

في هذه المهمة، ستستخدم LiveData نمط المراقب لنمذجة حدث انتهاء اللعبة.

  1. في GameViewModel، أنشئ عنصر Boolean MutableLiveData باسم _eventGameFinish. سيحتوي هذا العنصر على حدث انتهاء اللعبة.
  2. بعد تهيئة الكائن _eventGameFinish، أنشئ وتهيئ خاصية احتياطية باسم eventGameFinish.
// Event which triggers the end of the game
private val _eventGameFinish = MutableLiveData<Boolean>()
val eventGameFinish: LiveData<Boolean>
   get() = _eventGameFinish
  1. في GameViewModel، أضِف طريقة onGameFinish(). في الطريقة، اضبط حدث انتهاء اللعبة، eventGameFinish، على true.
/** Method for the game completed event **/
fun onGameFinish() {
   _eventGameFinish.value = true
}
  1. في GameViewModel، داخل طريقة nextWord()، أنهِ اللعبة إذا كانت قائمة الكلمات فارغة.
private fun nextWord() {
   if (wordList.isEmpty()) {
       onGameFinish()
   } else {
       //Select and remove a _word from the list
       _word.value = wordList.removeAt(0)
   }
}
  1. في GameFragment، داخل onCreateView()، بعد تهيئة viewModel، أضِف مراقبًا إلى eventGameFinish. استخدِم طريقة observe(). داخل دالة lambda، استدعِ طريقة gameFinished().
// Observer for the Game finished event
viewModel.eventGameFinish.observe(this, Observer<Boolean> { hasFinished ->
   if (hasFinished) gameFinished()
})
  1. شغِّل تطبيقك والعب اللعبة واقرأ جميع الكلمات. ينتقل التطبيق إلى شاشة النتيجة تلقائيًا، بدلاً من البقاء في جزء اللعبة إلى أن تنقر على إنهاء اللعبة.

    بعد أن تصبح قائمة الكلمات فارغة، يتم ضبط eventGameFinish، ويتم استدعاء طريقة المراقبة المرتبطة في جزء اللعبة، وينتقل التطبيق إلى جزء الشاشة.
  2. تسبّب الرمز الذي أضفته في حدوث مشكلة في مراحل النشاط. لفهم المشكلة، في فئة GameFragment، علِّق على رمز التنقّل في طريقة gameFinished(). احرص على الاحتفاظ بالرسالة Toast في الطريقة.
private fun gameFinished() {
       Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
//        val action = GameFragmentDirections.actionGameToScore()
//        action.score = viewModel.score.value?:0
//        NavHostFragment.findNavController(this).navigate(action)
   }
  1. شغِّل تطبيقك والعب اللعبة واقرأ جميع الكلمات. تظهر رسالة تنبيه "انتهت اللعبة للتو" لفترة وجيزة في أسفل شاشة اللعبة، وهذا هو السلوك المتوقّع.

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

الخطوة 2: إعادة ضبط حدث انتهاء اللعبة

عادةً، لا يرسل LiveData تحديثات إلى المراقبين إلا عند تغيُّر البيانات. ويستثنى من هذا السلوك أنّ المراقبين يتلقّون أيضًا إشعارات عندما تتغيّر حالة المراقب من غير نشط إلى نشط.

لهذا السبب يتم تشغيل إشعار "اكتملت اللعبة" بشكل متكرّر في تطبيقك. وعند إعادة إنشاء جزء اللعبة بعد تدوير الشاشة، ينتقل من حالة غير نشطة إلى حالة نشطة. يتم إعادة ربط المراقب في الجزء بـ ViewModel الحالي ويتلقّى البيانات الحالية. يتم إعادة تشغيل الطريقة gameFinished()، ويظهر الإشعار.

في هذه المهمة، عليك حلّ هذه المشكلة وعرض الإشعار مرة واحدة فقط، وذلك عن طريق إعادة ضبط العلامة eventGameFinish في GameViewModel.

  1. في GameViewModel، أضِف طريقة onGameFinishComplete() لإعادة ضبط حدث انتهاء اللعبة، _eventGameFinish.
/** Method for the game completed event **/

fun onGameFinishComplete() {
   _eventGameFinish.value = false
}
  1. في GameFragment، في نهاية gameFinished()، اتّصِل بـ onGameFinishComplete() على العنصر viewModel. (اترك رمز التنقّل في gameFinished() معلّقًا في الوقت الحالي).
private fun gameFinished() {
   ...
   viewModel.onGameFinishComplete()
}
  1. شغِّل التطبيق واللعبة. انتقِل إلى جميع الكلمات، ثم غيِّر اتجاه شاشة الجهاز. يتم عرض الإشعار مرة واحدة فقط.
  2. في GameFragment، داخل طريقة gameFinished()، أزِل التعليق من رمز التنقّل.

    لإزالة التعليق في "استوديو Android"، حدِّد الأسطر التي تم التعليق عليها واضغط على Control+/ (Command+/ على جهاز Mac).
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score.value?:0
   findNavController(this).navigate(action)
   viewModel.onGameFinishComplete()
}

إذا طلب منك "استوديو Android" ذلك، استورِد androidx.navigation.fragment.NavHostFragment.findNavController.

  1. شغِّل التطبيق واللعبة. تأكَّد من أنّ التطبيق ينتقل تلقائيًا إلى شاشة النتيجة النهائية بعد المرور على جميع الكلمات.

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

في هذه المهمة، ستغيّر النتيجة إلى كائن LiveData في ScoreViewModel وتربط مراقبًا به. هذه المهمة مشابهة لما فعلته عند إضافة LiveData إلى GameViewModel.

تُجري هذه التغييرات على ScoreViewModel لضمان اكتمالها، حتى تستخدم جميع البيانات في تطبيقك LiveData.

  1. في ScoreViewModel، غيِّر نوع المتغيّر score إلى MutableLiveData. أعِد تسميته حسب الاصطلاح إلى _score وأضِف خاصية احتياطية.
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
   get() = _score
  1. في ScoreViewModel، داخل كتلة init، ابدأ _score. يمكنك إزالة سجلّ الدخول أو تركه في المربّع init كما تريد.
init {
   _score.value = finalScore
}
  1. في ScoreFragment، داخل onCreateView()، بعد تهيئة viewModel، أضِف مراقبًا لكائن النتيجة LiveData. داخل تعبير lambda، اضبط قيمة النتيجة على طريقة عرض نص النتيجة. أزِل الرمز الذي يعيّن عرض النص مباشرةً بقيمة النتيجة من ViewModel.

الرمز المطلوب إضافته:

// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})

الرمز المطلوب إزالته:

binding.scoreText.text = viewModel.score.toString()

عندما يطلب منك Android Studio ذلك، استورِد androidx.lifecycle.Observer.

  1. شغِّل تطبيقك والعب اللعبة. من المفترض أن يعمل التطبيق كما كان من قبل، ولكنّه يستخدم الآن LiveData ومراقبًا لتعديل النتيجة.

في هذه المهمة، ستضيف زر اللعب مرة أخرى إلى شاشة النتيجة وتنفّذ أداة معالجة النقرات باستخدام حدث LiveData. يؤدي الزر إلى تشغيل حدث للانتقال من شاشة النتيجة إلى شاشة اللعبة.

يتضمّن الرمز البرمجي الأولي للتطبيق الزر تشغيل مرة أخرى، ولكنّه مخفي.

  1. في res/layout/score_fragment.xml، بالنسبة إلى الزر play_again_button، غيِّر قيمة السمة visibility إلى visible.
<Button
   android:id="@+id/play_again_button"
...
   android:visibility="visible"
 />
  1. في ScoreViewModel، أضِف عنصر LiveData لتضمين Boolean باسم _eventPlayAgain. يُستخدَم هذا العنصر لحفظ الحدث LiveData للتنقّل من شاشة النتيجة إلى شاشة اللعبة.
private val _eventPlayAgain = MutableLiveData<Boolean>()
val eventPlayAgain: LiveData<Boolean>
   get() = _eventPlayAgain
  1. في ScoreViewModel، حدِّد طرقًا لضبط الحدث وإعادة ضبطه، _eventPlayAgain.
fun onPlayAgain() {
   _eventPlayAgain.value = true
}
fun onPlayAgainComplete() {
   _eventPlayAgain.value = false
}
  1. في ScoreFragment، أضِف مراقبًا لـ eventPlayAgain. ضَع الرمز في نهاية onCreateView()، قبل عبارة return. داخل تعبير lambda، ارجع إلى شاشة اللعبة وأعِد ضبط eventPlayAgain.
// Navigates back to game when button is pressed
viewModel.eventPlayAgain.observe(this, Observer { playAgain ->
   if (playAgain) {
      findNavController().navigate(ScoreFragmentDirections.actionRestart())
       viewModel.onPlayAgainComplete()
   }
})

استورِد androidx.navigation.fragment.findNavController عندما يطلب منك Android Studio ذلك.

  1. في ScoreFragment، داخل onCreateView()، أضِف أداة معالجة نقرات إلى الزر PlayAgain واستدعِ الدالة viewModel.onPlayAgain().
binding.playAgainButton.setOnClickListener {  viewModel.onPlayAgain()  }
  1. شغِّل تطبيقك والعب اللعبة. عند انتهاء المباراة، تعرض شاشة النتائج النتيجة النهائية والزر لعب مجددًا. انقر على الزر PlayAgain، وسينتقِل التطبيق إلى شاشة اللعبة لتتمكّن من لعبها مرة أخرى.

عمل رائع لقد غيّرت بنية تطبيقك لاستخدام عناصر LiveData في ViewModel، وربطت أدوات مراقبة بعناصر LiveData. LiveData تُرسِل إشعارات إلى الكائنات المراقِبة عندما تتغيّر القيمة التي تحتفظ بها LiveData.

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

LiveData

  • LiveData هي فئة حاوية بيانات قابلة للملاحظة ومدرِكة لدورة الحياة، وهي إحدى مكوّنات بنية Android.
  • يمكنك استخدام LiveData لتفعيل التحديث التلقائي لواجهة المستخدم عند تعديل البيانات.
  • يمكن مراقبة LiveData، ما يعني أنّه يمكن إرسال إشعار إلى مراقب، مثل نشاط أو جزء، عند تغيُّر البيانات التي يحتوي عليها العنصر LiveData.
  • LiveData تحتفظ بالبيانات، وهي عبارة عن غلاف يمكن استخدامه مع أي بيانات.
  • LiveData هي مكوّن يراعي مراحل النشاط، ما يعني أنّه لا يرسل إشعارات إلا إلى المراقبين الذين يكونون في حالة نشطة من مراحل النشاط، مثل STARTED أو RESUMED.

لإضافة LiveData

  • غيِّر نوع متغيّرات البيانات في ViewModel إلى LiveData أو MutableLiveData.

MutableLiveData هو كائن LiveData يمكن تغيير قيمته. ‫MutableLiveData هي فئة عامة، لذا عليك تحديد نوع البيانات التي تحتوي عليها.

  • لتغيير قيمة البيانات التي يحتفظ بها LiveData، استخدِم طريقة setValue() على المتغيّر LiveData.

لتغليف LiveData، اتّبِع الخطوات التالية:

  • يجب أن يكون LiveData داخل ViewModel قابلاً للتعديل. خارج ViewModel، يجب أن يكون LiveData قابلاً للقراءة. يمكن تنفيذ ذلك باستخدام الخاصية الاحتياطية في Kotlin.
  • يتيح لك حقل التخزين الاحتياطي في Kotlin عرض قيمة من دالة الجلب تختلف عن الكائن المحدد.
  • لتضمين LiveData، استخدِم private MutableLiveData داخل ViewModel وأدرِج سمة LiveData خلفية خارج ViewModel.

LiveData قابلة للتتبّع

  • يتبع LiveData نمط المراقب. إنّ "العنصر القابل للمراقبة" هو الكائن LiveData، والمراقبون هم الطرق في وحدات التحكّم في واجهة المستخدم، مثل الأجزاء. عندما تتغيّر البيانات المضمّنة في LiveData، يتم إرسال إشعار إلى طرق المراقبة في أدوات التحكّم في واجهة المستخدم.
  • لجعل LiveData قابلاً للمراقبة، أضِف عنصر مراقبة إلى مرجع LiveData في عناصر المراقبة (مثل الأنشطة واللقطات) باستخدام الطريقة observe().
  • يمكن استخدام نمط المراقبة LiveData هذا للتواصل من ViewModel إلى أدوات التحكّم في واجهة المستخدم.

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

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

غير ذلك:

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

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

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

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

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

السؤال 1

كيف يمكنك تغليف LiveData المخزّنة في ViewModel حتى تتمكّن الكائنات الخارجية من قراءة البيانات بدون إمكانية تعديلها؟

  • داخل العنصر ViewModel، غيِّر نوع البيانات إلى private LiveData. استخدِم خاصية داعمة لعرض بيانات للقراءة فقط من النوع MutableLiveData.
  • داخل العنصر ViewModel، غيِّر نوع البيانات إلى private MutableLiveData. استخدِم خاصية داعمة لعرض بيانات للقراءة فقط من النوع LiveData.
  • داخل وحدة التحكّم في واجهة المستخدم، غيِّر نوع البيانات إلى private MutableLiveData. استخدِم خاصية داعمة لعرض بيانات للقراءة فقط من النوع LiveData.
  • داخل العنصر ViewModel، غيِّر نوع البيانات إلى LiveData. استخدِم خاصية داعمة لعرض بيانات للقراءة فقط من النوع LiveData.

السؤال 2

تعدِّل السمة LiveData وحدة التحكّم في واجهة المستخدم (مثل جزء) إذا كانت وحدة التحكّم في واجهة المستخدم في أيّ من الحالات التالية؟

  • تم الاستئناف
  • في الخلفية
  • متوقف مؤقتًا
  • متوقفة

السؤال 3

في نمط المراقب LiveData، ما هو العنصر القابل للمراقبة (ما تتم مراقبته)؟

  • طريقة المراقبة
  • البيانات في عنصر LiveData
  • وحدة التحكّم في واجهة المستخدم
  • العنصر ViewModel

ابدأ الدرس التالي: 5.3: ربط البيانات باستخدام ViewModel وLiveData

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