هذا الدرس التطبيقي حول الترميز هو جزء من دورة "أساسيات Android Kotlin". يمكنك تحقيق أقصى استفادة من هذه الدورة التدريبية إذا اتبعت ترتيب الخطوات في دروس البرمجة. يتم إدراج جميع الدروس التطبيقية حول الترميز الخاصة بالدورة التدريبية في الصفحة المقصودة للدروس التطبيقية حول الترميز في دورة Android Kotlin Fundamentals.
مقدمة
من أهم الأولويات لتقديم تجربة مستخدم خالية من الأخطاء في تطبيقك التأكّد من أنّ واجهة المستخدم تستجيب دائمًا وتعمل بسلاسة. إحدى طرق تحسين أداء واجهة المستخدم هي نقل المهام التي تستغرق وقتًا طويلاً، مثل عمليات قاعدة البيانات، إلى الخلفية.
في هذا الدرس التطبيقي حول الترميز، ستنفِّذ الجزء المخصّص للمستخدمين من تطبيق TrackMySleepQuality، وذلك باستخدام إجراءات Kotlin الفرعية لتنفيذ عمليات قاعدة البيانات بعيدًا عن سلسلة التعليمات الرئيسية.
ما يجب معرفته
يجب أن تكون على دراية بما يلي:
- إنشاء واجهة مستخدم أساسية باستخدام نشاط وقِطع وعناصر عرض ومعالجات نقرات
- التنقّل بين الأجزاء واستخدام
safeArgs
لتمرير بيانات بسيطة بين الأجزاء - عرض النماذج وعرض مصانع النماذج وعمليات التحويل و
LiveData
- كيفية إنشاء قاعدة بيانات
Room
وإنشاء كائن وصول إلى البيانات (DAO) وتحديد الكيانات - من المفيد أن تكون على دراية بمفاهيم سلاسل التنفيذ والمعالجة المتعددة.
أهداف الدورة التعليمية
- طريقة عمل سلاسل المحادثات في Android
- كيفية استخدام الروتينات الفرعية في Kotlin لنقل عمليات قاعدة البيانات بعيدًا عن سلسلة التعليمات الرئيسية
- كيفية عرض البيانات المنسَّقة في
TextView
الإجراءات التي ستنفذّها
- توسيع تطبيق TrackMySleepQuality لجمع البيانات وتخزينها وعرضها في قاعدة البيانات ومنها
- استخدِم إجراءات روتينية متزامنة لتنفيذ عمليات قاعدة البيانات التي تستغرق وقتًا طويلاً في الخلفية.
- استخدِم
LiveData
لتفعيل التنقّل وعرض شريط المعلومات. - استخدِم
LiveData
لتفعيل الأزرار وإيقافها.
في هذا الدرس التطبيقي حول الترميز، ستنشئ نموذج العرض والروتينات المشتركة وأجزاء عرض البيانات في تطبيق TrackMySleepQuality.
يحتوي التطبيق على شاشتَين، يتم تمثيلهما بلقطات، كما هو موضّح في الشكل أدناه.
تحتوي الشاشة الأولى، المعروضة على اليمين، على أزرار لبدء التتبُّع وإيقافه. تعرِض الشاشة جميع بيانات نوم المستخدم. يؤدي النقر على الزر محو إلى حذف جميع البيانات التي جمعها التطبيق للمستخدم نهائيًا.
الشاشة الثانية، المعروضة على اليسار، مخصّصة لاختيار تقييم لجودة النوم. في التطبيق، يتم تمثيل التقييم رقميًا. لأغراض التطوير، يعرض التطبيق رموز الوجوه وما يعادلها من أرقام.
يكون تدفّق المستخدم على النحو التالي:
- يفتح المستخدم التطبيق وتظهر له شاشة تتبُّع النوم.
- ينقر المستخدم على الزر بدء. تسجّل هذه السمة وقت البدء وتعرضه. يكون زر بدء غير مفعَّل، بينما يكون زر إيقاف مفعَّلاً.
- ينقر المستخدم على الزر إيقاف. يتم تسجيل وقت الانتهاء وفتح شاشة جودة النوم.
- يختار المستخدم رمزًا لجودة النوم. تنغلق الشاشة، وتعرض شاشة التتبُّع وقت انتهاء النوم وجودته. يكون زر إيقاف غير مفعَّل ويكون زر بدء مفعَّلاً. التطبيق جاهز للاستخدام في ليلة أخرى.
- يتم تفعيل الزر محو عندما تكون هناك بيانات في قاعدة البيانات. عندما ينقر المستخدم على الزر محو، يتم محو جميع بياناته بدون رجعة، ولن تظهر الرسالة "هل أنت متأكد؟".
يستخدم هذا التطبيق بنية مبسطة، كما هو موضّح أدناه في سياق البنية الكاملة. يستخدم التطبيق المكوّنات التالية فقط:
- وحدة التحكّم في واجهة المستخدم
- عرض النموذج و
LiveData
- قاعدة بيانات Room
في هذه المهمة، ستستخدم TextView
لعرض البيانات المنسَّقة لتتبُّع النوم. (هذه ليست الواجهة النهائية. ستتعرّف على طريقة أفضل في درس برمجة آخر.)
يمكنك مواصلة العمل على تطبيق TrackMySleepQuality الذي أنشأته في الدرس التطبيقي السابق أو تنزيل تطبيق البداية لهذا الدرس التطبيقي.
الخطوة 1: تنزيل تطبيق البداية وتشغيله
- نزِّل تطبيق TrackMySleepQuality-Coroutines-Starter من GitHub.
- أنشئ التطبيق وشغِّله. يعرض التطبيق واجهة المستخدم للجزء
SleepTrackerFragment
، ولكن بدون بيانات. لا تستجيب الأزرار للنقرات.
الخطوة 2: فحص الرمز
رمز البداية لهذا الدرس التطبيقي حول الترميز هو نفسه رمز الحلّ للدرس التطبيقي حول الترميز 6.1 إنشاء قاعدة بيانات Room.
- افتح الملف res/layout/activity_main.xml. يحتوي هذا التصميم على الجزء
nav_host_fragment
. لاحظ أيضًا العلامة<merge>
.
يمكن استخدام العلامةmerge
لإزالة التنسيقات المكرّرة عند تضمين التنسيقات، ويُنصح باستخدامها. مثال على تخطيط مكرّر: ConstraintLayout > LinearLayout > TextView، حيث قد يتمكّن النظام من إزالة LinearLayout. يمكن أن يؤدي هذا النوع من التحسين إلى تبسيط بنية العرض وتحسين أداء التطبيق. - في مجلد navigation، افتح الملف navigation.xml. يمكنك الاطّلاع على جزأين وإجراءات التنقّل التي تربط بينهما.
- في مجلد التصميم، انقر مرّتين على جزء "متتبّع النوم" للاطّلاع على تصميم XML الخاص به. يُرجى ملاحظة ما يلي:
- يتم تضمين بيانات التنسيق في العنصر
<layout>
لتفعيل ربط البيانات. - يتم ترتيب
ConstraintLayout
وطرق العرض الأخرى داخل العنصر<layout>
. - يتضمّن الملف علامة العنصر النائب
<data>
.
يوفر التطبيق الأولي أيضًا الأبعاد والألوان والأنماط لواجهة المستخدم. يحتوي التطبيق على قاعدة بيانات Room
ورمز DAO وكيان SleepNight
. إذا لم تكمل تجربة البرمجة السابقة، احرص على استكشاف هذه الجوانب من الرمز بنفسك.
بعد إنشاء قاعدة بيانات وواجهة مستخدم، عليك جمع البيانات وإضافتها إلى قاعدة البيانات وعرضها. يتم تنفيذ كل هذا العمل في نموذج العرض. سيتعامل نموذج عرض أداة تتبُّع النوم مع النقرات على الأزرار، ويتفاعل مع قاعدة البيانات من خلال DAO، ويوفّر البيانات لواجهة المستخدم من خلال LiveData
. يجب تنفيذ جميع عمليات قاعدة البيانات بعيدًا عن سلسلة التعليمات الرئيسية لواجهة المستخدم، ويمكنك إجراء ذلك باستخدام الروتينات المشتركة.
الخطوة 1: إضافة SleepTrackerViewModel
- في حزمة sleeptracker، افتح SleepTrackerViewModel.kt.
- افحص فئة
SleepTrackerViewModel
، التي يتم توفيرها لك في تطبيق البداية ويتم عرضها أيضًا أدناه. يُرجى العِلم أنّ الفئة توسّعAndroidViewModel()
. هذه الفئة هي نفسها الفئةViewModel
، ولكنّها تأخذ سياق التطبيق كمَعلمة وتتيحه كسمة. ستحتاج إلى ذلك لاحقًا.
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application) : AndroidViewModel(application) {
}
الخطوة 2: إضافة SleepTrackerViewModelFactory
- في حزمة sleeptracker، افتح SleepTrackerViewModelFactory.kt.
- افحص الرمز البرمجي المتوفّر لك للمصنع، والذي يظهر أدناه:
class SleepTrackerViewModelFactory(
private val dataSource: SleepDatabaseDao,
private val application: Application) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
return SleepTrackerViewModel(dataSource, application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
يُرجى مراعاة ما يلي:
- تتلقّى السمة
SleepTrackerViewModelFactory
الوسيطة نفسها التي تتلقّاها السمةViewModel
وتوسّع نطاقViewModelProvider.Factory
. - داخل المصنع، يتجاوز الرمز
create()
، الذي يقبل أي نوع من أنواع الفئات كمعلَمة ويعرضViewModel
. - في نص
create()
، يتحقّق الرمز من توفّر فئةSleepTrackerViewModel
، وإذا كانت متوفّرة، يعرض مثالاً عليها. وفي حال عدم توفّرها، يعرض الرمز خطأ.
الخطوة 3: تعديل SleepTrackerFragment
- في
SleepTrackerFragment
، احصل على مرجع لسياق التطبيق. ضَع المرجع فيonCreateView()
، أسفلbinding
. تحتاج إلى مرجع للتطبيق الذي يتم إرفاق هذه الفئة المجزّأة به، وذلك لتمريره إلى موفّر مصنع عرض النماذج.
تُطلق الدالةrequireNotNull
في Kotlin الخطأIllegalArgumentException
إذا كانت القيمة هيnull
.
val application = requireNotNull(this.activity).application
- تحتاج إلى مرجع إلى مصدر البيانات من خلال مرجع إلى DAO. في
onCreateView()
، قبلreturn
، حدِّدdataSource
. للحصول على مرجع إلى DAO الخاص بقاعدة البيانات، استخدِمSleepDatabase.getInstance(application).sleepDatabaseDao
.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- في
onCreateView()
، قبلreturn
، أنشئ نسخة منviewModelFactory
. يجب إدخالdataSource
وapplication
.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- بعد إنشاء المصنع، احصل على مرجع إلى
SleepTrackerViewModel
. تشير المَعلمةSleepTrackerViewModel::class.java
إلى فئة Java في وقت التشغيل لهذا العنصر.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
- يجب أن تبدو التعليمات البرمجية المكتملة على النحو التالي:
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
إليك طريقة onCreateView()
حتى الآن:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Get a reference to the binding object and inflate the fragment views.
val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_sleep_tracker, container, false)
val application = requireNotNull(this.activity).application
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
return binding.root
}
الخطوة 4: إضافة ربط البيانات لنموذج العرض
بعد إعداد ViewModel
الأساسي، عليك إكمال عملية إعداد ربط البيانات في SleepTrackerFragment
لربط ViewModel
بواجهة المستخدم.
في ملف التنسيق fragment_sleep_tracker.xml
:
- داخل الحظر
<data>
، أنشئ<variable>
يشير إلى الفئةSleepTrackerViewModel
.
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
في SleepTrackerFragment
:
- اضبط النشاط الحالي كمالك دورة حياة عملية الربط. أضِف هذا الرمز داخل طريقة
onCreateView()
، قبل عبارةreturn
:
binding.setLifecycleOwner(this)
- عيِّن متغير ربط
sleepTrackerViewModel
إلىsleepTrackerViewModel
. ضَع هذا الرمز داخلonCreateView()
، أسفل الرمز الذي ينشئSleepTrackerViewModel
:
binding.sleepTrackerViewModel = sleepTrackerViewModel
- من المحتمل أن يظهر لك خطأ، لأنّه عليك إعادة إنشاء عنصر الربط. نظِّف المشروع وأعِد إنشاءه للتخلّص من الخطأ.
- أخيرًا، كما هو الحال دائمًا، تأكَّد من أنّ الرمز البرمجي يتم إنشاؤه وتشغيله بدون أخطاء.
في Kotlin، تُعد الروتينات الفرعية طريقة للتعامل مع المهام الطويلة الأمد بشكل أنيق وفعّال. تتيح لك إجراءات Kotlin الفرعية تحويل الرمز البرمجي المستند إلى معاودة الاتصال إلى رمز برمجي تسلسلي. عادةً ما يكون من الأسهل قراءة الرمز المكتوب بالتسلسل، ويمكنه حتى استخدام ميزات اللغة مثل الاستثناءات. في النهاية، تؤدي الروتينات الفرعية وعمليات الاسترجاع الوظيفة نفسها: فهي تنتظر إلى أن تتوفّر نتيجة من مهمة تستغرق وقتًا طويلاً وتواصل التنفيذ.
تتضمّن الروتينات المشتركة الخصائص التالية:
- الروتينات الفرعية متزامنة ولا تحظر التنفيذ.
- تستخدِم إجراءات Coroutines دوال تعليق لجعل الرمز غير المتزامن تسلسليًا.
الروتينات الفرعية غير متزامنة.
يتم تشغيل الروتين الفرعي بشكل مستقل عن خطوات التنفيذ الرئيسية لبرنامجك. ويمكن أن يتم ذلك بالتوازي أو على معالج منفصل. قد يكون السبب أيضًا أنّك تنفّذ بعض عمليات المعالجة بينما ينتظر باقي التطبيق إدخال البيانات. من الجوانب المهمة في البرمجة غير المتزامنة أنّه لا يمكنك توقّع توفّر النتيجة إلى أن تنتظرها بشكل صريح.
على سبيل المثال، لنفترض أنّ لديك سؤالاً يتطلّب بحثًا، وطلبت من زميل العثور على الإجابة. ثم يغادرون ويعملون على حلّها، وكأنّهم يعملون "بشكل غير متزامن" و"في سلسلة محادثات منفصلة". يمكنك مواصلة العمل على مهام أخرى لا تعتمد على الإجابة، إلى أن يعود زميلك ويخبرك بها.
الروتينات الفرعية لا تحظر التنفيذ.
عدم الحظر يعني أنّ الروتين الفرعي لا يحظر سلسلة التعليمات الرئيسية أو سلسلة تعليمات واجهة المستخدم. وباستخدام الروتينات المشتركة، يحصل المستخدمون دائمًا على أفضل تجربة ممكنة، لأنّ التفاعل مع واجهة المستخدم له الأولوية دائمًا.
تستخدم الروتينات الفرعية دوال تعليق لجعل الرمز غير المتزامن تسلسليًا.
الكلمة الرئيسية suspend
هي طريقة Kotlin للإشارة إلى أنّ دالة أو نوع دالة متاحان للروتينات الفرعية. عندما تستدعي روتين فرعي دالة تم وضع علامة suspend
عليها، بدلاً من الحظر إلى أن تعرض الدالة نتيجة مثل استدعاء دالة عادية، يعلّق الروتين الفرعي التنفيذ إلى أن تصبح النتيجة جاهزة. بعد ذلك، يتم استئناف الروتين الفرعي من حيث توقّف، مع النتيجة.
أثناء تعليق الروتين الفرعي وانتظار النتيجة، يتم إلغاء حظر سلسلة التعليمات التي يتم تشغيل الروتين الفرعي عليها. بهذه الطريقة، يمكن تشغيل الدوال أو الروتينات الفرعية الأخرى.
لا تحدّد الكلمة الرئيسية suspend
سلسلة التعليمات البرمجية التي يتم تنفيذها. قد يتم تنفيذ دالة تعليق في سلسلة محادثات في الخلفية أو في سلسلة المحادثات الرئيسية.
لاستخدام الروتينات الفرعية في Kotlin، تحتاج إلى ثلاثة عناصر:
- وظيفة
- مرسِل
- نطاق
المهمة: المهمة هي أي شيء يمكن إلغاؤه. لكل روتين فرعي مهمة، ويمكنك استخدام المهمة لإلغاء الروتين الفرعي. يمكن ترتيب المهام في تسلسلات هرمية للعناصر الرئيسية والفرعية. يؤدي إلغاء مهمة رئيسية إلى إلغاء جميع المهام الثانوية التابعة لها على الفور، ما يوفّر عليك عناء إلغاء كل روتين فرعي يدويًا.
أداة الإرسال: ترسل أداة الإرسال إجراءات روتينية مشتركة لتنفيذها على سلاسل محادثات مختلفة. على سبيل المثال، ينفّذ Dispatcher.Main
المهام في سلسلة التعليمات الرئيسية، بينما ينقل Dispatcher.IO
مهام الحظر المتعلقة بوحدات الإدخال والإخراج إلى مجموعة مشتركة من سلاسل التعليمات.
النطاق: يحدّد نطاق الروتين المشترك السياق الذي يتم فيه تشغيل الروتين المشترك. يجمع النطاق بين معلومات حول مهمة روتين فرعي وموزّع. تتتبّع النطاقات الروتينات الفرعية. عند تشغيل روتين فرعي، يكون "في نطاق"، ما يعني أنّك حدّدت النطاق الذي سيتتبّع الروتين الفرعي.
أن يكون المستخدم قادرًا على التفاعل مع بيانات النوم بالطرق التالية:
- عندما ينقر المستخدم على الزر بدء، ينشئ التطبيق ليلة نوم جديدة ويخزّنها في قاعدة البيانات.
- عندما ينقر المستخدم على الزر إيقاف، يضيف التطبيق وقت انتهاء إلى الليلة.
- عندما ينقر المستخدم على الزر محو، يمحو التطبيق البيانات في قاعدة البيانات.
يمكن أن تستغرق عمليات قاعدة البيانات هذه وقتًا طويلاً، لذا يجب تشغيلها في سلسلة محادثات منفصلة.
الخطوة 1: إعداد إجراءات روتينية لعمليات قاعدة البيانات
عند النقر على الزر بدء في تطبيق "أداة تتبُّع النوم"، عليك استدعاء دالة في SleepTrackerViewModel
لإنشاء مثيل جديد من SleepNight
وتخزين المثيل في قاعدة البيانات.
يؤدي النقر على أي من الأزرار إلى تشغيل عملية في قاعدة البيانات، مثل إنشاء SleepNight
أو تعديله. لهذا السبب ولأسباب أخرى، يمكنك استخدام الروتينات المشتركة لتنفيذ معالجات النقرات لأزرار التطبيق.
- افتح ملف
build.gradle
على مستوى التطبيق وابحث عن التبعيات الخاصة بالروتينات المشتركة. لاستخدام الروتينات الفرعية، تحتاج إلى هذه التبعيات التي تمت إضافتها نيابةً عنك.
يتم تحديد$coroutine_version
في ملفbuild.gradle
الخاص بالمشروع على النحو التالي:coroutine_version =
'1.0.0'
.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
- افتح ملف
SleepTrackerViewModel
. - في نص الفئة، حدِّد
viewModelJob
وعيِّن له مثيلاً منJob
. تتيح لك هذه السمةviewModelJob
إلغاء جميع الروتينات الفرعية التي بدأها نموذج العرض هذا عندما لا يعود نموذج العرض مستخدَمًا ويتم إيقافه. بهذه الطريقة، لن ينتهي بك الأمر إلى استخدام إجراءات فرعية ليس لديها مكان للعودة إليه.
private var viewModelJob = Job()
- في نهاية نص الفئة، يمكنك إلغاء
onCleared()
وإلغاء جميع الروتينات الفرعية. عندما يتم تدميرViewModel
، يتم استدعاءonCleared()
.
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
- أسفل تعريف
viewModelJob
مباشرةً، حدِّدuiScope
للروتينات المشتركة. يحدّد النطاق مؤشر الترابط الذي سيتم تشغيل الروتين الفرعي عليه، ويجب أن يكون النطاق على دراية بالمهمة أيضًا. للحصول على نطاق، اطلب مثيلاً منCoroutineScope
، ومرِّر أداة إرسال ومهمة.
يعني استخدام Dispatchers.Main
أنّ الروتينات الفرعية التي يتم تشغيلها في uiScope
ستعمل على سلسلة التعليمات الرئيسية. ويكون ذلك منطقيًا بالنسبة إلى العديد من الروتينات المشتركة التي يبدأها ViewModel
، لأنّه بعد أن تنفّذ هذه الروتينات المشتركة بعض عمليات المعالجة، تؤدي إلى تعديل واجهة المستخدم.
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
- أسفل تعريف
uiScope
، حدِّد متغيّرًا باسمtonight
لتخزين الليلة الحالية. اجعل المتغيرMutableLiveData
، لأنّك بحاجة إلى أن تتمكّن من مراقبة البيانات وتغييرها.
private var tonight = MutableLiveData<SleepNight?>()
- لتهيئة المتغيّر
tonight
في أقرب وقت ممكن، أنشئ كتلةinit
أسفل تعريفtonight
واستدعِinitializeTonight()
. يمكنك تحديدinitializeTonight()
في الخطوة التالية.
init {
initializeTonight()
}
- أسفل الحظر
init
، نفِّذinitializeTonight()
. فيuiScope
، شغِّل روتينًا فرعيًا. في الداخل، احصل على قيمةtonight
من قاعدة البيانات عن طريق استدعاءgetTonightFromDatabase()
، ثمّ خصّص القيمة إلىtonight.value
. يمكنك تحديدgetTonightFromDatabase()
في الخطوة التالية.
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
- تنفيذ
getTonightFromDatabase()
. عرِّفها كدالةprivate suspend
تعرض قيمة قابلة للتصغيرSleepNight
، إذا لم يكن هناكSleepNight
حالي تم بدؤه. سيؤدي ذلك إلى حدوث خطأ، لأنّ الدالة يجب أن تعرض قيمة.
private suspend fun getTonightFromDatabase(): SleepNight? { }
- داخل نص الدالة
getTonightFromDatabase()
، اعرض النتيجة من روتين فرعي يعمل في سياقDispatchers.IO
. استخدِم أداة توزيع مهام الإدخال/الإخراج، لأنّ الحصول على البيانات من قاعدة البيانات هو عملية إدخال/إخراج ولا علاقة لها بواجهة المستخدم.
return withContext(Dispatchers.IO) {}
- داخل كتلة الإرجاع، اسمح للروتين المشترك بالحصول على الليلة (الأحدث) من قاعدة البيانات. إذا لم يكن وقت البدء ووقت الانتهاء متطابقَين، ما يعني أنّه قد تم إكمال الليلة، عليك عرض القيمة
null
. بخلاف ذلك، يتم عرض الليل.
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
يجب أن تبدو getTonightFromDatabase()
وظيفة التعليق المكتملة على النحو التالي. يجب ألا تظهر أي أخطاء أخرى.
private suspend fun getTonightFromDatabase(): SleepNight? {
return withContext(Dispatchers.IO) {
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
}
}
الخطوة 2: إضافة معالج النقر لزر "البدء"
يمكنك الآن تنفيذ onStartTracking()
، وهو معالج النقرات للزر بدء. عليك إنشاء SleepNight
جديد وإدراجه في قاعدة البيانات وتعيينه إلى tonight
. ستبدو بنية onStartTracking()
مشابهة إلى حد كبير لبنية initializeTonight()
.
- ابدأ بتعريف الدالة
onStartTracking()
. يمكنك وضع معالِجات النقرات فوقonCleared()
في الملفSleepTrackerViewModel
.
fun onStartTracking() {}
- داخل
onStartTracking()
، شغِّل روتينًا فرعيًا فيuiScope
، لأنّك تحتاج إلى هذه النتيجة للمتابعة وتعديل واجهة المستخدم.
uiScope.launch {}
- داخل عملية تشغيل الروتين الفرعي، أنشئ
SleepNight
جديدًا، والذي يسجّل الوقت الحالي كوقت بدء.
val newNight = SleepNight()
- داخل عملية تشغيل الروتين الفرعي، استدعِ الدالة
insert()
لإدراجnewNight
في قاعدة البيانات. سيظهر لك خطأ لأنّك لم تحدّد وظيفة التعليقinsert()
هذه بعد. (هذه ليست دالة DAO التي تحمل الاسم نفسه).
insert(newNight)
- داخل عملية تشغيل الروتين الفرعي، عدِّل
tonight
أيضًا.
tonight.value = getTonightFromDatabase()
- أسفل
onStartTracking()
، عرِّفinsert()
كدالةprivate suspend
تأخذSleepNight
كمعلَمة.
private suspend fun insert(night: SleepNight) {}
- بالنسبة إلى نص الدالة
insert()
، شغِّل روتينًا فرعيًا في سياق الإدخال/الإخراج وأدرِج الليلة في قاعدة البيانات من خلال استدعاء الدالةinsert()
من كائن الوصول إلى البيانات (DAO).
withContext(Dispatchers.IO) {
database.insert(night)
}
- في ملف التنسيق
fragment_sleep_tracker.xml
، أضِف معالج النقراتonStartTracking()
إلىstart_button
باستخدام ميزة ربط البيانات السحرية التي أعددتها سابقًا. تنشئ صيغة الدالة@{() ->
دالة lambda لا تأخذ أي وسيطات وتستدعي معالج النقرات فيsleepTrackerViewModel
.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
- أنشئ تطبيقك وشغِّله. انقر على الزر بدء. يؤدي هذا الإجراء إلى إنشاء بيانات، ولكن لا يمكنك الاطّلاع على أي شيء بعد. عليك إصلاح ذلك الخطأ أولاً.
fun someWorkNeedsToBeDone { uiScope.launch { suspendFunction() } } suspend fun suspendFunction() { withContext(Dispatchers.IO) { longrunningWork() } }
الخطوة 3: عرض البيانات
في SleepTrackerViewModel
، يشير المتغيّر nights
إلى LiveData
لأنّ getAllNights()
في DAO يعرض LiveData
.
هذه ميزة في Room
تتيح تعديل LiveData
nights
في كل مرة تتغير فيها البيانات في قاعدة البيانات لعرض أحدث البيانات. ولن تحتاج أبدًا إلى ضبط قيمة LiveData
بشكلٍ صريح أو تعديلها. Room
تعدّل البيانات لتتطابق مع قاعدة البيانات.
ومع ذلك، إذا عرضت nights
في عرض نصي، سيتم عرض مرجع العنصر. للاطّلاع على محتوى الكائن، حوِّل البيانات إلى سلسلة منسَّقة. استخدِم خريطة Transformation
يتم تنفيذها في كل مرة يتلقّى فيها nights
بيانات جديدة من قاعدة البيانات.
- افتح ملف
Util.kt
وأزِل التعليق من رمز تعريفformatNights()
وعباراتimport
المرتبطة به. لإزالة التعليق من التعليمات البرمجية في "استوديو Android"، حدِّد كل التعليمات البرمجية التي تحمل العلامة//
واضغط علىCmd+/
أوControl+/
. - لاحظ أنّ
formatNights()
تعرض النوعSpanned
، وهو سلسلة بتنسيق HTML. - افتح الملف strings.xml. لاحظ استخدام
CDATA
لتنسيق موارد السلسلة من أجل عرض بيانات النوم. - افتح SleepTrackerViewModel. في الفئة
SleepTrackerViewModel
، أسفل تعريفuiScope
، حدِّد متغيّرًا باسمnights
. احصل على جميع الليالي من قاعدة البيانات وخصِّصها للمتغيرnights
.
private val nights = database.getAllNights()
- أسفل تعريف
nights
مباشرةً، أضِف رمزًا برمجيًا لتحويلnights
إلىnightsString
. استخدِم الدالةformatNights()
منUtil.kt
.
. مرِّرnights
إلى الدالةmap()
من الفئةTransformations
. للوصول إلى موارد السلسلة، حدِّد دالة الربط على أنّها استدعاءformatNights()
. يجب توفيرnights
وResources
.
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- افتح ملف التنسيق
fragment_sleep_tracker.xml
. فيTextView
، ضمن السمةandroid:text
، يمكنك الآن استبدال سلسلة الموارد بمرجع إلىnightsString
.
"@{sleepTrackerViewModel.nightsString}"
- أعِد إنشاء الرمز وشغِّل التطبيق. من المفترض أن تظهر الآن جميع بيانات نومك مع أوقات البدء.
- انقر على الزر بدء بضع مرات أخرى، وستظهر لك المزيد من البيانات.
في الخطوة التالية، عليك تفعيل وظيفة الزر إيقاف.
الخطوة 4: إضافة معالج النقر لزر الإيقاف
باستخدام النمط نفسه كما في الخطوة السابقة، نفِّذ معالج النقر على الزر إيقاف في SleepTrackerViewModel.
- أضِف
onStopTracking()
إلىViewModel
. إطلاق روتين فرعي فيuiScope
إذا لم يتم ضبط وقت الانتهاء بعد، اضبطendTimeMilli
على وقت النظام الحالي واستدعِupdate()
باستخدام بيانات الليل.
في Kotlin، يحدّد بناء الجملةreturn@
label
الدالة التي تعود منها هذه العبارة، من بين عدة دوال متداخلة.
fun onStopTracking() {
uiScope.launch {
val oldNight = tonight.value ?: return@launch
oldNight.endTimeMilli = System.currentTimeMillis()
update(oldNight)
}
}
- نفِّذ
update()
باستخدام النمط نفسه الذي استخدمته لتنفيذinsert()
.
.
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- لربط معالج النقرات بواجهة المستخدم، افتح ملف التنسيق
fragment_sleep_tracker.xml
وأضِف معالج النقرات إلىstop_button
.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
- إنشاء تطبيقك وتشغيله
- انقر على بدء، ثم انقر على إيقاف. يظهر لك وقت البدء ووقت الانتهاء وجودة النوم بدون قيمة ومدة النوم.
الخطوة 5: إضافة معالج النقر لزر "محو"
- وبالمثل، نفِّذ الترميزَين
onClear()
وclear()
.
fun onClear() {
uiScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
- لربط معالج النقرات بواجهة المستخدم، افتح
fragment_sleep_tracker.xml
وأضِف معالج النقرات إلىclear_button
.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
- إنشاء تطبيقك وتشغيله
- انقر على محو للتخلّص من جميع البيانات. بعد ذلك، انقر على بدء وإيقاف لإنشاء بيانات جديدة.
مشروع "استوديو Android": TrackMySleepQualityCoroutines
- استخدِم
ViewModel
وViewModelFactory
وربط البيانات لإعداد بنية واجهة المستخدم للتطبيق. - للحفاظ على سلاسة تشغيل واجهة المستخدم، استخدِم إجراءات روتينية متزامنة للمهام التي تستغرق وقتًا طويلاً، مثل جميع عمليات قاعدة البيانات.
- الروتينات الفرعية متزامنة ولا تحظر التنفيذ. تستخدم
suspend
وظائف لجعل الرمز غير المتزامن تسلسليًا. - عندما تستدعي روتين فرعي دالة تم وضع علامة
suspend
عليها، بدلاً من حظر التنفيذ إلى أن تعرض الدالة نتيجة مثل استدعاء الدالة العادي، يتم تعليق التنفيذ إلى أن تصبح النتيجة جاهزة. ثم يتم استئناف العملية من حيث توقفت مع النتيجة. - الفرق بين الحظر والتعليق هو أنّه في حال حظر سلسلة محادثات، لن يتم تنفيذ أي عمل آخر. إذا تم تعليق سلسلة التعليمات، سيتم تنفيذ أعمال أخرى إلى أن تصبح النتيجة متاحة.
لتشغيل روتين فرعي، تحتاج إلى مهمة وموزّع ونطاق:
- بشكل أساسي، المهمة هي أي شيء يمكن إلغاؤه. لكل روتين فرعي مهمة، ويمكنك استخدام مهمة لإلغاء روتين فرعي.
- يرسل المرسِل الروتينات الفرعية لتشغيلها على سلاسل محادثات مختلفة. تنفِّذ
Dispatcher.Main
المهام على سلسلة التعليمات الرئيسية، بينما تُستخدمDispartcher.IO
لتفريغ مهام الحظر المتعلقة بوحدات الإدخال والإخراج إلى مجموعة مشتركة من سلاسل التعليمات. - يجمع النطاق المعلومات، بما في ذلك الوظيفة والموزّع، لتحديد السياق الذي يتم فيه تنفيذ الروتين الفرعي. تتتبّع النطاقات الروتينات الفرعية.
لتنفيذ معالجات النقرات التي تؤدي إلى تشغيل عمليات قاعدة البيانات، اتّبِع النمط التالي:
- تشغيل روتين فرعي يعمل على سلسلة التعليمات الرئيسية أو سلسلة تعليمات واجهة المستخدم، لأنّ النتيجة تؤثر في واجهة المستخدم
- استدعِ دالة تعليق لتنفيذ العمليات التي تستغرق وقتًا طويلاً، وذلك حتى لا تحظر سلسلة التعليمات الخاصة بواجهة المستخدم أثناء انتظار النتيجة.
- لا علاقة للعملية الطويلة الأمد بواجهة المستخدم، لذا عليك التبديل إلى سياق الإدخال/الإخراج. بهذه الطريقة، يمكن تشغيل العمل في مجموعة سلاسل محادثات تم تحسينها وتخصيصها لهذه الأنواع من العمليات.
- بعد ذلك، استدعِ دالة قاعدة البيانات لتنفيذ العمل.
استخدِم Transformations
خريطة لإنشاء سلسلة من عنصر LiveData
في كل مرة يتغيّر فيها العنصر.
دورة Udacity التدريبية:
مستندات مطوّري تطبيقات Android:
RoomDatabase
- إعادة استخدام التصاميم باستخدام <include/>
ViewModelProvider.Factory
SimpleDateFormat
HtmlCompat
المستندات والمقالات الأخرى:
- نمط المصنع
- درس تطبيقي حول الترميز باستخدام الكوروتينات
- الروتينات المشتركة، المستندات الرسمية
- سياق الروتين الفرعي وعوامل الإرسال
Dispatchers
- تجاوز الحد الأقصى لسرعة Android
Job
launch
- عمليات الإرجاع والانتقال في Kotlin
- CDATA هي اختصار لبيانات الأحرف. يشير CDATA إلى أنّ البيانات بين السلسلتين تتضمّن بيانات يمكن تفسيرها على أنّها ترميز XML، ولكن لا يجب تفسيرها على هذا النحو.
يسرد هذا القسم مهامًا منزلية محتملة للطلاب الذين يعملون على هذا الدرس التطبيقي العملي كجزء من دورة تدريبية يقودها مدرّب. على المعلّم تنفيذ ما يلي:
- حدِّد واجبًا منزليًا إذا لزم الأمر.
- توضيح كيفية إرسال الواجبات المنزلية للطلاب
- وضع درجات للواجبات المنزلية
يمكن للمدرّبين استخدام هذه الاقتراحات بالقدر الذي يريدونه، ويجب ألا يترددوا في تكليف الطلاب بأي واجبات منزلية أخرى يرونها مناسبة.
إذا كنت تعمل على هذا الدرس العملي بنفسك، يمكنك استخدام مهام الواجب المنزلي هذه لاختبار معلوماتك.
الإجابة عن هذه الأسئلة
السؤال 1
في ما يلي مزايا الروتينات الفرعية:
- وهي غير حاصرة
- يتم تشغيلها بشكل غير متزامن.
- يمكن تشغيلها على سلسلة محادثات غير السلسلة الرئيسية.
- وهي تساهم دائمًا في تسريع عمليات تشغيل التطبيق.
- يمكنهم استخدام الاستثناءات.
- ويمكن كتابتها وقراءتها كرمز خطي.
السؤال 2
ما هي دالة التعليق؟
- دالة عادية يتمّ إضافة التعليق التوضيحي إليها باستخدام الكلمة الرئيسية
suspend
. - دالة يمكن طلبها داخل إجراءات فرعية.
- أثناء تنفيذ دالة تعليق، يتم تعليق سلسلة التعليمات التي تستدعي الدالة.
- يجب أن يتم تشغيل وظائف التعليق دائمًا في الخلفية.
السؤال 3
ما الفرق بين حظر سلسلة محادثات وتعليقها؟ ضَع علامة على جميع الإجابات الصحيحة.
- عند حظر التنفيذ، لا يمكن تنفيذ أي عمل آخر على سلسلة التعليمات المحظورة.
- عند تعليق التنفيذ، يمكن لسلسلة التعليمات تنفيذ مهام أخرى أثناء انتظار اكتمال العمل الذي تم تفويضه.
- يكون التعليق أكثر فعالية، لأنّ سلاسل المحادثات قد لا تكون في انتظار أي إجراء.
- سواء تم حظر التنفيذ أو تعليقه، سيظل في انتظار نتيجة الروتين الفرعي قبل المتابعة.
الانتقال إلى الدرس التالي:
للحصول على روابط تؤدي إلى دروس تطبيقية أخرى في هذه الدورة التدريبية، اطّلِع على الصفحة المقصودة الخاصة بالدروس التطبيقية حول أساسيات Android Kotlin.