هذا الدرس التطبيقي حول الترميز هو جزء من دورة "أساسيات Android Kotlin". يمكنك تحقيق أقصى استفادة من هذه الدورة التدريبية إذا اتبعت ترتيب الخطوات في دروس البرمجة. يتم إدراج جميع الدروس التطبيقية حول الترميز الخاصة بالدورة التدريبية في الصفحة المقصودة للدروس التطبيقية حول الترميز في دورة Android Kotlin Fundamentals.
مقدمة
سيتطلّب أي تطبيق Android تقريبًا تنشئه الاتصال بالإنترنت في مرحلة ما. في هذا الدرس التطبيقي حول الترميز والدروس التالية، ستنشئ تطبيقًا يتصل بخدمة ويب لاسترداد البيانات وعرضها. يمكنك أيضًا الاستفادة من المعلومات التي تعلّمتها في دروس الترميز السابقة حول ViewModel وLiveData وRecyclerView.
في هذا الدرس العملي، ستستخدم مكتبات طوّرها المنتدى لإنشاء طبقة الشبكة. يؤدي ذلك إلى تبسيط عملية جلب البيانات والصور بشكل كبير، كما يساعد التطبيق في الالتزام ببعض أفضل ممارسات Android، مثل تحميل الصور في سلسلة محادثات في الخلفية وتخزين الصور التي تم تحميلها مؤقتًا. بالنسبة إلى الأقسام غير المتزامنة أو غير الحظرية في الرمز، مثل التفاعل مع طبقة خدمات الويب، عليك تعديل التطبيق لاستخدام الروتينات الفرعية في Kotlin. عليك أيضًا تعديل واجهة مستخدم التطبيق إذا كان الإنترنت بطيئًا أو غير متاح لإعلام المستخدم بما يحدث.
ما يجب معرفته
- كيفية إنشاء واستخدام الأجزاء
- كيفية التنقّل بين الأجزاء واستخدام
safeArgsلتمرير البيانات بين الأجزاء - كيفية استخدام مكوّنات البنية، بما في ذلك
ViewModelوViewModelProvider.FactoryوLiveDataوعمليات تحويلLiveData - كيفية استخدام الروتينات المشتركة للمهام الطويلة الأمد
ما ستتعرّف عليه
- ما هي خدمة الويب REST؟
- استخدام مكتبة Retrofit للاتصال بخدمة ويب REST على الإنترنت والحصول على ردّ
- استخدام مكتبة Moshi لتحليل استجابة JSON إلى عنصر بيانات
المهام التي ستنفذها
- عدِّل تطبيقًا أوليًا لتقديم طلب إلى واجهة برمجة تطبيقات خدمة الويب والتعامل مع الردّ.
- نفِّذ طبقة شبكة لتطبيقك باستخدام مكتبة Retrofit.
- حلِّل استجابة JSON من خدمة الويب إلى بيانات تطبيقك المباشرة باستخدام مكتبة Moshi.
- استخدِم ميزة توافق Retrofit مع الروتينات المشتركة لتبسيط الرمز.
في هذا الدرس التطبيقي حول الترميز (والدروس التطبيقية حول الترميز التالية)، ستعمل على تطبيق مبتدئ يُسمى MarsRealEstate، ويعرض عقارات معروضة للبيع على كوكب المريخ. يتصل هذا التطبيق بخدمة ويب لاسترداد بيانات العقارات وعرضها، بما في ذلك تفاصيل مثل السعر وما إذا كان العقار متاحًا للبيع أو الإيجار. الصور التي تمثّل كلّ موقع هي صور حقيقية من المريخ تم التقاطها بواسطة مركبات استكشاف المريخ التابعة لوكالة ناسا.

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

.
يتضمّن تصميم تطبيق MarsRealEstate وحدتَين رئيسيتَين:
- مقتطف نظرة عامة يحتوي على شبكة من الصور المصغّرة الخاصة بالمكان المخصّص للاستئجار، تم إنشاؤه باستخدام
RecyclerView. - جزء من عرض تفصيلي يحتوي على معلومات حول كل موقع.

