يشكّل هذا الدرس التطبيقي جزءًا من الدورة التدريبية لأساسيات Android Kotlin. ستحصل على أقصى قيمة ممكنة من هذه الدورة التدريبية إذا كنت تستخدم الدروس التطبيقية حول الترميز بشكل متسلسل. يتم إدراج جميع الدروس التطبيقية حول ترميز الدورات التدريبية في الصفحة المقصودة لدروس الترميز Android Kotlin Fundamentals.
مقدمة
من أهم أولويات إنشاء تجربة مستخدم خالية من الأخطاء التأكد من تجاوب واجهة المستخدم دائمًا وتشغيلها بسلاسة. وتتمثل إحدى طرق تحسين أداء واجهة المستخدم في نقل المهام التي تستغرق مدة طويلة، مثل عمليات قاعدة البيانات، إلى الخلفية.
في هذا الدرس التطبيقي حول الترميز، تُنفِّذ الجزء الذي يواجهه المستخدم من تطبيقTrackMySleepquality، باستخدام الكوروتينات بلغة Kotlin لإجراء عمليات قاعدة البيانات بعيدًا عن سلسلة المحادثات الرئيسية.
ما يجب معرفته
ويجب أن تكون على دراية بما يلي:
- إنشاء واجهة مستخدم أساسية (UI) باستخدام النشاط والأجزاء والمشاهدات ومعالجات النقرات.
- التنقّل بين الأجزاء واستخدام
safeArgs
لتمرير البيانات البسيطة بين الأجزاء - يمكنك عرض النماذج وعرض مصانع النماذج والتحوّلات و
LiveData
. - كيفية إنشاء قاعدة بيانات
Room
وإنشاء داود والتعريف بالكيانات - ويُعدّ هذا الإجراء مفيدًا إذا كنت على دراية بمفاهيم سلاسل المحادثات والمعالجة المتعددة.
ما ستتعرَّف عليه
- آلية عمل سلاسل المحادثات في Android
- كيفية استخدام الكوروتينات في لغة Kotlin لإبعاد عمليات قاعدة البيانات عن سلسلة المحادثات الرئيسية.
- طريقة عرض البيانات المنسقة في
TextView
.
الإجراءات التي ستنفذّها
- يمكنك توسيع نطاق تطبيق تتبع MyMylelequality لجمع البيانات وتخزينها وعرضها في قاعدة البيانات.
- استخدام الكوروتينات لتشغيل عمليات قاعدة البيانات الطويلة في الخلفية
- استخدِم
LiveData
لتفعيل التنقُّل وعرض شريط الوجبات الخفيفة. - يمكنك استخدام
LiveData
لتفعيل الأزرار وإيقافها.
في هذا الدرس التطبيقي حول الترميز، تُنشئ جزءًا من نموذج العرض والكوروتينات وأجزاء عرض البيانات في تطبيقTrackMySleepquality.
يحتوي التطبيق على شاشتين، تمثلهما الأجزاء، كما هو موضّح في الشكل التالي.
تحتوي الشاشة الأولى، الموضحة على اليمين، على أزرار لبدء التتبع وإيقافه. تعرض الشاشة جميع بيانات النوم للمستخدم. يعمل الزر محو على حذف جميع البيانات التي جمعها التطبيق للمستخدم نهائيًا.
الشاشة الثانية، التي تظهر على اليسار، مخصّصة لاختيار تقييم جودة النوم. في التطبيق، يتم تمثيل التقييم رقميًا. لأغراض التطوير، يعرض التطبيق كلاً من رموز الوجه والمعادلات الرقمية لها.
تكون خطوات المستخدم على النحو التالي:
- يفتح المستخدم التطبيق وتظهر له شاشة تتبُّع النوم.
- ينقر المستخدم على الزر ابدأ. يسجِّل هذا العمود وقت البدء ويعرضه. يكون زر البدء غير مفعّل، والزر إيقاف مفعّل.
- ينقر المستخدم على الزر إيقاف. ويتم تسجيل وقت الانتهاء وفتح شاشة جودة النوم.
- ويختار المستخدم رمز جودة النوم. ويتم إغلاق الشاشة، كما تعرض شاشة التتبُّع وقت انتهاء النوم وجودة النوم. الزر إيقاف غير مفعّل والزر ابدأ مفعّل. التطبيق جاهز لليلة أخرى.
- يتم تفعيل الزر محو عند توفّر بيانات في قاعدة البيانات. عندما ينقر المستخدم على الزر محو، يتم محو جميع بياناته بدون اللجوء إلى الخطأ - لا تظهر هذه الرسالة&كذلك، هل أنت متأكد؟
يستخدم هذا التطبيق بنية مبسطة، كما هو موضح أدناه في سياق البنية الكاملة. يستخدم التطبيق المكونات التالية فقط:
- وحدة تحكُّم واجهة المستخدم
- عرض النموذج و
LiveData
- قاعدة بيانات الغرفة
في هذه المهمة، يمكنك استخدام TextView
لعرض بيانات تتبُّع النوم المنسَّقة. (هذه ليست الواجهة النهائية. وستتعلّم طريقة أفضل في درس تطبيقي آخر حول الترميز).
ويمكنك المتابعة باستخدام تطبيقTrackMySleepquality الذي أنشأته في الدرس التطبيقي السابق أو تنزيل التطبيق للمبتدئين لهذا الدرس التطبيقي.
الخطوة 1: تنزيل تطبيق بدء التشغيل وتشغيله
- نزِّل التطبيق TrackMySleepquality-Coroutines-Starter من GitHub.
- إنشاء التطبيق وتشغيله. يعرض التطبيق واجهة المستخدم للجزء
SleepTrackerFragment
، ولكن بدون بيانات. الأزرار لا تستجيب للنقرات.
الخطوة 2: فحص الرمز
يُعد رمز التفعيل لهذا الدرس التطبيقي هو نفسه رمز الحلّ للدرس التطبيقي حول ترميز قاعدة بيانات غرفة 6.1.
- افتح res/Layout/activity_main.xml. يتضمّن هذا التنسيق الجزء
nav_host_fragment
. وتجدر الإشارة أيضًا إلى العلامة<merge>
.
يمكن استخدام العلامةmerge
للتخلّص من التنسيقات المتكرّرة عند استخدامها، كما يُفضَّل استخدامها. ومن أمثلة التنسيق المكرر ConstraintLayout > LineLayout > TextView، حيث قد يتمكن النظام من إسقاط التنسيق الخطي. ويمكن لهذا النوع من التحسين تبسيط العرض الهرمي وتحسين أداء التطبيق. - في مجلد التنقّل، افتح Navigation.xml. يمكنك الاطّلاع على جزأين وإجراءات التنقل التي تربط بينهما.
- في مجلد التنسيق، انقر مرّتين على جزء أداة تتبّع النوم للاطّلاع على تنسيق XML. يُرجى مراعاة ما يلي:
- وتكون بيانات التنسيق محاطة بعنصر
<layout>
لتفعيل ربط البيانات. - يتم ترتيب
ConstraintLayout
والملفات الشخصية الأخرى داخل العنصر<layout>
. - يحتوي الملف على علامة
<data>
نائبة.
يوفر تطبيق بدء التشغيل أيضًا أبعادًا وألوانًا ونمطًا لواجهة المستخدم. يحتوي التطبيق على قاعدة بيانات Room
وDAO وكيان SleepNight
. إذا لم تكمل الدرس التطبيقي السابق للرمز، احرص على استكشاف هذه الجوانب من الرمز بنفسك.
الآن بعد أن أنشأت قاعدة بيانات وواجهة مستخدم، عليك جمع البيانات وإضافة البيانات إلى قاعدة البيانات وعرض البيانات. ويتم كل هذا العمل في نموذج العرض. سيعمل نموذج تتبُّع النوم على معالجة النقرات على الأزرار والتفاعل مع قاعدة البيانات من خلال "إحصاءات Google" لتقديم بيانات إلى واجهة المستخدم من خلال LiveData
. يجب تشغيل جميع عمليات قاعدة البيانات بعيدًا عن سلسلة محادثات واجهة المستخدم الرئيسية، وستنفّذ ذلك باستخدام الكوروتين.
الخطوة 1: إضافة SleepTrackerViewmodel
- في حزمة sleeptracker، افتح SleepTrackerViewmodel.kt.
- تحقَّق من درجة
SleepTrackerViewModel
، التي يتم توفيرها لك في تطبيق بدء التشغيل، وتظهر أيضًا أدناه. يُرجى العِلم أنّ الصف الدراسي يمتد إلىAndroidViewModel()
. هذه الفئة مماثلة للسمةViewModel
، ولكنها تأخذ سياق التطبيق كمعلَمة وتجعله متاحًا كموقع. ستحتاج لتلك المعلومات لاحقًا.
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application) : AndroidViewModel(application) {
}
الخطوة 2: إضافة SleepTrackerViewmodelMan
- في حزمة sleeptracker، افتح SleepTrackerView والمصنع.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
لغة KotlinIllegalArgumentException
إذا كانت القيمة هيnull
.
val application = requireNotNull(this.activity).application
- تحتاج إلى مرجع إلى مصدر بياناتك من خلال مرجع إلى DAO. في
onCreateView()
، قبلreturn
، حدِّدdataSource
. للحصول على مرجع إلى مشاهِد البيانات في يوم واحد، استخدِمSleepDatabase.getInstance(application).sleepDatabaseDao
.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- في
onCreateView()
، قبلreturn
، أنشئ مثيلًا لـviewModelFactory
. يجب تمريرهاdataSource
وapplication
.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- بما أنّه لديك مصنع الآن، يمكنك الحصول على مرجع إلى
SleepTrackerViewModel
. تشير المعلمةSleepTrackerViewModel::class.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 بتحويل الرمز المستند إلى معاودة الاتصال إلى رمز تسلسلي. عادةً ما تسهل قراءة الرمز المكتوب بشكل تسلسلي ويمكن أن يستخدم ميزات لغة مثل الاستثناءات. وفي النهاية، تعمل الكوروتينات واستدعاءات التجزئة بالطريقة نفسها، وهي تنتظر حتى تتوفّر نتيجة من مهمة طويلة الأمد وتستمر في التنفيذ.
ولن تتوفّر الخصائص الكوروتينية التالية:
- الكوروتينات غير متزامنة وغير محظورة.
- تستخدم الكوروتينات وظائف تعليق لجعل الرمز غير المتزامن تسلسليًا.
الكوروتينات غير متزامنة
تعمل كوروتين بشكل مستقل عن خطوات التنفيذ الرئيسية في البرنامج. ويمكن أن يكون ذلك بالتوازي أو معالِج منفصل. وقد يكون ذلك أثناء انتظار بقية أجزاء التطبيق للإدخال، ولكن يتسلّل بعض الشيء. هناك جانب مهم في حالة عدم المزامنة وهو أنه لا يمكنك توقع أن النتيجة متاحة، حتى تنتظرها صراحةً.
على سبيل المثال، لنفترض أن لديك سؤالًا يتطلب البحث، وتطلب من أحد زملائك العثور على الإجابة. وبعد ذلك، يعمل المعلِّمون على تنفيذ هذا العمل، كما لو كانوا يقومون بالعمل، ويمكنك الاستمرار في عمل آخر لا يعتمد على الإجابة، إلى أن يعود زميلك ويخبرك بالإجابة.
الكوروتينات غير محظورة
تعني عدم الحظر أن الكوروتين لا يحظر السلسلة الرئيسية أو واجهة المستخدم. ولهذا السبب، يقدّم المستخدمون دائمًا تجربة سلسة قدر الإمكان، لأنّ التفاعل مع واجهة المستخدم له الأولوية دائمًا.
تستخدم الكوروتينات وظائف التعليق لجعل الرمز غير المتزامن تسلسليًا.
الكلمة الرئيسية suspend
هي طريقة Kotlin' لوضع علامة على دالة أو نوع دالة على أنها متاحة للكوروتينات. عندما يستدعي الكوروتين دالة تم وضع علامة suspend
عليها، بدلاً من حظرها حتى تعود الدالة إلى وظيفتها مثل استدعاء الدالة العادية، يعلّق كوروتين التنفيذ حتى تصبح النتيجة جاهزة. يَتِمُّ اسْتِئْنَافُ الْكُرْيُونْ مِنْ حَيْثُ تَوَقَّفَ تَنَاوُلُهُ وَتَأْدِيَهُ إِلَى النَّتِيجَة.
أثناء تعليق الكورتينية وانتظار نتيجة، تتم إزالة الحظر عن سلسلة المحادثات التي يعمل فيها. وبهذه الطريقة، يمكن تشغيل وظائف أو كوروتين أخرى.
لا تحدّد الكلمة الرئيسية suspend
سلسلة المحادثات التي يتم تنفيذ الرمز عليها. قد تعمل دالة التعليق على سلسلة محادثات في الخلفية أو على سلسلة المحادثات الرئيسية.
لاستخدام الكوروتينات في لغة Kotlin، تحتاج إلى ثلاثة عناصر:
- وظيفة
- المُرسل
- نطاق
الوظيفة: الوظيفة هي أي شيء يمكن إلغاؤه. لكلّ كورتين وظيفة، ويمكنك استخدام الوظيفة لإلغاء الكوروتين. يمكن ترتيب الوظائف في التدرجات الهرمية بين الوالدين والطفل. يؤدي إلغاء وظيفة مخصّصة للوالدَين إلى إلغاء كل الأطفال في الحال، وهذا أسهل بكثير من إلغاء كل كورونين يدويًا.
المرسل: يرسل المُرسل صوت الكوروتينات لتعمل على سلاسل محادثات مختلفة. على سبيل المثال، ينفِّذ Dispatcher.Main
مهام على سلسلة المحادثات الرئيسية، وتُزيل Dispatcher.IO
عمليات التحميل التي تحظر مهام الإدخال والإخراج إلى مجموعة مشتركة من سلاسل المحادثات.
النطاق: يحدد النطاق "كوروتين" السياق الذي يتم فيه تشغيل الكوروتين. يدمج النطاق معلومات حول وظيفة الكوروتين والمرسل. تتبّع النطاقات الكوروتينات. عند إطلاق كورنوتين، يشير ذلك إلى علامة @ في نطاق، وهو ما يعني أنك أشرت إلى النطاق الذي سيتتبّع كوروتين.
تريد أن يتمكن المستخدم من التفاعل مع بيانات النوم بالطرق التالية:
- عندما ينقر المستخدم على الزر ابدأ، ينشئ التطبيق ليلة نوم جديدة ويخزّن ليلة النوم في قاعدة البيانات.
- عندما ينقر المستخدم على الزر إيقاف، يُحدِّث التطبيق ليلاً بوقت انتهاء.
- عندما ينقر المستخدم على الزر محو، يمحو التطبيق البيانات في قاعدة البيانات.
ويمكن أن تستغرق عمليات قاعدة البيانات هذه وقتًا طويلاً، لذا يجب تنفيذها في سلسلة محادثات منفصلة.
الخطوة 1: إعداد الكوروتينات لعمليات قواعد البيانات
عند النقر على الزر بدء في تطبيق Sleep Tracker، تريد استدعاء دالة في 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()
، عليك إطلاق كوروتين في سياق مؤتمر I/O وإدراج الليل في قاعدة البيانات من خلال الاتصال بـ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: إضافة معالج النقرات للزر "إيقاف"
باستخدام النمط نفسه كما في الخطوة السابقة، نفِّذ معالج النقر للزر Stop (إيقاف) في 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
مخصّص لإلغاء تحميل مهام I/O إلى مجموعة مشتركة من سلاسل المحادثات. - ويدمج النطاق المعلومات، بما في ذلك الوظيفة والمُرسل، لتحديد السياق الذي تعمل فيه الكوروتين. تتبّع النطاقات الكوروتينات.
لتنفيذ معالجات النقرات التي تؤدي إلى تشغيل عمليات قاعدة البيانات، اتبع النمط التالي:
- يمكنك تشغيل كوروتين على سلسلة التعليمات الرئيسية أو واجهة المستخدم، لأن النتيجة تؤثر في واجهة المستخدم.
- يجب استدعاء وظيفة تعليق لتنفيذ العمل الطويل الأمد بحيث لا يتم حظر سلسلة محادثات واجهة المستخدم أثناء انتظار النتيجة.
- ليس هناك تأثير طويل الأمد للعمل على واجهة المستخدم، لذا يمكنك التبديل إلى سياق I/O. وبهذه الطريقة، يمكن العمل في مجموعة سلاسل محادثات محسّنة ومخصّصة لهذه الأنواع من العمليات.
- وبعد ذلك، عليك استدعاء دالة قاعدة البيانات لتنفيذ العمل.
يمكنك استخدام خريطة Transformations
لإنشاء سلسلة من كائن LiveData
في كل مرة يتغير فيها الكائن.
دورة Udacity:
مستندات مطوّري برامج Android:
RoomDatabase
- إعادة استخدام التنسيقات مع <include/>
ViewModelProvider.Factory
SimpleDateFormat
HtmlCompat
وثائق ومقالات أخرى:
- نمط المصنع
- درس تطبيقي حول ترميز الكوروتين
- الكوروتين، المستندات الرسمية
- سياق الكوروتين والمرسلان
Dispatchers
- تجاوز حد سرعة Android
Job
launch
- الإرجاع والقفز في لغة Kotlin
- تشير CDATA إلى بيانات الأحرف. تعني CDATA أن البيانات بين هذه السلاسل تشتمل على بيانات يمكن تفسيرها كترميز XML، ولكن يجب ألا يتم تفسيرها.
يسرد هذا القسم المهام الدراسية المحتملة للطلاب الذين يعملون من خلال هذا الدرس التطبيقي حول الترميز في إطار دورة تدريبية يُديرها معلِّم. يجب أن ينفِّذ المعلّم ما يلي:
- يمكنك تخصيص واجب منزلي إذا لزم الأمر.
- التواصل مع الطلاب بشأن كيفية إرسال الواجبات المنزلية
- وضع درجات للواجبات المنزلية.
ويمكن للمعلّمين استخدام هذه الاقتراحات بقدر ما يريدون أو بقدر ما يريدون، ويجب عدم التردد في تخصيص أي واجبات منزلية أخرى مناسبة.
إذا كنت تستخدم هذا الدرس التطبيقي بنفسك، يمكنك استخدام هذه الواجبات المنزلية لاختبار معلوماتك.
الإجابة عن هذه الأسئلة
السؤال 1
أيّ مما يلي مزايا من الكوروتين:
- لا تؤدي هذه العناصر إلى الحظر
- ويتم تشغيلها بشكلٍ غير متزامن.
- ويمكن تشغيلها في سلسلة محادثات بخلاف سلسلة المحادثات الرئيسية.
- تجعل التطبيقات تعمل بشكل أسرع دائمًا.
- ويمكنهم استخدام الاستثناءات.
- ويمكن كتابتها وقراءتها كرمز خطي.
السؤال 2
ما هي وظيفة التعليق؟
- دالة عادية بها تعليقات توضيحية للكلمة الرئيسية
suspend
. - دالة يمكن استدعاؤها داخل الكوروتينات.
- أثناء تشغيل وظيفة التعليق، يتم تعليق سلسلة محادثات الاتصال.
- يجب أن تعمل دوال التعليق دائمًا في الخلفية.
السؤال 3
ما الفرق بين حظر سلسلة محادثات وتعليقها؟ ضَع علامة على كل المعلومات الصحيحة.
- عند حظر التنفيذ، لا يمكن تنفيذ أي عمل آخر على سلسلة المحادثات المحظورة.
- وعند تعليق عملية التنفيذ، يمكن لسلسلة المحادثات تنفيذ مهام أخرى أثناء انتظار اكتمال العمل الذي تم تحميله.
- ويتم التعليق بشكل أكثر فعالية لأن سلاسل المحادثات قد لا تنتظر الاستجابة، ولا تفعل أي شيء آخر.
- وسواء كانت محظورة أو معلَّقة، لا تزال عملية التنفيذ في انتظار نتيجة الكوروتين قبل المتابعة.
الانتقال إلى الدرس التالي:
وللحصول على روابط إلى دروس تطبيقية أخرى حول الترميز في هذه الدورة التدريبية، يُرجى الاطّلاع على الصفحة المقصودة لتطبيق الدروس التطبيقية حول الترميز Kotlin Fundamentals.