Android Kotlin Fundamentals 06.2: الكوروتينات والغرفة

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

مقدمة

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

في هذا الدرس التطبيقي حول الترميز، تُنفِّذ الجزء الذي يواجهه المستخدم من تطبيقTrackMySleepquality، باستخدام الكوروتينات بلغة Kotlin لإجراء عمليات قاعدة البيانات بعيدًا عن سلسلة المحادثات الرئيسية.

ما يجب معرفته

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

  • إنشاء واجهة مستخدم أساسية (UI) باستخدام النشاط والأجزاء والمشاهدات ومعالجات النقرات.
  • التنقّل بين الأجزاء واستخدام safeArgs لتمرير البيانات البسيطة بين الأجزاء
  • يمكنك عرض النماذج وعرض مصانع النماذج والتحوّلات وLiveData.
  • كيفية إنشاء قاعدة بيانات Room وإنشاء داود والتعريف بالكيانات
  • ويُعدّ هذا الإجراء مفيدًا إذا كنت على دراية بمفاهيم سلاسل المحادثات والمعالجة المتعددة.

ما ستتعرَّف عليه

  • آلية عمل سلاسل المحادثات في Android
  • كيفية استخدام الكوروتينات في لغة Kotlin لإبعاد عمليات قاعدة البيانات عن سلسلة المحادثات الرئيسية.
  • طريقة عرض البيانات المنسقة في TextView.

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

  • يمكنك توسيع نطاق تطبيق تتبع MyMylelequality لجمع البيانات وتخزينها وعرضها في قاعدة البيانات.
  • استخدام الكوروتينات لتشغيل عمليات قاعدة البيانات الطويلة في الخلفية
  • استخدِم LiveData لتفعيل التنقُّل وعرض شريط الوجبات الخفيفة.
  • يمكنك استخدام LiveData لتفعيل الأزرار وإيقافها.

في هذا الدرس التطبيقي حول الترميز، تُنشئ جزءًا من نموذج العرض والكوروتينات وأجزاء عرض البيانات في تطبيقTrackMySleepquality.

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

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

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

تكون خطوات المستخدم على النحو التالي:

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

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

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

في هذه المهمة، يمكنك استخدام TextView لعرض بيانات تتبُّع النوم المنسَّقة. (هذه ليست الواجهة النهائية. وستتعلّم طريقة أفضل في درس تطبيقي آخر حول الترميز).

ويمكنك المتابعة باستخدام تطبيقTrackMySleepquality الذي أنشأته في الدرس التطبيقي السابق أو تنزيل التطبيق للمبتدئين لهذا الدرس التطبيقي.

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

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

الخطوة 2: فحص الرمز

يُعد رمز التفعيل لهذا الدرس التطبيقي هو نفسه رمز الحلّ للدرس التطبيقي حول ترميز قاعدة بيانات غرفة 6.1.

  1. افتح res/Layout/activity_main.xml. يتضمّن هذا التنسيق الجزء nav_host_fragment. وتجدر الإشارة أيضًا إلى العلامة <merge>.

    يمكن استخدام العلامة merge للتخلّص من التنسيقات المتكرّرة عند استخدامها، كما يُفضَّل استخدامها. ومن أمثلة التنسيق المكرر ConstraintLayout > LineLayout > TextView، حيث قد يتمكن النظام من إسقاط التنسيق الخطي. ويمكن لهذا النوع من التحسين تبسيط العرض الهرمي وتحسين أداء التطبيق.
  2. في مجلد التنقّل، افتح Navigation.xml. يمكنك الاطّلاع على جزأين وإجراءات التنقل التي تربط بينهما.
  3. في مجلد التنسيق، انقر مرّتين على جزء أداة تتبّع النوم للاطّلاع على تنسيق XML. يُرجى مراعاة ما يلي:
  • وتكون بيانات التنسيق محاطة بعنصر <layout> لتفعيل ربط البيانات.
  • يتم ترتيب ConstraintLayout والملفات الشخصية الأخرى داخل العنصر <layout>.
  • يحتوي الملف على علامة <data> نائبة.

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