يحتوي التطبيق على ViewModel لكل جزء. في هذا الدرس التطبيقي حول الترميز، ستنشئ طبقة لخدمة الشبكة، وسيتواصل ViewModel مباشرةً مع طبقة الشبكة هذه. يشبه ذلك ما فعلته في دروس سابقة عندما كان ViewModel يتواصل مع قاعدة بيانات Room.
تكون النظرة العامة ViewModel مسؤولة عن إجراء طلب الشبكة للحصول على معلومات العقارات على كوكب المريخ. تحتوي السمة ViewModel على تفاصيل حول قطعة واحدة من العقارات على كوكب المريخ معروضة في جزء التفاصيل. لكل ViewModel، يمكنك استخدام LiveData مع ربط البيانات المتوافق مع مراحل النشاط لتعديل واجهة مستخدم التطبيق عند تغيُّر البيانات.
يمكنك استخدام "مكوّن التنقّل" للتنقّل بين الجزأين وتمرير الموقع المحدّد كمعلَمة.
في هذه المهمة، عليك تنزيل تطبيق البداية وتشغيله لمشروع MarsRealEstate والتعرّف على بنية المشروع.
الخطوة 1: استكشاف الأجزاء والتنقّل
- نزِّل تطبيق MarsRealEstate المبدئي وافتحه في "استوديو Android".
- فحص
app/java/MainActivity.ktيستخدم التطبيق أجزاءً لكلا الشاشتين، لذا فإنّ المهمة الوحيدة للنشاط هي تحميل تنسيق النشاط. - فحص
app/res/layout/activity_main.xmlتنسيق النشاط هو المضيف للجزأين، ويتم تحديده في ملف التنقّل. ينشئ هذا التصميم مثيلاً منNavHostFragmentووحدة التحكّم في التنقّل المرتبطة به باستخدام موردnav_graph. - فتح "
app/res/navigation/nav_graph.xml" يمكنك هنا الاطّلاع على علاقة التنقّل بين جزأَي الصفحة. يشير الرسم البياني للتنقّلStartDestinationإلىoverviewFragment، لذا يتم إنشاء جزء النظرة العامة عند تشغيل التطبيق.
الخطوة 2: استكشاف ملفات مصدر Kotlinوربط البيانات
- في لوحة المشروع، وسِّع التطبيق > java. لاحظ أنّ تطبيق MarsRealEstate يحتوي على ثلاثة مجلدات حِزم:
detailوnetworkوoverview. تتطابق هذه الملفات مع المكوّنات الرئيسية الثلاثة لتطبيقك: اللقطات العامة ولقطات التفاصيل، ورمز طبقة الشبكة.
- فتح "
app/java/overview/OverviewFragment.kt" يتمّ تهيئةOverviewFragmentبشكلٍ غير مباشر، ما يعني أنّه يتمّ إنشاءOverviewViewModelفي المرّة الأولى التي يتمّ فيها استخدامه.OverviewViewModel - افحص طريقة
onCreateView(). تؤدي هذه الطريقة إلى تضخيم تنسيقfragment_overviewباستخدام ربط البيانات، وضبط مالك دورة حياة الربط على نفسه (this)، وضبط المتغيرviewModelفي الكائنbindingعليه. بما أنّنا ضبطنا مالك عمر التطبيق، سيتم تلقائيًا رصد أيLiveDataمستخدَم في ربط البيانات لرصد أي تغييرات، وسيتم تعديل واجهة المستخدم وفقًا لذلك. - فتح "
app/java/overview/OverviewViewModel" بما أنّ الردّ هوLiveDataوقد ضبطنا عمر المتغير المرتبط، سيؤدي أي تغيير فيه إلى تعديل واجهة مستخدم التطبيق. - افحصوا كتلة
init. عند إنشاءViewModel، يتم استدعاء الطريقةgetMarsRealEstateProperties(). - افحص طريقة
getMarsRealEstateProperties(). في هذا التطبيق الأوّلي، تحتوي هذه الطريقة على ردّ عنصر نائب. الهدف من هذا الدرس العملي هو تعديل الردLiveDataداخلViewModelباستخدام بيانات حقيقية تحصل عليها من الإنترنت. - فتح "
app/res/layout/fragment_overview.xml" هذا هو التصميم الخاص بقطعة النظرة العامة التي ستعمل عليها في هذا الدرس التطبيقي، ويتضمّن ربط البيانات بنموذج العرض. يتم استيرادOverviewViewModelثم ربط الردّ منViewModelبـTextView. في دروس لاحقة، ستستبدل طريقة عرض النص بشبكة من الصور فيRecyclerView. - يمكنك تجميع التطبيق وتشغيله. كل ما يظهر لك في الإصدار الحالي من هذا التطبيق هو الردّ الأوّلي: "Set the Mars API Response here!"
يتم تخزين بيانات العقارات على كوكب المريخ على خادم ويب كخدمة ويب REST. يتم إنشاء خدمات الويب التي تستخدم بنية REST باستخدام بروتوكولات ومكوّنات الويب العادية.
يمكنك تقديم طلب إلى خدمة ويب بطريقة موحّدة من خلال معرّفات URI. إنّ عنوان URL المألوف على الويب هو في الواقع نوع من معرّف الموارد الموحّد (URI)، ويتم استخدام كليهما بالتبادل في هذه الدورة التدريبية. على سبيل المثال، في تطبيق هذا الدرس، يمكنك استرداد جميع البيانات من الخادم التالي:
https://android-kotlin-fun-mars-server.appspot.com
إذا كتبت عنوان URL التالي في المتصفّح، ستحصل على قائمة بجميع العقارات المتاحة على سطح المريخ.
https://android-kotlin-fun-mars-server.appspot.com/realestate
عادةً ما يتم تنسيق الردّ من خدمة الويب بتنسيق JSON، وهو تنسيق تبادل لتمثيل البيانات المنظَّمة. يمكنك التعرّف على المزيد حول JSON في المهمة التالية، ولكن التفسير الموجز هو أنّ كائن JSON هو مجموعة من أزواج المفاتيح والقيم، ويُطلق عليه أحيانًا قاموس أو خريطة تجزئة أو مصفوفة ترابطية. مجموعة من عناصر JSON هي مصفوفة JSON، وهي المصفوفة التي تتلقّاها كاستجابة من خدمة ويب.
ولإدخال هذه البيانات إلى التطبيق، يجب أن ينشئ تطبيقك اتصالاً بالشبكة ويتواصل مع هذا الخادم، ثم يتلقّى بيانات الرد ويحلّلها إلى تنسيق يمكن للتطبيق استخدامه. في هذا الدرس التطبيقي، ستستخدم مكتبة برنامج REST تسمى Retrofit لإجراء عملية الربط هذه.
الخطوة 1: إضافة تبعيات Retrofit إلى Gradle
- افتح ملف build.gradle (وحدة التطبيق).
- في القسم
dependencies، أضِف الأسطر التالية لمكتبات Retrofit:
implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"
يُرجى العِلم أنّه يتم تحديد أرقام الإصدارات بشكل منفصل في ملف Gradle الخاص بالمشروع. تتعلّق التبعية الأولى بمكتبة Retrofit 2 نفسها، بينما تتعلّق التبعية الثانية بمحوّل Retrofit العددي. يتيح هذا المحوّل لـ Retrofit عرض نتيجة JSON كـ String. تعمل المكتبتان معًا.
- انقر على المزامنة الآن لإعادة إنشاء المشروع باستخدام التبعيات الجديدة.
الخطوة 2: تنفيذ MarsApiService
تنشئ Retrofit واجهة برمجة تطبيقات شبكة للتطبيق استنادًا إلى المحتوى من خدمة الويب. وهي تستردّ البيانات من خدمة الويب وتوجّهها من خلال مكتبة محوّل منفصلة تعرف كيفية فك ترميز البيانات وعرضها في شكل عناصر مفيدة. تتضمّن Retrofit إمكانية استخدام تنسيقات بيانات الويب الشائعة، مثل XML وJSON. توفّر Retrofit في النهاية معظم طبقة الشبكة، بما في ذلك التفاصيل المهمة، مثل تنفيذ الطلبات في سلاسل التعليمات البرمجية في الخلفية.
يحتوي الصف MarsApiService على طبقة الشبكة للتطبيق، أي أنّ هذه هي واجهة برمجة التطبيقات التي سيستخدمها ViewModel للتواصل مع خدمة الويب. هذا هو الصف الذي ستنفّذ فيه واجهة برمجة التطبيقات لخدمة Retrofit.
- فتح "
app/java/network/MarsApiService.kt" يحتوي الملف حاليًا على عنصر واحد فقط: ثابت لعنوان URL الأساسي لخدمة الويب.
private const val BASE_URL =
"https://android-kotlin-fun-mars-server.appspot.com"- أسفل هذا الثابت مباشرةً، استخدِم أداة إنشاء Retrofit لإنشاء عنصر Retrofit. استورِد
retrofit2.Retrofitوretrofit2.converter.scalars.ScalarsConverterFactoryعند الطلب.
private val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl(BASE_URL)
.build()
يحتاج Retrofit إلى توفُّر عنصرَين على الأقل لإنشاء واجهة برمجة تطبيقات لخدمات الويب: معرّف الموارد المنتظم الأساسي لخدمة الويب، ومصنع المحوّلات. يخبر المحوّل Retrofit بما يجب فعله بالبيانات التي يتم استلامها من خدمة الويب. في هذه الحالة، تريد أن يجلب Retrofit استجابة JSON من خدمة الويب، ويعرضها كـ String. يحتوي Retrofit على ScalarsConverter يتوافق مع السلاسل وأنواع البيانات الأساسية الأخرى، لذا يمكنك استدعاء addConverterFactory() في أداة الإنشاء باستخدام مثيل ScalarsConverterFactory. أخيرًا، يمكنك استدعاء build() لإنشاء عنصر Retrofit.
- أسفل استدعاء أداة إنشاء Retrofit مباشرةً، حدِّد واجهة تحدّد كيفية تواصل Retrofit مع خادم الويب باستخدام طلبات HTTP. استورِد
retrofit2.http.GETوretrofit2.Callعند الطلب.
interface MarsApiService {
@GET("realestate")
fun getProperties():
Call<String>
}في الوقت الحالي، الهدف هو الحصول على سلسلة استجابة JSON من خدمة الويب، ولا تحتاج إلا إلى طريقة واحدة لإجراء ذلك: getProperties(). لتحديد الإجراء الذي يجب أن تنفّذه هذه الطريقة في Retrofit، استخدِم التعليق التوضيحي @GET وحدِّد المسار أو نقطة النهاية لطريقة خدمة الويب هذه. في هذه الحالة، يُطلق على نقطة النهاية اسم realestate. عند استدعاء الطريقة getProperties()، يضيف Retrofit نقطة النهاية realestate إلى عنوان URL الأساسي (الذي حدّدته في أداة إنشاء Retrofit)، وينشئ الكائن Call. يُستخدَم عنصر Call هذا لبدء الطلب.
- أسفل واجهة
MarsApiService، حدِّد عنصرًا عامًا باسمMarsApiلتهيئة خدمة Retrofit.
object MarsApi {
val retrofitService : MarsApiService by lazy {
retrofit.create(MarsApiService::class.java) }
}تنشئ طريقة Retrofit create() خدمة Retrofit نفسها باستخدام الواجهة MarsApiService. بما أنّ هذه العملية مكلفة، ولا يحتاج التطبيق إلا إلى مثيل واحد من خدمة Retrofit، يمكنك عرض الخدمة لبقية التطبيق باستخدام عنصر عام يُسمى MarsApi، ثم تهيئة خدمة Retrofit بشكل غير مباشر. بعد اكتمال عملية الإعداد، سيحصل تطبيقك في كل مرة يطلب فيها MarsApi.retrofitService على عنصر Retrofit فردي ينفّذ MarsApiService.
الخطوة 3: استدعاء خدمة الويب في OverviewViewModel
- فتح "
app/java/overview/OverviewViewModel.kt" انتقِل إلى طريقةgetMarsRealEstateProperties().
private fun getMarsRealEstateProperties() {
_response.value = "Set the Mars API Response here!"
}هذه هي الطريقة التي ستتصل فيها بخدمة Retrofit وتعالج سلسلة JSON التي تم إرجاعها. في الوقت الحالي، لا يتوفّر سوى سلسلة عناصر نائبة للردّ.
- احذف سطر العنصر النائب الذي يضبط الرد على "Set the Mars API Response here!"
- داخل
getMarsRealEstateProperties()، أضِف الرمز الموضّح أدناه. استورِدretrofit2.Callbackوcom.example.android.marsrealestate.network.MarsApiعند الطلب.
تعرض الطريقةMarsApi.retrofitService.getProperties()الكائنCall. بعد ذلك، يمكنك استدعاءenqueue()على هذا العنصر لبدء طلب الشبكة في سلسلة محادثات في الخلفية.
MarsApi.retrofitService.getProperties().enqueue(
object: Callback<String> {
})- انقر على الكلمة
objectالتي يظهر تحتها خط أحمر. اختَر الرمز > تنفيذ الطرق. اختَر كلاً منonResponse()وonFailure()من القائمة.
يضيف "استوديو Android" الرمز مع ملاحظات TODO في كل طريقة:
override fun onFailure(call: Call<String>, t: Throwable) {
TODO("not implemented")
}
override fun onResponse(call: Call<String>,
response: Response<String>) {
TODO("not implemented")
}- في
onFailure()، احذف TODO واضبط_responseعلى رسالة خطأ، كما هو موضّح أدناه. _responseهي سلسلةLiveDataتحدّد المحتوى المعروض في عرض النص. يجب أن تعدّل كل حالة_responseLiveData.
يتم استدعاء معاودة الاتصالonFailure()عندما يتعذّر الرد من خدمة الويب. بالنسبة إلى هذا الردّ، اضبط حالة_responseعلى"Failure: "مدمجة مع الرسالة من الوسيطةThrowable.
override fun onFailure(call: Call<String>, t: Throwable) {
_response.value = "Failure: " + t.message
}- في
onResponse()، احذف TODO واضبط_responseعلى نص الردّ. يتم استدعاء الدالةonResponse()عند نجاح الطلب وإرجاع خدمة الويب لردّ.
override fun onResponse(call: Call<String>,
response: Response<String>) {
_response.value = response.body()
}الخطوة 4: تحديد إذن الوصول إلى الإنترنت
- جمِّع تطبيق MarsRealEstate وشغِّله. لاحظ أنّ التطبيق يُغلق على الفور مع ظهور خطأ.
- انقر على علامة التبويب Logcat في "استوديو Android" ولاحِظ الخطأ في السجلّ الذي يبدأ بسطر على النحو التالي:
Process: com.example.android.marsrealestate, PID: 10646 java.lang.SecurityException: Permission denied (missing INTERNET permission?)
تخبرك رسالة الخطأ بأنّ تطبيقك قد لا يتضمّن إذن INTERNET. يؤدي الاتصال بالإنترنت إلى ظهور مخاوف أمنية، ولهذا السبب لا تتوفّر إمكانية الاتصال بالإنترنت في التطبيقات تلقائيًا. عليك إخبار نظام التشغيل Android صراحةً بأنّ التطبيق يحتاج إلى الوصول إلى الإنترنت.
- فتح "
app/manifests/AndroidManifest.xml" أضِف هذا السطر قبل العلامة<application>مباشرةً:
<uses-permission android:name="android.permission.INTERNET" />- أعِد تجميع التطبيق وتشغيله. إذا كان كل شيء يعمل بشكل صحيح مع اتصالك بالإنترنت، سيظهر لك نص JSON يحتوي على بيانات "موقع المريخ".

