يشكّل هذا الدرس التطبيقي جزءًا من الدورة التدريبية لأساسيات Android Kotlin. ستحصل على أقصى قيمة ممكنة من هذه الدورة التدريبية إذا كنت تستخدم الدروس التطبيقية حول الترميز بشكل متسلسل. يتم إدراج جميع الدروس التطبيقية حول ترميز الدورات التدريبية في الصفحة المقصودة لدروس الترميز Android Kotlin Fundamentals.
شاشة العنوان | شاشة اللعبة | شاشة النتائج |
مقدمة
في هذا الدرس التطبيقي حول الترميز، ستتعرّف على أحد "مكوّنات Android للبنية" ViewModel
:
- يمكنك استخدام الصف
ViewModel
في تخزين وإدارة البيانات ذات الصلة بواجهة المستخدم بطريقة تعيق مراحل النشاط. تسمح الفئةViewModel
بالبيانات بالحفاظ على بقاء التغييرات في إعداد الجهاز، مثل تدوير الشاشة والتغييرات التي تطرأ على مدى توفّر لوحة المفاتيح. - يمكنك استخدام الفئة
ViewModelFactory
لإنشاء مثيل من الكائنViewModel
الذي يستمر في تغيير الإعدادات بسبب عرض تغييرات عليه.
ما يجب معرفته
- كيفية إنشاء تطبيقات Android الأساسية في Kotlin.
- كيفية استخدام الرسم البياني للتنقل لتنفيذ التنقل في تطبيقك
- كيفية إضافة رمز للتنقّل بين وجهات تطبيقك وتمرير البيانات بين وجهات التنقّل
- آلية عمل النشاط ومراحل النشاط المجزأة.
- كيفية إضافة معلومات التسجيل إلى أحد التطبيقات وقراءة السجلّات باستخدام Logcat في "استوديو Android".
ما ستتعرَّف عليه
- كيفية استخدام بنية تطبيق Android الموصى بها.
- طريقة استخدام صفوف
Lifecycle
وViewModel
وViewModelFactory
في تطبيقك. - كيفية الاحتفاظ ببيانات واجهة المستخدم من خلال تغييرات ضبط الجهاز.
- نمط تصميم طريقة المصنع وكيفية استخدامه.
- كيفية إنشاء عنصر
ViewModel
باستخدام الواجهةViewModelProvider.Factory
الإجراءات التي ستنفذّها
- أضِف
ViewModel
إلى التطبيق لحفظ بيانات التطبيق حتى تظل البيانات نشطة. - استخدِم
ViewModelFactory
ونمط التصميم على الإعدادات الأصلية لإنشاء عنصرViewModel
باستخدام معلّمات دالة الإنشاء.
في الدرس التطبيقي حول الترميز، يمكنك تطوير تطبيق GuessTheWord الذي يبدأ برمز التفعيل. GuessTheWord هي لعبة من فئة charades ثنائية اللاعبين، حيث يتعاون اللاعبون لتحقيق أعلى نتيجة ممكنة.
ينظر اللاعب الأول إلى الكلمات في التطبيق ويدور كل منها بدوره، مع التأكد من عدم عرض الكلمة على اللاعب الثاني. يحاول اللاعب الثاني تخمين الكلمة.
لتشغيل اللعبة، يفتح المشغِّل الأول التطبيق على الجهاز ويرى كلمة، على سبيل المثال "غيتار&"& كما يظهر في لقطة الشاشة أدناه.
يمارس اللاعب الأول الكلمة، مع الحرص على عدم قول الكلمة نفسها في الواقع.
- عندما يخمّن اللاعب الثاني الكلمة بشكلٍ صحيح، يضغط اللاعب الأول على الزر حسنًا، ما يؤدي إلى زيادة العدد بمقدار كلمة واحدة وعرض الكلمة التالية.
- إذا لم يتمكّن المشغّل الثاني من تخمين الكلمة، يضغط اللاعب الأول على الزر التخطّي، ما يؤدي إلى خفض العدد بمقدار كلمة واحدة والانتقال إلى الكلمة التالية.
- لإنهاء اللعبة، اضغط على زر إنهاء اللعبة. (هذه الوظيفة ليست في رمز بدء الدرس التطبيقي للترميز الأول في السلسلة).
في هذه المَهمّة، يمكنك تنزيل تطبيق المبتدئين وتشغيله وفحص الرمز.
الخطوة 1: تنفيذ الخطوات الأولى
- نزِّل رمز GuessThe Word Starter وافتح المشروع في "استوديو Android".
- شغِّل التطبيق على جهاز يعمل بنظام التشغيل Android أو محاكي.
- انقر على الأزرار. لاحظ أن الزر تخط يعرض الكلمة التالية ويقلل النتيجة بمقدار كلمة واحدة، ويعرض الزر حسنًا الكلمة التالية ويزيد النتيجة بمقدار كلمة رئيسية واحدة. لم يتم تنفيذ زر إنهاء اللعبة، لذا لن يحدث أي شيء عند النقر عليها.
الخطوة 2: إجراء جولة تفصيلية حول الرمز
- في Android Studio، استكشِف الرمز للاطّلاع على آلية عمل التطبيق.
- احرص على الاطّلاع على الملفات الموضّحة أدناه، وهي مهمة بشكل خاص.
MainActivity.kt
يحتوي هذا الملف على رمز تلقائي تم إنشاؤه بواسطة النموذج فقط.
res/Layout/main_activity.xml
يحتوي هذا الملف على التنسيق الرئيسي للتطبيق. يستضيف NavHostFragment
الأجزاء الأخرى أثناء تنقّل المستخدم في التطبيق.
أجزاء واجهة المستخدم
يتألف رمز التفعيل من ثلاثة أجزاء في ثلاث حِزم مختلفة ضمن حزمة com.example.android.guesstheword.screens
:
title/TitleFragment
لشاشة العنوانgame/GameFragment
على شاشة اللعبةscore/ScoreFragment
لشاشة النتائج
screen/title/TitleFragment.kt
جزء العنوان هو أول شاشة يتم عرضها عند تشغيل التطبيق. تم ضبط معالج النقر على الزر تشغيل للانتقال إلى شاشة اللعبة.
شاشات/لعبة/GameFragment.kt
هذا هو الجزء الرئيسي، الذي يحدث فيه معظم محتوى اللعبة:
- يتم تحديد المتغيرات للكلمة الحالية والنتيجة الحالية.
wordList
المُحدَّدة داخل طريقةresetList()
هي نموذج لقائمة الكلمات التي سيتم استخدامها في اللعبة.- الطريقة
onSkip()
هي معالج النقر على الزر تخطّي. ويؤدي ذلك إلى خفض النتيجة بمقدار 1، ثم عرض الكلمة التالية باستخدام طريقةnextWord()
. - الطريقة
onCorrect()
هي معالج النقر على الزر حسنًا. يتم تطبيق هذه الطريقة بالطريقة نفسها التي تتّبعها طريقةonSkip()
. الفرق الوحيد هو أن هذه الطريقة تضيف 1 إلى النتيجة بدلاً من طرحها.
screen/score/ScoreFragment.kt
ScoreFragment
هي الشاشة النهائية في اللعبة، وتعرض النتيجة النهائية للاعب. في هذا الدرس التطبيقي حول الترميز، يمكنك إضافة عملية التنفيذ لعرض هذه الشاشة وإظهار النتيجة النهائية.
res/Navigation/main_Navigation.xml.
يوضّح الرسم البياني للتنقل كيفية ربط الأجزاء من خلال التنقّل:
- ويمكن أن ينتقل المستخدم إلى جزء اللعبة من جزء العنوان.
- ومن جزء اللعبة، يمكن للمستخدم الانتقال إلى جزء النتيجة.
- ومن جزء النتيجة، يمكن للمستخدم الانتقال مرة أخرى إلى جزء اللعبة.
في هذه المهمة، ستجد مشاكل في تطبيق GuessTheWord مبتدئ.
- شغِّل رمز البدء والعب بضع كلمات، ثم انقر على تخطي أو حسنًا بعد كل كلمة.
- تعرض شاشة اللعبة الآن الكلمة والنتيجة الحالية. يمكنك تغيير اتجاه الشاشة عن طريق تدوير الجهاز أو المحاكي. يُرجى العلم بفقدان النتيجة الحالية.
- شغّل اللعبة ببضع كلمات أخرى. عندما يتم عرض شاشة اللعبة مع بعض النتائج، أغلِق التطبيق وأعِد فتحه. لاحظ أن اللعبة تتم إعادة تشغيلها من البداية، وذلك لأن حالة التطبيق غير محفوظة.
- يمكنك تشغيل اللعبة خلال بضع كلمات، ثم النقر على الزر إنهاء اللعبة. وتجدر الإشارة إلى عدم حدوث أي تغيير.
مشاكل في التطبيق:
- لا يحفظ تطبيق إجراء التفعيل حالة التطبيق واستعادتها أثناء تغيير الإعدادات، مثل عندما يتغير اتجاه الجهاز أو عند إيقاف التطبيق وإعادة تشغيله.
يمكنك حلّ هذه المشكلة باستخدام استدعاءonSaveInstanceState()
. ومع ذلك، فإن استخدام الطريقةonSaveInstanceState()
يتطلب منك كتابة رمز إضافي لحفظ الحالة في حزمة، وتنفيذ المنطق لاسترداد تلك الحالة. ويكون مقدار البيانات التي يمكن تخزينها بسيطًا أيضًا. - لا تنتقل شاشة اللعبة إلى شاشة النتائج عندما ينقر المستخدم على زر إنهاء اللعبة.
يمكنك حل هذه المشاكل باستخدام مكوّنات بنية التطبيق التي تعرف عليها في هذا الدرس التطبيقي حول الترميز.
بنية التطبيق
بنية التطبيق هي طريقة لتصميم تطبيقاتك وصفوفها والعلاقات بينها، بحيث يتم تنظيم الرمز وتحقيق أداء جيد في سيناريوهات محددة ويسهل العمل معها. في هذه المجموعة من الدروس التطبيقية حول الترميز، تتّبع التحسينات التي تجريها على تطبيق GuessTheWord إرشادات بنية تطبيق Android، وتستخدم مكوّنات Android الأساسية. تتشابه بنية تطبيق Android مع النمط المعماري MVVM (نموذج عرض النموذج).
يتبع تطبيق GuessTheWord مبدأ تصميم فصل المخاوف وينقسم إلى فئات، وتتعامل كل فئة مع المخاوف المنفصلة. في هذا الدرس التطبيقي حول الترميز الأول للدرس، تتضمن الصفوف التي تعمل معها وحدة تحكُّم في واجهة المستخدم وViewModel
وViewModelFactory
.
وحدة تحكُّم واجهة المستخدم
وحدة تحكُّم واجهة المستخدم هي فئة مستندة إلى واجهة المستخدم، مثل Activity
أو Fragment
. يجب أن تتضمن وحدة التحكم في واجهة المستخدم منطقًا فقط يتعامل مع تفاعلات واجهة المستخدم ونظام التشغيل، مثل عرض الملفات الشخصية والتقاط بيانات المستخدمين. لا تضع منطق اتّخاذ القرار، مثل المنطق الذي يحدّد النص المطلوب عرضه، في وحدة التحكّم في واجهة المستخدم.
في رمز GuessTheWord للمبتدئين، تكون وحدات التحكّم في واجهة المستخدم هي الأجزاء الثلاثة: GameFragment
وScoreFragment,
وTitleFragment
. وفقًا لمبدأ التصميم والفصل عن المخاوف والمشاكل، فإن GameFragment
مسؤول فقط عن رسم عناصر اللعبة والشاشة عندما ينقر المستخدم على الأزرار فقط. عندما ينقر المستخدم على زر، يتم تمرير هذه المعلومات إلى GameViewModel
.
نموذج العرض
تحتفظ ViewModel
بالبيانات لعرضها في جزء أو نشاط مرتبط بـ ViewModel
. يمكن لمتصفّح ViewModel
إجراء عمليات حسابية بسيطة وإحالات ناجحة للبيانات بهدف تحضير البيانات لعرضها من خلال وحدة تحكُّم واجهة المستخدم. في هذه البنية، ينفذ ViewModel
عملية اتخاذ القرار.
يحتفظ GameViewModel
بالبيانات مثل قيمة النتيجة وقائمة الكلمات والكلمة الحالية لأن هذه هي البيانات التي يتم عرضها على الشاشة. يحتوي GameViewModel
أيضًا على منطق النشاط التجاري لإجراء عمليات حسابية بسيطة لتحديد الحالة الحالية للبيانات.
عرض النماذج
ينشئ ViewModelFactory
مثيلات ViewModel
، سواء كانت تحتوي على معلمات إنشاء أو بدونها.
في الدروس التطبيقية حول الترميز اللاحقة، يمكنك التعرّف على مكوّنات Android المعمارية الأخرى ذات الصلة بوحدة التحكّم في واجهة المستخدم وViewModel
.
تم تصميم فئة ViewModel
لتخزين البيانات ذات الصلة بواجهة المستخدم وإدارتها. في هذا التطبيق، يتم ربط كل ViewModel
بجزء واحد.
في هذه المهمة، يمكنك إضافة أول ViewModel
إلى تطبيقك، وGameViewModel
لـ GameFragment
. ويمكنك أيضًا التعرّف على معنى ViewModel
عندما يكون واعيًا بمراحل الحياة.
الخطوة 1: إضافة فئة GameViewmodel
- افتح ملف
build.gradle(module:app)
. داخل الكتلةdependencies
، أضِف تبعية Gradle إلىViewModel
، و
إذا كنت تستخدم أحدث إصدار من المكتبة، يجب أن يجمع تطبيق الحل كما هو متوقع. وإذا لم يحدث ذلك، جرِّب حل المشكلة، أو ارجع إلى الإصدار الموضّح أدناه.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
- في المجلد
screens/game/
، أنشِئ فئة Kotlin جديدة باسمGameViewModel
. - جعل الصف
GameViewModel
يعمل على تمديد الصف التجريديViewModel
. - لمساعدتك في فهم كيفية فهم
ViewModel
لمراحل النشاط بشكل أفضل، يمكنك إضافة حظرinit
باستخدام عبارةlog
.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}
الخطوة 2: تجاوز onCleared() وإضافة تسجيل
يتم تلف ViewModel
عند فصل الجزء المرتبط أو عند انتهاء النشاط. قبل تدمير ViewModel
مباشرة، يتم استدعاء استدعاء onCleared()
لتنظيف الموارد.
- في الصف
GameViewModel
، يجب إلغاء الطريقةonCleared()
. - يمكنك إضافة بيان سجل داخل
onCleared()
لتتبّع مراحل نشاطGameViewModel
.
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
الخطوة 3: ربط GameViewmodel بجزء اللعبة
يجب ربط ViewModel
بوحدة تحكّم في واجهة المستخدم. ولربط الاثنين، يمكنك إنشاء مرجع إلى ViewModel
داخل وحدة التحكُّم في واجهة المستخدم.
في هذه الخطوة، يمكنك إنشاء مرجع لـ GameViewModel
داخل وحدة التحكّم المقابلة في واجهة المستخدم، وهو GameFragment
.
- في الصف
GameFragment
، أضِف حقلاً من النوعGameViewModel
في المستوى العلوي على أنه متغيّر الفئة.
private lateinit var viewModel: GameViewModel
الخطوة 4: إعداد طريقة العرض
أثناء إجراء تغييرات على الإعدادات، مثل تدوير الشاشة، تتم إعادة إنشاء وحدات تحكُّم واجهة المستخدم، مثل الأجزاء. ومع ذلك، تبقى ViewModel
مثيلات. وإذا أنشأت مثيل ViewModel
باستخدام الفئة ViewModel
، يتم إنشاء كائن جديد في كل مرة تتم فيها إعادة إنشاء الجزء. بدلاً من ذلك، يمكنك إنشاء مثيل ViewModel
باستخدام ViewModelProvider
.
آلية عمل تطبيق ViewModelProvider
:
- تعرض
ViewModelProvider
القيمةViewModel
الحالية إذا كانت موجودة، أو تنشئ رمزًا جديدًا إذا لم تكن موجودة. - تنشئ السمة
ViewModelProvider
مثيلViewModel
مرتبطًا بالنطاق المحدّد (نشاط أو جزء). - يتم الاحتفاظ بـ
ViewModel
التي تم إنشاؤها طالما كان النطاق نشطًا. على سبيل المثال، إذا كان النطاق عبارة عن جزء، يتم الاحتفاظ بـViewModel
حتى يتم فصل الجزء.
عليك إعداد ViewModel
، باستخدام طريقة ViewModelProviders.of()
لإنشاء ViewModelProvider
:
- في الصف
GameFragment
، عليك إعداد المتغيّرviewModel
. ضع هذا الرمز داخلonCreateView()
، بعد تعريف متغير الربط. استخدِم الطريقةViewModelProviders.of()
، وأجِب السياقGameFragment
المرتبط والصفGameViewModel
. - فوق إعداد الكائن
ViewModel
، عليك إضافة عبارة سجلّ لتسجيل استدعاء طريقةViewModelProviders.of()
.
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
- شغِّل التطبيق. في "استوديو Android"، افتح الجزء Logcat واختَر الفلتر
Game
. انقر على زر تشغيل على جهازك أو المحاكي. يتم فتح شاشة اللعبة.
كما هو موضّح في Logcat، فإن طريقةonCreateView()
فيGameFragment
تتطلب الطريقةViewModelProviders.of()
لإنشاءGameViewModel
. تظهر بيانات التسجيل التي أضفتها إلىGameFragment
وGameViewModel
في Logcat.
- فعِّل إعداد "التدوير التلقائي" على جهازك أو المحاكي واغيِّر اتجاه الشاشة بضع مرات. يتم محو
GameFragment
وإعادة إنشائها في كل مرة، لذلك يتم استدعاءViewModelProviders.of()
في كل مرة. ولكن يتم إنشاءGameViewModel
مرة واحدة فقط، ولا تتم إعادة إنشائه أو محوه لكل مكالمة.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- اخرج من اللعبة أو انتقِل من جزء اللعبة. تم تدمير
GameFragment
. سيتم أيضًا محوGameViewModel
المرتبطة واستدعاءonCleared()
.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel destroyed!
يظل ViewModel
ساريًا على تغييرات الضبط، لذا فهو مكان جيد للبيانات التي تحتاج إلى تجاوز تغييرات الضبط:
- ضع البيانات التي تريد عرضها على الشاشة، ورمزًا لمعالجة هذه البيانات في
ViewModel
. - يجب ألا تحتوي
ViewModel
على إشارات إلى الأجزاء أو الأنشطة أو الملفات الشخصية مطلقًا، لأنّ الأنشطة والأجزاء والمشاهدات لا تبقى محفوظة في شكل تغييرات في الإعدادات.
للمقارنة، في ما يلي كيفية معالجة بيانات واجهة مستخدم GameFragment
في تطبيق إجراء التفعيل قبل إضافة ViewModel
، وبعد إضافة ViewModel
:
- قبل إضافة
ViewModel
:
عندما يخضع التطبيق لتغيير في الضبط، مثل تدوير الشاشة، يتم إتلاف جزء اللعبة وإعادة إنشائه. يتم فقدان البيانات. - بعد إضافة
ViewModel
ونقل بيانات واجهة مستخدم اللعبة المجزأة's إلىViewModel
:
أصبحت جميع البيانات التي يحتاج إليها الجزء للعرض الآنViewModel
. عند خضوع التطبيق لعملية تغيير في الإعداد، سيتم الإبقاء علىViewModel
، ويتم الاحتفاظ بالبيانات.
في هذه المهمة، تنقل بيانات واجهة مستخدم التطبيق إلى صف GameViewModel
، إلى جانب طرق معالجة البيانات. ويمكنك إجراء ذلك حتى يتم الاحتفاظ بالبيانات أثناء تغييرات الضبط.
الخطوة 1: نقل حقول البيانات ومعالجة البيانات إلى مشاهدة النموذج
نقل حقول البيانات التالية وطرقها من GameFragment
إلى GameViewModel
:
- نقل حقول البيانات
word
وscore
وwordList
تأكَّد من أنword
وscore
ليساprivate
.
لا تنقل المتغير المتغير المُلزِم،GameFragmentBinding
لأنه يحتوي على مراجع إلى الملفات الشخصية. يُستخدَم هذا المتغير في تضخيم التنسيق وإعداد مستمعي النقرات وعرض البيانات على الشاشة - وهي مسؤوليات الكسر. - انقل طريقتَي
resetList()
وnextWord()
. تحدِّد هذه الطرق الكلمات التي يجب عرضها على الشاشة. - من داخل طريقة
onCreateView()
، انقل الطريقة التي تم استدعاءها إلىresetList()
وnextWord()
إلى كتلةinit
منGameViewModel
.
يجب أن تكون هذه الطرق في الكتلةinit
، لأنّه يجب إعادة ضبط قائمة الكلمات عند إنشاءViewModel
، وليس في كل مرة يتم فيها إنشاء الجزء. يمكنك حذف عبارة السجلّ في جزءinit
منGameFragment
.
تحتوي معالجات النقرة onSkip()
وonCorrect()
في GameFragment
على رمز لمعالجة البيانات وتعديل واجهة المستخدم. ويجب أن يظل الرمز المخصّص لتحديث واجهة المستخدم في الجزء، ولكن يجب نقل رمز معالجة البيانات إلى ViewModel
.
وفي الوقت الحالي، يمكنك وضع الطرق نفسها في كلتا الطريقتين:
- انسخ طريقة
onSkip()
وonCorrect()
منGameFragment
إلىGameViewModel
. - في
GameViewModel
، تأكَّد من أنّonSkip()
وonCorrect()
طريقةً غيرprivate
، لأنك ستشير إلى هاتين الطريقتين من الكسر.
إليك رمز صف GameViewModel
، بعد إعادة البناء:
class GameViewModel : ViewModel() {
// The current word
var word = ""
// The current score
var score = 0
// The list of words - the front of the list is the next word to guess
private lateinit var wordList: MutableList<String>
/**
* Resets the list of words and randomizes the order
*/
private fun resetList() {
wordList = mutableListOf(
"queen",
"hospital",
"basketball",
"cat",
"change",
"snail",
"soup",
"calendar",
"sad",
"desk",
"guitar",
"home",
"railway",
"zebra",
"jelly",
"car",
"crow",
"trade",
"bag",
"roll",
"bubble"
)
wordList.shuffle()
}
init {
resetList()
nextWord()
Log.i("GameViewModel", "GameViewModel created!")
}
/**
* Moves to the next word in the list
*/
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word = wordList.removeAt(0)
}
updateWordText()
updateScoreText()
}
/** Methods for buttons presses **/
fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
}
إليك رمز الصف GameFragment
، بعد إعادة البناء:
/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {
private lateinit var binding: GameFragmentBinding
private lateinit var viewModel: GameViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.game_fragment,
container,
false
)
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
updateScoreText()
updateWordText()
return binding.root
}
/** Methods for button click handlers **/
private fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
private fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = word
}
private fun updateScoreText() {
binding.scoreText.text = score.toString()
}
}
الخطوة 2: تعديل المراجع لمعالجات النقرات وحقول البيانات في GameFragment
- في
GameFragment
، عدِّل طريقتَيonSkip()
وonCorrect()
. أزِل الرمز لتعديل النتيجة واستدعِ طريقتَيonSkip()
وonCorrect()
المقابلتَين علىviewModel
. - بما أنك نقلت طريقة
nextWord()
إلىViewModel
، لم يعد بإمكان جزء اللعبة الوصول إليه.
فيGameFragment
، بالطريقةonSkip()
وonCorrect()
، استبدِل المكالمة بـnextWord()
بupdateScoreText()
وupdateWordText()
. تعرض هذه الطرق البيانات على الشاشة.
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}
- في
GameFragment
، عليك تعديل المتغيّرَينscore
وword
لاستخدام المتغيّراتGameViewModel
، لأن هذه المتغيّرَين متوفّرة الآن فيGameViewModel
.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}
- في
GameViewModel
، ضمن طريقةnextWord()
، أزِل الاستدعاءات إلى طريقتيupdateWordText()
وupdateScoreText()
. يتم استدعاء هذه الطرق الآن منGameFragment
. - أنشِئ التطبيق وتأكَّد من عدم حدوث أي أخطاء. إذا كانت لديك أخطاء، نظِّف المشروع وأعِد إنشائه.
- يمكنك تشغيل التطبيق وتشغيل اللعبة من خلال بعض الكلمات. عندما تكون في شاشة اللعبة، عليك تدوير الجهاز. لاحظ أن النتيجة الحالية والكلمة الحالية يتم الاحتفاظ بها بعد تغيير الاتجاه.
رائع يتم الآن تخزين جميع بيانات تطبيقك في ViewModel
، لذا يتم الاحتفاظ بها أثناء تغييرات الضبط.
في هذه المهمة، يتم تنفيذ أداة معالجة النقرات على زر إنهاء اللعبة.
- في
GameFragment
، أضِف طريقة باسمonEndGame()
. سيتم استدعاء طريقةonEndGame()
عندما ينقر المستخدم على زر إنهاء اللعبة.
private fun onEndGame() {
}
- في
GameFragment
، ضمن طريقةonCreateView()
، حدِّد مكان الرمز الذي يضبط أدوات معالجة النقرات من أجل الزرين حسنًا والتخطّي. وأسفل هذين السطرين مباشرةً، اضبط أداة معالجة نقرة على زر إنهاء اللعبة. استخدِم المتغير المُلزِم،binding
. داخل المستمع عند النقر، يمكنك استدعاء طريقةonEndGame()
.
binding.endGameButton.setOnClickListener { onEndGame() }
- في
GameFragment
، أضِف طريقة باسمgameFinished()
للانتقال إلى شاشة النتائج في التطبيق. أدخِل النتيجة كوسيطة باستخدام الوسيطات الآمنة.
/**
* Called when the game is finished
*/
private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score
NavHostFragment.findNavController(this).navigate(action)
}
- في الطريقة
onEndGame()
، عليك استدعاء الطريقةgameFinished()
.
private fun onEndGame() {
gameFinished()
}
- يمكنك تشغيل التطبيق وتشغيل اللعبة والتبديل بين الكلمات. انقر على الزر إنهاء اللعبة. لاحظ أن التطبيق ينتقل إلى شاشة النتائج، ولكن لا يتم عرض النتيجة النهائية. يمكنك حل هذه المشكلة في المهمة التالية.
وعندما ينهي المستخدم اللعبة، لا تعرض ScoreFragment
النتيجة. تريد أن يحتفظ ViewModel
بالنتيجة ليتم عرضها بواسطة ScoreFragment
. سيتم تمرير قيمة النتيجة أثناء إعداد ViewModel
باستخدام نمط طريقة المصنع.
نمط طريقة المصنع هو نمط تصميم مبتكر يستخدم أساليب المصنع لإنشاء العناصر. طريقة المصنع هي طريقة لعرض مثيل للصف نفسه.
في هذه المَهمّة، يمكنك إنشاء ViewModel
مع مُنشئ معلّمات لجزء النتيجة وطريقة المصنع لإنشاء مثيل ViewModel
.
- ضمن حزمة
score
، أنشِئ فئة Kotlin جديدة باسمScoreViewModel
. سيكون هذا الصفViewModel
لجزء النتيجة. - تمديد فئة
ScoreViewModel
منViewModel.
إضافة معلمة دالة إنشاء للنتيجة النهائية. أضِف كتلةinit
باستخدام بيان سجل. - في الصف
ScoreViewModel
، يمكنك إضافة متغيّر باسمscore
لحفظ النتيجة النهائية.
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}
- ضمن حزمة
score
، أنشِئ فئة Kotlin أخرى باسمScoreViewModelFactory
. ستكون هذه الفئة مسؤولة عن إنشاء مثيلScoreViewModel
. - تمديد فترة
ScoreViewModelFactory
للصف منViewModelProvider.Factory
. أضِف معلّم وضع للنتيجة النهائية.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}
- في
ScoreViewModelFactory
، يعرض "استوديو Android" خطأ عن عضو مجرّد غير مُطبّق. لحل الخطأ، يمكنك إلغاء طريقةcreate()
. في طريقةcreate()
، اعرض العنصرScoreViewModel
الذي تم إنشاؤه حديثًا.
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
- في
ScoreFragment
، أنشئ متغيّرات الفئة لـScoreViewModel
وScoreViewModelFactory
.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
- في
ScoreFragment
، داخلonCreateView()
، بعد إعداد المتغيّرbinding
، عليك إعدادviewModelFactory
. استخدِمScoreViewModelFactory
. تمرير النتيجة النهائية من حِزمة الوسيطة كمعلّمة إنشاء إلىScoreViewModelFactory()
.
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
- في
onCreateView(
)، بعد إعدادviewModelFactory
، عليك إعداد الكائنviewModel
. يجب استدعاء طريقةViewModelProviders.of()
، وتمريرها في سياق جزء النتيجة المرتبط وviewModelFactory
. سيؤدي هذا إلى إنشاء الكائنScoreViewModel
باستخدام طريقة المصنع المحدّدة في فئةviewModelFactory
.
.
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)
- في طريقة
onCreateView()
، بعد إعدادviewModel
، اضبط نص طريقة العرضscoreText
على النتيجة النهائية المحدّدة فيScoreViewModel
.
binding.scoreText.text = viewModel.score.toString()
- شغِّل تطبيقك وشغِّل اللعبة. انقر على بعض الكلمات أو انقر على إنهاء اللعبة. تجدر الإشارة إلى أن جزء النتيجة يعرض الآن النتيجة النهائية.
- اختياري: تحقّق من سجلات
ScoreViewModel
في Logcat عن طريق الفلترة علىScoreViewModel
. ويجب عرض قيمة النتيجة.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
في هذه المهمة، نفذت ScoreFragment
لاستخدام ViewModel
. وتعرّفت أيضًا على كيفية إنشاء مُنشئ معلمات تحتوي على معلَمات ViewModel
باستخدام واجهة ViewModelFactory
.
تهانينا. لقد غيّرت بنية تطبيقك لاستخدام أحد"مكوّنات Android الأساسية"في ViewModel
. لقد تم حل مشكلة مراحل نشاط التطبيق، والآن أصبحت بيانات اللعبة قائمة على التغييرات في الضبط. وتعرّفت أيضًا على كيفية إنشاء مُنشئ معلَمات من أجل إنشاء ViewModel
باستخدام واجهة ViewModelFactory
.
مشروع Android Studio: GuessTheWord
- تقترح إرشادات بنية تطبيق Android فصل الصفوف التي تتضمن مسؤوليات مختلفة.
- وحدة تحكُّم واجهة المستخدم هي فئة تستند إلى واجهة المستخدم، مثل
Activity
أوFragment
. يجب أن تتضمن وحدات التحكم في واجهة المستخدم منطقًا يتعامل مع تفاعلات واجهة المستخدم ونظام التشغيل فقط؛ يجب ألا تحتوي على بيانات لعرضها في واجهة المستخدم. ضع هذه البيانات فيViewModel
. - تخزّن فئة
ViewModel
البيانات ذات الصلة بواجهة المستخدم وتديرها. تسمح الفئةViewModel
بالبيانات بالحفاظ على بقاء تغييرات الضبط، مثل تدوير الشاشة. ViewModel
هو أحد مكوِّنات بنية Android التي يُنصَح بها.ViewModelProvider.Factory
هي واجهة يمكنك استخدامها لإنشاء عنصرViewModel
.
يقارن الجدول التالي وحدات التحكُّم في واجهة المستخدم مع مثيلات ViewModel
التي تحتفظ ببياناتها:
وحدة تحكُّم واجهة المستخدم | ViewView |
ومن أمثلة وحدات التحكم في واجهة المستخدم | ومن أمثلة |
لا تحتوي على أي بيانات سيتم عرضها في واجهة المستخدم. | يحتوي على بيانات تعرضها وحدة تحكم واجهة المستخدم في واجهة المستخدم. |
يحتوي على رمز لعرض البيانات ورمز حدث المستخدم مثل مستمعي النقر. | يحتوي على رمز لمعالجة البيانات. |
مسحها وإعادة إنشائها أثناء كل تغيير إعداد. | يتم إتلافها فقط عند اختفاء وحدة تحكُّم واجهة المستخدم المرتبطة بها نهائيًا، بالنسبة إلى نشاط أو وقت انتهاء النشاط أو جزء منه، عندما يتم فصل الجزء. |
تحتوي على من المشاهدات. | يجب ألّا يحتوي اسم التطبيق على إشارات إلى أنشطة أو أجزاء أو مشاهدات، لأنّها لا تبقى محفوظة في شكل تغييرات، ولكنّ |
يحتوي على مرجع إلى | لا تحتوي على أي مرجع إلى وحدة التحكّم المرتبطة بواجهة المستخدم. |
دورة Udacity:
مستندات مطوّر برامج Android:
- نظرة عامة على طريقة العرض
- التعامل مع مراحل النشاط باستخدام مكوّنات الوعي بمراحل الحياة
- دليل بنية التطبيقات
ViewModelProvider
ViewModelProvider.Factory
غير ذلك:
- النمط المعماري MVVM (نموذج العرض-العرض).
- مبدأ فصل المخاوف (SoC)
- نمط الطريقة الخاصة بالمصنع
يسرد هذا القسم المهام الدراسية المحتملة للطلاب الذين يعملون من خلال هذا الدرس التطبيقي حول الترميز في إطار دورة تدريبية يُديرها معلِّم. يجب أن ينفِّذ المعلّم ما يلي:
- يمكنك تخصيص واجب منزلي إذا لزم الأمر.
- التواصل مع الطلاب بشأن كيفية إرسال الواجبات المنزلية
- وضع درجات للواجبات المنزلية.
ويمكن للمعلّمين استخدام هذه الاقتراحات بقدر ما يريدون أو بقدر ما يريدون، ويجب عدم التردد في تخصيص أي واجبات منزلية أخرى مناسبة.
إذا كنت تستخدم هذا الدرس التطبيقي بنفسك، يمكنك استخدام هذه الواجبات المنزلية لاختبار معلوماتك.
الإجابة عن هذه الأسئلة
السؤال 1
لتجنب فقدان البيانات أثناء تغيير إعداد الجهاز، عليك حفظ بيانات التطبيق في أي فئة؟
ViewModel
LiveData
Fragment
Activity
السؤال 2
يجب ألا تحتوي ViewModel
على أي إشارات إلى الأجزاء أو الأنشطة أو الملفات الشخصية. صحيح أم خطأ؟
- صحيح
- خطأ
السؤال 3
متى يتم تدمير ViewModel
؟
- عندما يتم محو وحدة التحكّم المرتبطة بواجهة المستخدم وإعادة إنشائها أثناء تغيير اتجاه الجهاز.
- في تغيير الاتجاه
- عند انتهاء وحدة التحكم في واجهة المستخدم المرتبطة (إذا كان النشاط) أو منفصلة (إذا كانت جزءًا).
- عندما يضغط المستخدم على زر الرجوع.
السؤال 4
ما هي واجهة ViewModelFactory
؟
- جارٍ إنشاء مثيل لكائن
ViewModel
. - الاحتفاظ بالبيانات أثناء تغييرات الاتجاه
- إعادة تحميل البيانات التي يتم عرضها على الشاشة.
- يمكنك تلقّي إشعارات عند تغيير بيانات التطبيق.
بدء الدرس التالي:
وللحصول على روابط إلى دروس تطبيقية أخرى حول الترميز في هذه الدورة التدريبية، يُرجى الاطّلاع على الصفحة المقصودة لتطبيق الدروس التطبيقية حول الترميز Kotlin Fundamentals.