الآن بعد أن أنشأت قاعدة بيانات وواجهة مستخدم، عليك جمع البيانات وإضافة البيانات إلى قاعدة البيانات وعرض البيانات. ويتم كل هذا العمل في نموذج العرض. سيعمل نموذج تتبُّع النوم على معالجة النقرات على الأزرار والتفاعل مع قاعدة البيانات من خلال "إحصاءات Google" لتقديم بيانات إلى واجهة المستخدم من خلال LiveData. يجب تشغيل جميع عمليات قاعدة البيانات بعيدًا عن سلسلة محادثات واجهة المستخدم الرئيسية، وستنفّذ ذلك باستخدام الكوروتين.

الخطوة 1: إضافة SleepTrackerViewmodel

  1. في حزمة sleeptracker، افتح SleepTrackerViewmodel.kt.
  2. تحقَّق من درجة SleepTrackerViewModel، التي يتم توفيرها لك في تطبيق بدء التشغيل، وتظهر أيضًا أدناه. يُرجى العِلم أنّ الصف الدراسي يمتد إلى AndroidViewModel(). هذه الفئة مماثلة للسمة ViewModel، ولكنها تأخذ سياق التطبيق كمعلَمة وتجعله متاحًا كموقع. ستحتاج لتلك المعلومات لاحقًا.
class SleepTrackerViewModel(
       val database: SleepDatabaseDao,
       application: Application) : AndroidViewModel(application) {
}

الخطوة 2: إضافة SleepTrackerViewmodelMan

  1. في حزمة sleeptracker، افتح SleepTrackerView والمصنع.kt.
  2. افحص الرمز الذي يتم تقديمه لك للمصنع، كما هو موضّح أدناه:
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

  1. في SleepTrackerFragment، احصل على مرجع لسياق التطبيقات. ضع المرجع في onCreateView()، أسفل binding. أنت بحاجة إلى مرجع للتطبيق الذي تم إرفاق هذا الجزء به، لتمريره إلى موفّر المصنع لنموذج العرض.

    تعرض دالة requireNotNull لغة Kotlin IllegalArgumentException إذا كانت القيمة هي null.
val application = requireNotNull(this.activity).application
  1. تحتاج إلى مرجع إلى مصدر بياناتك من خلال مرجع إلى DAO. في onCreateView()، قبل return، حدِّد dataSource. للحصول على مرجع إلى مشاهِد البيانات في يوم واحد، استخدِم SleepDatabase.getInstance(application).sleepDatabaseDao.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. في onCreateView()، قبل return، أنشئ مثيلًا لـ viewModelFactory. يجب تمريرها dataSource وapplication.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
  1. بما أنّه لديك مصنع الآن، يمكنك الحصول على مرجع إلى SleepTrackerViewModel. تشير المعلمة SleepTrackerViewModel::class.java إلى فئة جافا وقت التشغيل لهذا الكائن.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)
  1. ويجب أن يظهر الرمز النهائي بالشكل التالي:
// 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:

  1. داخل الكتلة <data>، أنشئ <variable> تشير إلى الصف SleepTrackerViewModel.
<data>
   <variable
       name="sleepTrackerViewModel"
       type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>

في SleepTrackerFragment:

  1. ضبط النشاط الحالي كمالك مراحل النشاط للربط. أضِف هذا الرمز داخل طريقة onCreateView()، قبل عبارة return:
binding.setLifecycleOwner(this)
  1. عليك تخصيص متغيّر الربط sleepTrackerViewModel للسمة sleepTrackerViewModel. ضع هذا الرمز داخل onCreateView()، أسفل الرمز الذي ينشئ SleepTrackerViewModel:
binding.sleepTrackerViewModel = sleepTrackerViewModel
  1. قد يظهر لك خطأ، لأنه يجب عليك إعادة إنشاء العنصر المُلزِم. نظِّف المشروع وأعِد إنشائه للتخلّص من الخطأ.
  2. أخيرًا، كالمعتاد، تأكّد من إنشاء الرمز وتشغيله بدون أخطاء.

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

