استدعاء خدمة ويب باستخدام Android Architecture Component
تركّز هذه المقالة على استدعاء خدمة ويب (web service) باستخدام Android Architecture Component.

مقدمة
كان Android Architecture Component من أكثر المواضيع التي أثارت اهتمامي (وأظن أنني لست الوحيد، بل كثير من مطوّري Android). توجد اليوم بنى برمجية كثيرة مثل MVP وMVVM وغيرها. لكن Google قدّمت هذه المرة دليلًا رسميًا يساعدكم على تصميم بنية تطبيق Android، ولذلك يستحق التجربة فعلًا. سأركّز في هذه المقالة على استدعاء خدمة ويب (web service) باستخدام هذه البنية. الكود المصدري لهذا المشروع موجود على حسابي في Github. قبل البدء **تركّز هذه المقالة على استدعاء خدمة ويب باستخدام Android Architecture Component. ولن تتناول أساسيات Kotlin أو المكتبات الأخرى مثل Dagger وRetrofit _. _
كيف تبدو هذه البنية؟
الصورة أعلاه مأخوذة من موقع Android الرسمي. وأود تقسيمها إلى طبقتين.
طبقة البيانات (Data Layer)
- Repository: يتواصل مع مصدر البيانات المحلي (قاعدة البيانات) ومصدر البيانات البعيد (خدمة الويب)
- مصدر البيانات البعيد: يتصل بخدمة الويب
طبقة العرض (Presentation Layer)
- ViewModel: يتواصل مع الـ Repository ثم يحدّث البيانات في الواجهة (fragment/activity) تبعًا لذلك
- Fragment/Activity: كل ما يخص الواجهة (تجهيزها وتحديثها)
التطبيق التجريبي
يجلب التطبيق التجريبي البيانات من https://swapi.co، ويعرض قائمة بالأنواع (species) في سلسلة Star War.
سأتناول في هذه المقالة أيضًا الإجراءات الشائعة عند التعامل مع خدمة الويب، ومنها الحصول على النتيجة ومعالجة الأخطاء وعرض واجهة التحميل. يمكنكم نسخ الكود من هنا ومتابعته مع هذه المقالة.
طبقة البيانات
مصدر البيانات البعيد
نستخدم Retrofit مصدرًا للبيانات البعيدة للاتصال بخدمة الويب. الكود أدناه هو الـ interface الخاص بالـ API. إذا كنتم تستخدمون Retrofit، فهو مألوف لكم بالتأكيد.
حزمة الكود المصدري_: boonya.ben.callingwebservice.api _
interface Swapi {
@GET("species/")
fun getSpecies(): Call<SpeciesList>
}
يحتوي على endpoint واحد فقط من أجل التبسيط. Repository
هنا يحدث استدعاء خدمة الويب، ويشمل أيضًا استقبال البيانات والأخطاء منها. لنلقِ نظرة على الكود أدناه.
حزمة الكود المصدري: boonya.ben.callingwebservice.species
class SpeciesRepositoryImpl(val apiService: Swapi) : SpeciesRepository {
override fun getSpecies(successHandler: (List<Species>?) -> Unit, failureHandler: (Throwable?) -> Unit) {
apiService.getSpecies().enqueue(object : Callback<SpeciesList> {
override fun onResponse(call: Call<SpeciesList>?, response: Response<SpeciesList>?) {
response?.body()?.let {
successHandler(it.speciesList)
}
}
override fun onFailure(call: Call<SpeciesList>?, t: Throwable?) {
failureHandler(t)
}
})
}
}
كما ترون، الدالة getSpecies هي دالة من رتبة أعلى (higher-order function)الدالة من رتبة أعلى** هي دالة تستقبل دوالًا كمعاملات أو تُرجع دالة. تستقبل getSpecies دالتين كوسائط
- (successHandler: (List<Species>?) -> Unit ) تُستدعى هذه الدالة عندما ينجح استدعاء خدمة الويب. وتستقبل List<Species>، أي بيانات الاستجابة من خدمة الويب، كوسيط. وسترون لاحقًا كيف يصل الـ ViewModelإلى هذه البيانات
- (failureHandler: (Throwable?) -> Unit) تُستدعى هذه الدالة عندما يفشل استدعاء خدمة الويب. وتستقبل أيضًا Throwable كوسيط، وسنتعامل معه في الـ ViewModel.
بوجود الـ Repository، لن تحتاج طبقة العرض (Activity/Fragment وViewModel) إلى الوصول مباشرة إلى المصادر البعيدة. لذلك إذا أردنا استخدام مصدر بعيد آخر بدلًا من Retrofit، يمكننا فعل ذلك دون تغيير الكود في طبقة العرض.
طبقة العرض
قبل أن نتابع، دعوني أعرّفكم بمكوّن جديد آخر اسمه LiveData.
إليكم شرحًا موجزًا.
LiveData يجعل صنف النموذج العادي قابلًا للمراقبة (observable).MutableLiveData هو تطبيق لـ LiveData. MutableLiveData يمكنه استخدام دالة اسمها setValue لإسناد قيمة جديدة إلى الـ **_LiveData. _ViewModel
الـ ViewModel مكوّن جديد قُدّم ضمن Android Architecture Component. فبدلًا من وضع كل شيء في Activity/Fragment، يمكننا فصل الكود غير المرتبط بالواجهة ونقله إلى ViewModel. إضافة إلى ذلك، يبقى الـ ViewModel قائمًا عند حدوث تغيير في الإعدادات (configuration change) مثل تدوير الشاشة. ففي العادة يُعاد إنشاء Activity/Fragment وكل ما بداخلهما عند حدوث تغيير في الإعدادات. لكن باستخدام ViewModel، يمكن إعادة استخدام النسخة الموجودة دون إنشاء نسخة جديدة.
لنلقِ نظرة على مثال. حزمة الكود المصدري: boonya.ben.callingwebservice.species
class SpeciesListViewModel : ViewModel() {
@Inject
lateinit var repository: SpeciesRepository
var isLoading = MutableLiveData<Boolean>()
var apiError = MutableLiveData<Throwable>()
var speciesResponse = MutableLiveData<List<Species>>()
fun getSpecies() {
isLoading.value = true
repository.getSpecies(
{
speciesResponse.value = it
isLoading.value = false
},
{
apiError.value = it
isLoading.value = false
})
}
//ignore other code
}
جهّزنا ثلاثة متغيرات من نوع MutableLiveData لتُراقَب من الـ activity/fragment.
- isLoading: تُضبط على true عند بدء استدعاء خدمة الويب، وتتغير إلى false عند نجاح الاستدعاء أو فشله.
- apiError: إذا حدث خطأ في استدعاء خدمة الويب، تُسند بيانات الـ Throwable إلى هذا المتغير.
- speciesResponse: عند الحصول على البيانات من خدمة الويب بنجاح، تُسند بيانات الاستجابة إلى هذا المتغير.
استدعاء الـ Repository من الـ ViewModel
لكي يستدعي الـ Repository خدمة الويب، علينا تشغيل الدالة getSpecies. وتستدعي هذه الدالة بدورها دالة أخرى في الـ Repository شرحتها في قسم Repository.
كما ترون، تستقبل repository.getSpecies(…. , ….) دالتين كوسائط. وإليكم شرح كل وسيط.
- الوسيط الأول هو الدالة التي تُستدعى عند نجاح استدعاء خدمة الويب. وتعود بيانات الاستجابة من خدمة الويب، ويمكن الإشارة إليها بـ it2. أما دالة الوسيط الثاني فتُستدعى عند فشل استدعاء خدمة الويب، ويمكن الإشارة إلى بيانات الـ Throwable بـ it.
Activity
لن تحتوي الـ activity إلا على الكود المرتبط بالواجهة. وإليكم كود الـ Activity.
class SpeciesActivity : AppCompatActivity() {
private val registry = LifecycleRegistry(this)
private lateinit var viewModel: SpeciesListViewModel
private lateinit var adapter: SpeciesListAdapter
override fun getLifecycle(): LifecycleRegistry = registry
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_species)
//set up ui
viewModel = createViewModel()
attachObserver()
viewModel.getSpecies()
}
private fun attachObserver() {
viewModel.isLoading.observe(this, Observer<Boolean> {
it?.let { showLoadingDialog(it) }
})
viewModel.apiError.observe(this, Observer<Throwable> {
it?.let { Snackbar.make(rvSpecies, it.localizedMessage, Snackbar.LENGTH_LONG).show() }
})
viewModel.speciesResponse.observe(this, Observer<List<Species>> {
it?.let { adapter.notifyDataSetChanged() }
})
}
private fun createViewModel(): SpeciesListViewModel =
ViewModelProviders.of(this).get(SpeciesListViewModel::class.java).also {
ComponentInjector.component.inject(it)
}
private fun showLoadingDialog(show: Boolean) {
if (show) progressBar.visibility = View.VISIBLE else progressBar.visibility = View.GONE
}
}
لكي تعمل الـ activity مع Android Architecture Component، يجب أن تطبّق LifecycleRegistryOwner وأن تعيد تعريف الدالة getLifeCycle() كما في الكود أعلاه.
لنلقِ نظرة أقرب على الدالة attachObserver(). ستراقب هذه الـ activity ثلاثة متغيرات MutableLiveData في الـ ViewModel.
- viewModel.isLoading: عند حدوث تغيير في هذا المتغير، تُستدعى الدالة showLoadingDialog لإظهار progressBar أو إخفائه2. viewModel.apiError: يظهر Snackbar برسالة الخطأ عند حدوث تغيير في هذا المتغير.
- viewModel.speciesResponse: يُبلَّغ الـ recycler adapter عند إسناد بيانات إلى هذه القيمة
الخلاصة
أحب Android Architecture Component كثيرًا. فهو لا يتيح لكم كتابة كود أسهل في الاختبار والصيانة فحسب، بل يحل أيضًا مشكلات يعاني منها معظم مطوّري Android، مثل التعامل مع تغيير الإعدادات. وإذا كنتم على وشك بدء مشروع جديد، فهذه البنية تستحق التجربة فعلًا.
المرجع