- انقر على زر رجوع في جهازك أو المحاكي لإغلاق التطبيق.
- ضَع جهازك أو المحاكي في وضع الطيران، ثم أعِد فتح التطبيق من قائمة "التطبيقات الحديثة"، أو أعِد تشغيل التطبيق من Android Studio.

- أوقِف وضع الطيران مرة أخرى.
أنت الآن تتلقّى استجابة JSON من خدمة الويب الخاصة بكوكب المريخ، وهذه بداية رائعة. لكنّ ما تحتاجه حقًا هو عناصر Kotlin، وليس سلسلة JSON كبيرة. تتوفّر مكتبة باسم Moshi، وهي أداة تحليل JSON لنظام Android تحوّل سلسلة JSON إلى عناصر Kotlin. يحتوي Retrofit على محوّل يعمل مع Moshi، لذا فهو مكتبة رائعة لأغراضك هنا.
في هذه المهمة، ستستخدم مكتبة Moshi مع Retrofit لتحليل استجابة JSON من خدمة الويب إلى كائنات Kotlin مفيدة خاصة بـ Mars Property. تغيّر التطبيق بحيث يعرض عدد "مواقع المريخ" التي تم إرجاعها بدلاً من عرض JSON الأوّلي.
الخطوة 1: إضافة تبعيات مكتبة Moshi
- افتح ملف build.gradle (وحدة التطبيق).
- في قسم التبعيات، أضِف الرمز المعروض أدناه لتضمين تبعيات Moshi. كما هو الحال مع Retrofit، يتم تحديد
$version_moshiبشكل منفصل في ملف Gradle على مستوى المشروع. تضيف هذه التبعيات إمكانية استخدام مكتبة Moshi JSON الأساسية، وإمكانية استخدام Moshi مع Kotlin.
implementation "com.squareup.moshi:moshi:$version_moshi"
implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"- ابحث عن سطر محوّل Retrofit العددي في الحزمة
dependencies:
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"- غيِّر هذا السطر لاستخدام
converter-moshi:
implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"- انقر على المزامنة الآن لإعادة إنشاء المشروع باستخدام التبعيات الجديدة.
الخطوة 2: تنفيذ فئة بيانات MarsProperty
يبدو نموذج إدخال استجابة JSON التي تتلقّاها من خدمة الويب على النحو التالي:
[{"price":450000,
"id":"424906",
"type":"rent",
"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"},
...]استجابة JSON الموضّحة أعلاه هي عبارة عن مصفوفة، ويُشار إليها بالأقواس المربّعة. تحتوي المصفوفة على عناصر JSON محاطة بأقواس معقوفة. يحتوي كل عنصر على مجموعة من أزواج الاسم والقيمة مفصولة بنقطتين رأسيتين. تكون الأسماء محاطة بعلامات اقتباس. يمكن أن تكون القيم أرقامًا أو سلاسل، كما أنّ السلاسل محاطة أيضًا بعلامات اقتباس. على سبيل المثال، قيمة price لهذا العقار هي 450,000 دولار أمريكي، وimg_src هو عنوان URL، وهو موقع ملف الصورة على الخادم.
في المثال أعلاه، لاحظ أنّ كل إدخال لخاصية Mars يتضمّن أزواج مفاتيح وقيم JSON التالية:
price: تمثّل هذه السمة سعر العقار على كوكب المريخ كرقم.-
id: معرّف الموقع، كسلسلة type: إما"rent"أو"buy"img_src: عنوان URL الخاص بالصورة كسلسلة
تحلّل مكتبة Moshi بيانات JSON هذه وتحوّلها إلى عناصر Kotlin. ولإجراء ذلك، يجب أن يتضمّن فئة بيانات Kotlin لتخزين النتائج التي تم تحليلها، لذا فإنّ الخطوة التالية هي إنشاء هذه الفئة.
- فتح "
app/java/network/MarsProperty.kt" - استبدِل تعريف الفئة
MarsPropertyالحالي بالرمز التالي:
data class MarsProperty(
val id: String, val img_src: String,
val type: String,
val price: Double
)لاحظ أنّ كل متغيّر في الفئة MarsProperty يتوافق مع اسم مفتاح في عنصر JSON. لمطابقة الأنواع في JSON، يمكنك استخدام عناصر String لجميع القيم باستثناء price، وهو Double. يمكن استخدام Double لتمثيل أي رقم JSON.
عندما يحلّل Moshi ملف JSON، يطابق المفاتيح حسب الاسم ويملأ عناصر البيانات بالقيم المناسبة.
- استبدِل السطر الخاص بالمفتاح
img_srcبالسطر الموضّح أدناه. استورِدcom.squareup.moshi.Jsonعند الطلب.
@Json(name = "img_src") val imgSrcUrl: String,في بعض الأحيان، يمكن أن تؤدي أسماء المفاتيح في استجابة JSON إلى إنشاء خصائص Kotlin مربكة، أو قد لا تتطابق مع أسلوب الترميز الخاص بك، على سبيل المثال، يستخدم المفتاح img_src في ملف JSON شرطة سفلية، بينما تستخدم خصائص Kotlin عادةً أحرفًا كبيرة وصغيرة ("camel case").
لاستخدام أسماء متغيّرات في فئة البيانات تختلف عن أسماء المفاتيح في استجابة JSON، استخدِم التعليق التوضيحي @Json. في هذا المثال، اسم المتغيّر في فئة البيانات هو imgSrcUrl. يتم ربط المتغيّر بسمة JSON img_src باستخدام @Json(name = "img_src").
الخطوة 3: تعديل MarsApiService وOverviewViewModel
بعد إعداد فئة البيانات MarsProperty، يمكنك الآن تعديل واجهة برمجة التطبيقات للشبكة وViewModel لتضمين بيانات Moshi.
- فتح "
network/MarsApiService.kt" قد تظهر لك أخطاء missing-class فيScalarsConverterFactory. ويرجع ذلك إلى تغيير التبعية في Retrofit الذي أجريته في الخطوة 1. عليك إصلاح هذه الأخطاء قريبًا. - في أعلى الملف، قبل أداة إنشاء Retrofit مباشرةً، أضِف الرمز التالي لإنشاء مثيل Moshi. استورِد
com.squareup.moshi.Moshiوcom.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactoryعند الطلب.
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()على غرار ما فعلته مع Retrofit، يمكنك هنا إنشاء كائن moshi باستخدام أداة إنشاء Moshi. لكي تعمل تعليقات Moshi التوضيحية بشكلٍ صحيح مع Kotlin، أضِف KotlinJsonAdapterFactory، ثم استدعِ build().
- غيِّر أداة إنشاء Retrofit لاستخدام
MoshiConverterFactoryبدلاً منScalarConverterFactory، وأدخِل مثيلmoshiالذي أنشأته للتو. استيرادretrofit2.converter.moshi.MoshiConverterFactoryعند الطلب
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()- احذف عملية الاستيراد الخاصة بـ
ScalarConverterFactoryأيضًا.
الرمز المراد حذفه:
import retrofit2.converter.scalars.ScalarsConverterFactory- عدِّل واجهة
MarsApiServiceلكي تعرض Retrofit قائمة بكائناتMarsPropertyبدلاً من عرضCall<String>.
.
interface MarsApiService {
@GET("realestate")
fun getProperties():
Call<List<MarsProperty>>
}- فتح "
OverviewViewModel.kt" انتقِل إلى أسفل الصفحة وصولاً إلى المكالمة التي تم إجراؤها إلىgetProperties().enqueue()في الطريقةgetMarsRealEstateProperties(). - غيِّر الوسيط إلى
enqueue()منCallback<String>إلىCallback<List<MarsProperty>>. استورِدcom.example.android.marsrealestate.network.MarsPropertyعند الطلب.
MarsApi.retrofitService.getProperties().enqueue(
object: Callback<List<MarsProperty>> {- في
onFailure()، غيِّر الوسيط منCall<String>إلىCall<List<MarsProperty>>:
override fun onFailure(call: Call<List<MarsProperty>>, t: Throwable) {- أدخِل التغيير نفسه على كلتا الوسيطتين في
onResponse():
override fun onResponse(call: Call<List<MarsProperty>>,
response: Response<List<MarsProperty>>) {- في نص
onResponse()، استبدِل عملية التخصيص الحالية إلى_response.valueبعملية التخصيص الموضّحة أدناه. بما أنّresponse.body()أصبحت الآن قائمة بكائناتMarsProperty، فإنّ حجم هذه القائمة هو عدد السمات التي تم تحليلها. تعرض رسالة الرد هذه عدد السمات التالية:
_response.value =
"Success: ${response.body()?.size} Mars properties retrieved"- تأكَّد من إيقاف وضع الطائرة. جمِّع التطبيق وشغِّله. من المفترض أن تعرض الرسالة هذه المرة عدد السمات التي تم إرجاعها من خدمة الويب:

الآن، يتم تشغيل خدمة Retrofit API، ولكنها تستخدم دالة رد الاتصال مع طريقتَي رد اتصال كان عليك تنفيذهما. تتعامل إحدى الطريقتين مع النجاح وتتعامل الأخرى مع الخطأ، ويبلغ ناتج الخطأ عن الاستثناءات. سيكون الرمز البرمجي أكثر كفاءة وأسهل في القراءة إذا كان بإمكانك استخدام إجراءات فرعية مع معالجة الأخطاء، بدلاً من استخدام عمليات رد الاتصال. لحسن الحظ، تتضمّن Retrofit مكتبة تدمج الروتينات المشتركة.
في هذه المهمة، ستحوّل خدمة الشبكة وViewModel لاستخدام الروتينات الفرعية.
الخطوة 1: إضافة التبعيات الخاصة بالروتينات المشتركة
- افتح ملف build.gradle (وحدة التطبيق).
- في قسم التبعيات، أضِف دعمًا لمكتبات إجراءات Kotlin الفرعية الأساسية ومكتبة إجراءات Retrofit الفرعية:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version_kotlin_coroutines" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version_kotlin_coroutines" implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$version_retrofit_coroutines_adapter"
- انقر على المزامنة الآن لإعادة إنشاء المشروع باستخدام التبعيات الجديدة.
الخطوة 2: تعديل MarsApiService وOverviewViewModel
- في
MarsApiService.kt، عدِّل أداة إنشاء Retrofit لاستخدامCoroutineCallAdapterFactory. يبدو المنشئ الكامل الآن على النحو التالي:
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.baseUrl(BASE_URL)
.build()تضيف أدوات ربط طلبات البيانات القدرة إلى Retrofit لإنشاء واجهات برمجة تطبيقات تعرض شيئًا آخر غير فئة Call التلقائية. في هذه الحالة، يتيح لنا CoroutineCallAdapterFactory استبدال العنصر Call الذي تعرضه الدالة getProperties() بالعنصر Deferred بدلاً من ذلك.
- في طريقة
getProperties()، غيِّرCall<List<MarsProperty>>إلىDeferred<List<MarsProperty>>. استورِدkotlinx.coroutines.Deferredعند الطلب. تبدو طريقةgetProperties()الكاملة على النحو التالي:
@GET("realestate")
fun getProperties():
Deferred<List<MarsProperty>>تحدّد واجهة Deferred مهمة روتينية مشتركة تعرض قيمة نتيجة (ترث Deferred من Job). تتضمّن واجهة Deferred طريقة تُسمى await()، ما يؤدي إلى انتظار الرمز بدون حظر إلى أن تصبح القيمة جاهزة، ثم يتم عرض هذه القيمة.
- فتح "
OverviewViewModel.kt" قبل كتلةinitمباشرةً، أضِف مهمة روتينية مشتركة:
private var viewModelJob = Job()- أنشِئ نطاق روتين فرعي لهذه المهمة الجديدة باستخدام أداة الإرسال الرئيسية:
private val coroutineScope = CoroutineScope(
viewModelJob + Dispatchers.Main )يستخدم برنامج معالجة Dispatchers.Main سلسلة التعليمات الخاصة بواجهة المستخدم في عمله. بما أنّ Retrofit ينفّذ جميع عملياته في سلسلة محادثات في الخلفية، ليس هناك أي سبب لاستخدام أي سلسلة محادثات أخرى للنطاق. يتيح لك ذلك تعديل قيمة MutableLiveData بسهولة عند الحصول على نتيجة.
- احذف كل الرمز البرمجي داخل
getMarsRealEstateProperties(). ستستخدم هنا إجراءات روتينية مشتركة بدلاً من استدعاءenqueue()وعمليات معاودة الاتصالonFailure()وonResponse(). - داخل
getMarsRealEstateProperties()، شغِّل الروتين الفرعي:
coroutineScope.launch {
}
لاستخدام العنصر Deferred الذي تعرضه Retrofit لمهمة الشبكة، عليك أن تكون داخل روتين فرعي، لذا يمكنك هنا تشغيل الروتين الفرعي الذي أنشأته للتو. سيظلّ بإمكانك تنفيذ الرمز البرمجي في سلسلة التعليمات الرئيسية، ولكن ستتيح الآن للروتينات الفرعية إدارة التزامن.
- داخل مربّع التشغيل، استدعِ الدالة
getProperties()على العنصرretrofitService:
var getPropertiesDeferred = MarsApi.retrofitService.getProperties()يؤدي طلب getProperties() من خدمة MarsApi إلى إنشاء مكالمة الشبكة وبدءها في سلسلة محادثات في الخلفية، مع عرض عنصر Deferred لهذه المهمة.
- داخل مجموعة التشغيل أيضًا، أضِف مجموعة
try/catchللتعامل مع الاستثناءات:
try {
} catch (e: Exception) {
}- داخل الحظر
try {}، استدعِawait()على العنصرDeferred:
var listResult = getPropertiesDeferred.await()يؤدي استدعاء await() على العنصر Deferred إلى عرض النتيجة من طلب الشبكة عندما تكون القيمة جاهزة. الطريقة await() لا تحظر، لذا تسترد خدمة Mars API البيانات من الشبكة بدون حظر سلسلة المحادثات الحالية، وهذا مهم لأنّنا في نطاق سلسلة محادثات واجهة المستخدم. بعد اكتمال المهمة، يواصل الرمز التنفيذ من حيث توقّف، وذلك داخل try {} حتى تتمكّن من رصد الاستثناءات.
- داخل الحظر
try {}أيضًا، بعد الطريقةawait()، عدِّل رسالة الردّ للردّ الناجح:
_response.value =
"Success: ${listResult.size} Mars properties retrieved"- داخل الحظر
catch {}، عالِج الردّ الذي يشير إلى حدوث خطأ:
_response.value = "Failure: ${e.message}"
تبدو طريقة getMarsRealEstateProperties() الكاملة الآن على النحو التالي:
private fun getMarsRealEstateProperties() {
coroutineScope.launch {
var getPropertiesDeferred =
MarsApi.retrofitService.getProperties()
try {
_response.value =
"Success: ${listResult.size} Mars properties retrieved"
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
}
}- في أسفل الفئة، أضِف دالة ردّ
onCleared()باستخدام الرمز التالي:
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}يجب إيقاف تحميل البيانات عند إيقاف ViewModel، لأنّ OverviewFragment الذي يستخدم ViewModel هذا سيختفي. لإيقاف التحميل عند إيقاف ViewModel، عليك إلغاء onCleared() لإلغاء المهمة.
- جمِّع التطبيق وشغِّله. ستحصل على النتيجة نفسها هذه المرة كما في المهمة السابقة (تقرير بعدد السمات)، ولكن باستخدام رمز برمجي أكثر وضوحًا ومعالجة الأخطاء.
مشروع "استوديو Android": MarsRealEstateNetwork
خدمات الويب REST
- خدمة الويب هي خدمة على الإنترنت تتيح لتطبيقك إرسال الطلبات واسترداد البيانات.
- تستخدم خدمات الويب الشائعة بنية REST. تُعرف خدمات الويب التي توفّر بنية REST باسم خدمات RESTful. يتم إنشاء خدمات الويب المتوافقة مع REST باستخدام بروتوكولات ومكوّنات الويب العادية.
- يمكنك تقديم طلب إلى خدمة ويب REST بطريقة موحّدة، وذلك من خلال معرّفات URI.
- لاستخدام خدمة ويب، يجب أن ينشئ التطبيق اتصالاً بالشبكة ويتواصل مع الخدمة. بعد ذلك، يجب أن يتلقّى التطبيق بيانات الرد ويحلّلها إلى تنسيق يمكن للتطبيق استخدامه.
- Retrofit هي مكتبة عميل تتيح لتطبيقك إرسال طلبات إلى خدمة ويب REST.
- استخدِم المحوّلات لإخبار Retrofit بما يجب فعله بالبيانات التي يتم إرسالها إلى خدمة الويب وتلك التي يتم تلقّيها من خدمة الويب. على سبيل المثال، يعامل المحوّل
ScalarsConverterبيانات خدمة الويب على أنّهاStringأو نوع أساسي آخر. - للسماح لتطبيقك بالاتصال بالإنترنت، أضِف الإذن
"android.permission.INTERNET"في بيان Android.
تحليل JSON
- غالبًا ما يتم تنسيق الردّ من خدمة الويب بتنسيق JSON، وهو تنسيق تبادل شائع لتمثيل البيانات المنظَّمة.
- عنصر JSON هو مجموعة من أزواج المفاتيح والقيم. يُطلق على هذه المجموعة أحيانًا اسم قاموس أو خريطة تجزئة أو مصفوفة ترابطية.
- مجموعة من عناصر JSON هي مصفوفة JSON. ستتلقّى مصفوفة JSON كاستجابة من خدمة ويب.
- تكون المفاتيح في زوج المفتاح/القيمة محاطة بعلامات اقتباس. يمكن أن تكون القيم أرقامًا أو سلاسل. تكون السلاسل محاطة أيضًا بعلامات اقتباس.
- مكتبة Moshi هي أداة تحليل JSON في Android تحوّل سلسلة JSON إلى عناصر Kotlin. يحتوي Retrofit على محوّل متوافق مع Moshi.
- تطابق مكتبة Moshi المفاتيح في ردّ JSON مع السمات في عنصر البيانات الذي يحمل الاسم نفسه.
- لاستخدام اسم سمة مختلف لمفتاح، أضِف تعليقًا توضيحيًا إلى هذه السمة باستخدام التعليق التوضيحي
@Jsonواسم مفتاح JSON.
Retrofit و الروتينات الفرعية
- تتيح محوّلات الاتصال لـ Retrofit إنشاء واجهات برمجة تطبيقات تعرض شيئًا آخر غير فئة
Callالتلقائية. استخدِم الفئةCoroutineCallAdapterFactoryلاستبدالCallبروتين فرعيDeferred. - استخدِم طريقة
await()في عنصرDeferredلجعل رمز الروتين الفرعي ينتظر بدون حظر إلى أن تصبح القيمة جاهزة، ثم يتم عرض القيمة.
دورة Udacity التدريبية:
مستندات مطوّري تطبيقات Android:
مستندات Kotlin:
- درس تطبيقي حول الترميز باستخدام الكوروتينات
- الروتينات المشتركة، المستندات الرسمية
- سياق الروتين الفرعي وعوامل الإرسال
غير ذلك:
يسرد هذا القسم مهامًا منزلية محتملة للطلاب الذين يعملون على هذا الدرس التطبيقي العملي كجزء من دورة تدريبية يقودها مدرّب. على المعلّم تنفيذ ما يلي:
- حدِّد واجبًا منزليًا إذا لزم الأمر.
- توضيح كيفية إرسال الواجبات المنزلية للطلاب
- وضع درجات للواجبات المنزلية
يمكن للمدرّبين استخدام هذه الاقتراحات بالقدر الذي يريدونه، ويجب ألا يترددوا في تكليف الطلاب بأي واجبات منزلية أخرى يرونها مناسبة.
إذا كنت تعمل على هذا الدرس العملي بنفسك، يمكنك استخدام مهام الواجب المنزلي هذه لاختبار معلوماتك.
الإجابة عن هذه الأسئلة
السؤال 1
ما هما الشيئان الأساسيان اللذان تحتاج إليهما مكتبة Retrofit لإنشاء واجهة برمجة تطبيقات لخدمات الويب؟
▢ عنوان URI الأساسي لخدمة الويب، وطلب بحث GET
▢ معرّف الموارد المنتظم الأساسي لخدمة الويب، ومصنع محوّل
▢ اتصال بالشبكة بخدمة الويب ورمز مميز للمصادقة
▢ مصنع محوّل ومحلّل للردّ
السؤال 2
ما هو الغرض من مكتبة Moshi؟
▢ لاسترداد البيانات من خدمة ويب
▢ للتفاعل مع Retrofit لتقديم طلب خدمة ويب
▢ لتحليل استجابة JSON من خدمة ويب إلى عناصر بيانات Kotlin
▢ لإعادة تسمية عناصر Kotlin لتتطابق مع المفاتيح في استجابة JSON
السؤال 3
ما الغرض من استخدام مهايئات طلبات Retrofit؟
▢ تتيح هذه المكتبة استخدام الروتينات الفرعية في Retrofit.
▢ تحويل استجابة خدمة الويب إلى عناصر بيانات Kotlin
▢ تحويل طلب Retrofit إلى طلب خدمة ويب
▢ يتيح إمكانية إرجاع قيمة أخرى غير القيمة التلقائية Call في فئة Retrofit.
ابدأ الدرس التالي:
للحصول على روابط تؤدي إلى دروس تطبيقية أخرى في هذه الدورة التدريبية، اطّلِع على الصفحة المقصودة الخاصة بالدروس التطبيقية حول أساسيات Android Kotlin.