ولن تتوفّر الخصائص الكوروتينية التالية:

  • الكوروتينات غير متزامنة وغير محظورة.
  • تستخدم الكوروتينات وظائف تعليق لجعل الرمز غير المتزامن تسلسليًا.

الكوروتينات غير متزامنة

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

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

الكوروتينات غير محظورة

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

تستخدم الكوروتينات وظائف التعليق لجعل الرمز غير المتزامن تسلسليًا.

الكلمة الرئيسية suspend هي طريقة Kotlin' لوضع علامة على دالة أو نوع دالة على أنها متاحة للكوروتينات. عندما يستدعي الكوروتين دالة تم وضع علامة suspend عليها، بدلاً من حظرها حتى تعود الدالة إلى وظيفتها مثل استدعاء الدالة العادية، يعلّق كوروتين التنفيذ حتى تصبح النتيجة جاهزة. يَتِمُّ اسْتِئْنَافُ الْكُرْيُونْ مِنْ حَيْثُ تَوَقَّفَ تَنَاوُلُهُ وَتَأْدِيَهُ إِلَى النَّتِيجَة.

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

لا تحدّد الكلمة الرئيسية suspend سلسلة المحادثات التي يتم تنفيذ الرمز عليها. قد تعمل دالة التعليق على سلسلة محادثات في الخلفية أو على سلسلة المحادثات الرئيسية.

لاستخدام الكوروتينات في لغة Kotlin، تحتاج إلى ثلاثة عناصر:

  • وظيفة
  • المُرسل
  • نطاق

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

المرسل: يرسل المُرسل صوت الكوروتينات لتعمل على سلاسل محادثات مختلفة. على سبيل المثال، ينفِّذ Dispatcher.Main مهام على سلسلة المحادثات الرئيسية، وتُزيل Dispatcher.IO عمليات التحميل التي تحظر مهام الإدخال والإخراج إلى مجموعة مشتركة من سلاسل المحادثات.

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

تريد أن يتمكن المستخدم من التفاعل مع بيانات النوم بالطرق التالية:

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

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

الخطوة 1: إعداد الكوروتينات لعمليات قواعد البيانات

عند النقر على الزر بدء في تطبيق Sleep Tracker، تريد استدعاء دالة في SleepTrackerViewModel لإنشاء مثيل جديد من SleepNight وتخزين المثيل في قاعدة البيانات.

يؤدي النقر على أي من الأزرار إلى تشغيل عملية قاعدة البيانات، مثل إنشاء SleepNight أو تعديله. ولهذا السبب وغيره، يمكنك استخدام الكوروتين لتنفيذ معالجات النقرات لأزرار التطبيق.

  1. افتح الملف 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"
  1. افتح ملف SleepTrackerViewModel.
  2. في نص الصف الدراسي، حدِّد viewModelJob وخصِّص له مثيل Job. ويسمح لك هذا viewModelJob بإلغاء كل الكوروتينات التي يبدأها نموذج الملف الشخصي هذا عند عدم استخدام نموذج العرض وتلفه. وبهذه الطريقة، لن ينتهي بكائن الكوروتينات التي لا تعود إليها في أي وقت.
private var viewModelJob = Job()
  1. في نهاية نص الصف الدراسي، يتم إلغاء onCleared() وإلغاء كل الكوروتين. عندما يتم تدمير ViewModel، يتم استدعاء onCleared().
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. أسفل تعريف viewModelJob مباشرةً، حدِّد uiScope للكوروتينات. يحدّد النطاق سلسلة المحادثات التي سيتم تشغيل الكوروتين عليها، ويجب أن يعرف النطاق أيضًا هذه الوظيفة. للحصول على نطاق، اطلب مثيلًا لـ CoroutineScope، وأدخِل المُرسل ومهمة.

استخدام Dispatchers.Main يعني أنه سيتم تشغيل الكوروتينات في uiScope على سلسلة المحادثات الرئيسية. هذا الأمر معقول للعديد من الكوروتينات التي تبدأ في ViewModel، لأنه بعد تنفيذ هذه الكوروتينات لبعض المعالجة، فإنها تؤدي إلى تحديث واجهة المستخدم.

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  1. أسفل تعريف uiScope، حدِّد متغيّرًا باسم tonight يستضيف الليلة الحالية. اجعل المتغير MutableLiveData، لأنه يجب أن تكون قادرًا على ملاحظة البيانات وتغييرها.
private var tonight = MutableLiveData<SleepNight?>()
  1. لإعداد المتغير tonight في أقرب وقت ممكن، يمكنك إنشاء مجموعة init أسفل تعريف tonight والاستدعاء initializeTonight(). يمكنك تحديد initializeTonight() في الخطوة التالية.
init {
   initializeTonight()
}
  1. أسفل حظر init، نفِّذ initializeTonight(). فِي uiScope، يَتِمُّ الْآنْ تَشْغِيلُ الْكُورِيْتَيْنْ. في الداخل، احصل على قيمة tonight من قاعدة البيانات عن طريق استدعاء getTonightFromDatabase()، وتعيين القيمة إلى tonight.value. يمكنك تحديد getTonightFromDatabase() في الخطوة التالية.
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. تنفيذ getTonightFromDatabase(). ويجب تحديدها كدالة private suspend التي تعرض SleepNight فارغة من القيم، إذا لم تكن هناك قيمة حالية تبدأ SleepNight. تظهر لك رسالة خطأ، لأن الدالة يجب أن تعرض شيئًا ما.
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. داخل نص الدالة getTonightFromDatabase()، تعرض نتيجة من كوروتين يتم تشغيله في سياق Dispatchers.IO. استخدم المسؤول عن الإدخال/الإخراج، نظرًا لأن الحصول على البيانات من قاعدة البيانات هو عملية إدخال/إخراج ولا علاقة لها بواجهة المستخدم.
  return withContext(Dispatchers.IO) {}
  1. داخل كتلة الإرجاع، دع الكوروتين يحصل على الليلة (أحدث ليلة) من قاعدة البيانات. إذا لم يكن هناك فرق متطابق بين أوقات البدء والانتهاء، ما يعني أنّه قد سبق أن اكتمل الليل، يمكنك عرض 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() إلى حد كبير.

  1. ابدأ باستخدام تعريف الدالة لـ onStartTracking(). يمكنك وضع معالجات النقر أعلى من onCleared() في الملف SleepTrackerViewModel.
fun onStartTracking() {}
  1. داخل onStartTracking()، عليك تشغيل كوروتين في uiScope، لأنك تحتاج إلى هذه النتيجة للمتابعة وتحديث واجهة المستخدم.
uiScope.launch {}
  1. ضمن إطلاق الكورتين، أنشئ SleepNight جديدًا يسجِّل الوقت الحالي كوقت البدء.
        val newNight = SleepNight()
  1. لا تزال داخل إطلاق الكورتين، اتصل بالرقم insert() لإدراج newNight في قاعدة البيانات. ستظهر لك رسالة خطأ لأنك لم تحدد بعد دالة تعليق insert() هذه. (هذه ليست دالة DAO التي تحمل الاسم نفسه.)
       insert(newNight)
  1. يُرجى تحديث tonight أيضًا داخل الإطلاق في الكورونتين.
       tonight.value = getTonightFromDatabase()
  1. أسفل onStartTracking()، يتم تعريف insert() كدالة private suspend التي تستخدم SleepNight كوسيطة لها.
private suspend fun insert(night: SleepNight) {}
  1. بالنسبة لنص insert()، عليك إطلاق كوروتين في سياق مؤتمر I/O وإدراج الليل في قاعدة البيانات من خلال الاتصال بـ insert() من خلال DAO.
   withContext(Dispatchers.IO) {
       database.insert(night)
   }
  1. في ملف تنسيق fragment_sleep_tracker.xml، أضِف معالج النقر لـ onStartTracking() إلى start_button باستخدام سحر ربط البيانات الذي أعددته سابقًا. تُنشئ تدوين الدالة @{() -> دالة lambda لا تحتاج إلى وسيطات واستدعاء معالج النقرات في sleepTrackerViewModel.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. أنشئ تطبيقك وشغِّله. وانقر على الزر ابدأ. يؤدي هذا الإجراء إلى إنشاء بيانات، ولكن لا يمكنك الاطّلاع على أي شيء بعد. يمكنك حل هذه المشكلة بعد ذلك.
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 بيانات جديدة من قاعدة البيانات.

  1. يُرجى فتح الملف Util.kt وإلغاء تعليق الرمز لتعريف formatNights() والعبارات import المرتبطة بها. لإلغاء تعليق الرمز في "استوديو Android"، اختَر جميع الرموز التي تم وضع علامة // عليها واضغط على Cmd+/ أو Control+/.
  2. تجدر الإشارة إلى أن formatNights() تعرض النوع Spanned، وهو سلسلة بتنسيق HTML.
  3. افتح strings.xml. لاحِظ استخدام CDATA لتنسيق موارد السلسلة لعرض بيانات النوم.
  4. افتح SleepTrackerViewmodel. في الفئة SleepTrackerViewModel، أسفل تعريف uiScope، حدِّد متغيّرًا باسم nights. يمكنك الحصول على جميع الليالي من قاعدة البيانات وإسنادها إلى المتغيّر nights.
private val nights = database.getAllNights()
  1. أسفل الرمز nights مباشرةً، أضِف الرمز لتحويل nights إلى nightsString. استخدِم الدالة formatNights() من Util.kt.

    مرِّر nights في الدالة map() من الفئة Transformations. للوصول إلى موارد السلسلة، حدِّد وظيفة الربط على أنها استدعاء formatNights(). توفير nights وكائن Resources
val nightsString = Transformations.map(nights) { nights ->
   formatNights(nights, application.resources)
}
  1. افتح ملف التنسيق fragment_sleep_tracker.xml. في السمة TextView، في السمة android:text، يمكنك الآن استبدال سلسلة الموارد بمرجع إلى nightsString.
"@{sleepTrackerViewModel.nightsString}"
  1. أعِد إنشاء الرمز الخاص بك وشغِّل التطبيق. من المفترض أن تظهر الآن جميع بيانات النوم تتضمّن أوقات البدء.
  2. انقر على الزر ابدأ عدة مرات، وسيظهر لك المزيد من البيانات.

في الخطوة التالية، فعِّل الزر إيقاف.

الخطوة 4: إضافة معالج النقرات للزر "إيقاف"

باستخدام النمط نفسه كما في الخطوة السابقة، نفِّذ معالج النقر للزر Stop (إيقاف) في SleepTrackerViewModel..

  1. إضافة onStopTracking() إلى ViewModel بِيْتِمّْ تَشْغِيلْ مَعْلُومَة فِي uiScope. إذا لم يتم ضبط وقت الانتهاء حتى الآن، اضبط endTimeMilli على وقت النظام الحالي واستدعِ update() باستخدام البيانات الليلية.

    في لغة Kotlin، تحدِّد البنية return@label الدالة التي يتم إرجاع هذه العبارة منها من بين عدة وظائف متداخلة.
fun onStopTracking() {
   uiScope.launch {
       val oldNight = tonight.value ?: return@launch
       oldNight.endTimeMilli = System.currentTimeMillis()
       update(oldNight)
   }
}
  1. نفِّذ update() باستخدام النمط نفسه الذي استخدمته لتنفيذ insert().
private suspend fun update(night: SleepNight) {
   withContext(Dispatchers.IO) {
       database.update(night)
   }
}
  1. لربط معالج النقرات بواجهة المستخدم، افتح ملف تنسيق fragment_sleep_tracker.xml وأضِف معالج النقرة إلى stop_button.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  1. إنشاء التطبيق وتشغيله
  2. انقر على بدء، ثم انقر على إيقاف. سيظهر لك وقت البدء ووقت الانتهاء وجودة النوم بدون أي قيمة والمدة الزمنية للنوم.

الخطوة 5: إضافة معالج النقر على الزر "محو"

  1. تنفيذ مماثل لـ onClear() وclear().
fun onClear() {
   uiScope.launch {
       clear()
       tonight.value = null
   }
}

suspend fun clear() {
   withContext(Dispatchers.IO) {
       database.clear()
   }
}
  1. لربط معالج النقرات بواجهة المستخدم، افتح fragment_sleep_tracker.xml وأضِف معالج النقرة إلى clear_button.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
  1. إنشاء التطبيق وتشغيله
  2. انقر على محو لإزالة كل البيانات. بعد ذلك، انقر على بدء وإيقاف لإنشاء بيانات جديدة.

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

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

لإطلاق الكورتين، تحتاج إلى وظيفة ومرسل ونطاق:

  • في الأساس، تعتبر الوظيفة أي شيء يمكن إلغاؤه. لكلّ كورتين وظيفة، ويمكنك استخدام وظيفة لإلغاء الكوروتين.
  • يُرسل المُرسِل الكوروتينات لتعمل على سلاسل محادثات مختلفة. يعمل Dispatcher.Main على تنفيذ المهام في سلسلة المحادثات الرئيسية، وDispartcher.IO مخصّص لإلغاء تحميل مهام I/O إلى مجموعة مشتركة من سلاسل المحادثات.
  • ويدمج النطاق المعلومات، بما في ذلك الوظيفة والمُرسل، لتحديد السياق الذي تعمل فيه الكوروتين. تتبّع النطاقات الكوروتينات.

لتنفيذ معالجات النقرات التي تؤدي إلى تشغيل عمليات قاعدة البيانات، اتبع النمط التالي:

  1. يمكنك تشغيل كوروتين على سلسلة التعليمات الرئيسية أو واجهة المستخدم، لأن النتيجة تؤثر في واجهة المستخدم.
  2. يجب استدعاء وظيفة تعليق لتنفيذ العمل الطويل الأمد بحيث لا يتم حظر سلسلة محادثات واجهة المستخدم أثناء انتظار النتيجة.
  3. ليس هناك تأثير طويل الأمد للعمل على واجهة المستخدم، لذا يمكنك التبديل إلى سياق I/O. وبهذه الطريقة، يمكن العمل في مجموعة سلاسل محادثات محسّنة ومخصّصة لهذه الأنواع من العمليات.
  4. وبعد ذلك، عليك استدعاء دالة قاعدة البيانات لتنفيذ العمل.

يمكنك استخدام خريطة Transformations لإنشاء سلسلة من كائن LiveData في كل مرة يتغير فيها الكائن.

دورة Udacity:

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

وثائق ومقالات أخرى:

يسرد هذا القسم المهام الدراسية المحتملة للطلاب الذين يعملون من خلال هذا الدرس التطبيقي حول الترميز في إطار دورة تدريبية يُديرها معلِّم. يجب أن ينفِّذ المعلّم ما يلي:

  • يمكنك تخصيص واجب منزلي إذا لزم الأمر.
  • التواصل مع الطلاب بشأن كيفية إرسال الواجبات المنزلية
  • وضع درجات للواجبات المنزلية.

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

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

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

السؤال 1

أيّ مما يلي مزايا من الكوروتين:

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

السؤال 2

ما هي وظيفة التعليق؟

  • دالة عادية بها تعليقات توضيحية للكلمة الرئيسية suspend.
  • دالة يمكن استدعاؤها داخل الكوروتينات.
  • أثناء تشغيل وظيفة التعليق، يتم تعليق سلسلة محادثات الاتصال.
  • يجب أن تعمل دوال التعليق دائمًا في الخلفية.

السؤال 3

ما الفرق بين حظر سلسلة محادثات وتعليقها؟ ضَع علامة على كل المعلومات الصحيحة.

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

الانتقال إلى الدرس التالي: 6.3 استخدام LiveData للتحكم في حالات الزر

وللحصول على روابط إلى دروس تطبيقية أخرى حول الترميز في هذه الدورة التدريبية، يُرجى الاطّلاع على الصفحة المقصودة لتطبيق الدروس التطبيقية حول الترميز Kotlin Fundamentals.