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

في الماضي، كانت بنية التعليمات البرمجية تتكون من طبقتين: الأولى، طبقة البيانات، التي كانت مسؤولة عن استرداد/تخزين البيانات من واجهات برمجة تطبيقات REST واستقرار مخازن البيانات، والثانية، طبقة العرض، التي كانت مسؤولة عن التعامل مع و عرض البيانات في واجهة المستخدم.
تعمل الطرق التي يوفرها APIProvider على تسهيل التفاعل مع REST API عن طريق تنشيط الأنشطة والأجزاء . تستخدم هذه الأساليب URLConnection و Async Tasks لتنفيذ مكالمات الشبكة في سلسلة رسائل منفصلة . لدى CachProvider
أيضًا طرق لاسترداد البيانات وتخزينها من SharedPreferences أو قاعدة بيانات SQLite . ويستخدم عمليات الاسترجاعات لإرجاع النتيجة إلى الأنشطة.
الصعوبات
كانت المشكلة الرئيسية في هذا النهج هي أن طبقة العرض تتحمل الكثير من المسؤوليات. تخيل سيناريو بسيطًا وشائعًا حيث يجب على التطبيق تحميل قائمة منشورات المدونة وتخزينها في قاعدة بيانات SQLite (ذاكرة التخزين المؤقت ) وعرضها أخيرًا في ListView . في هذا الأسلوب، يجب أن يقوم النشاط بما يلي:
استدعاء أسلوب LoadPosts (رد الاتصال) في APIProvider .
انتظر رد الاتصال الناجح لـ CacheProvider واعرض المشاركات في عرض القائمة.
معالجة منفصلة لاثنين من أخطاء رد الاتصال المحتملة من APIProvider و CacheProvider .
النظر في مثال بسيط جدا. في السيناريو الحقيقي ، قد لا تقوم REST API بإرجاع البيانات بالطريقة التي يحتاجها العرض. ولذلك، يقوم النشاط بتغيير البيانات أو تصفيتها قبل عرضها. تحدث حالة شائعة أخرى عندما تأخذ طريقة LoadPosts المعلمات التي يجب جلبها من مكان آخر، مثل عنوان البريد الإلكتروني المقدم من Playservices SDK . في مثل هذه الحالة، من الممكن أن يقوم SDK بإرجاع البريد الإلكتروني بشكل غير متزامن باستخدام رد الاتصال، بمعنى أن لدينا ثلاث مراحل من عمليات الاسترجاعات المتداخلة. إذا تمت إضافة المزيد من التعقيدات، فإن نتيجة هذا النهج هي ظاهرة تعرف باسم رد الاتصال الجحيم . باختصار:
الأنشطة والأجزاء تصبح كبيرة جدًا ويصعب الحفاظ عليها.
تعني زيادة عمليات الاسترجاعات المتداخلة أن الرموز تصبح قبيحة ويصعب فهمها، ونتيجة لذلك، يصبح من الصعب إجراء تغييرات أو إضافة ميزات جديدة.
إن اختبار الوحدة
، إن لم يكن مستحيلاً، سيصبح بالتأكيد تحديًا نظرًا لوجود العديد من المنطق المباشر في الأنشطة والأجزاء التي يصعب اختبار الوحدة.
تقديم بنية جديدة من Rx Java
استخدمنا الطريقة المذكورة أعلاه لمدة عامين. خلال هذين العامين، حققنا بعض التحسينات التي قللت من المشاكل المذكورة بشكل طفيف. على سبيل المثال، تمت إضافة عدة فئات مساعدة لتقليل التعليمات البرمجية في الأنشطة والأجزاء ، وتم استخدام Volley في APIProvider . على الرغم من كل هذه التغييرات، فإن كود تطبيقنا لا يزال غير مناسب للاختبار وحدثت مشكلة رد الاتصال كثيرًا.
كان ذلك في أواخر عام 2013 عندما بدأنا القراءة عن RxJava . بعد اختبار RX Java على عدة نماذج من المشاريع، أدركنا أننا وجدنا أخيرًا حلاً لمشكلة رد الاتصال المتداخلة. يتيح لك RX Java إدارة البيانات من خلال التدفقات غير المتزامنة ويمنحك أيضًا العديد من عوامل التشغيل التي يمكنك تطبيقها على التدفق لتغيير البيانات أو دمجها أو تصفيتها.
وبالنظر إلى المعاناة التي مررنا بها منذ سنوات، قررنا أن نفكر في الشكل الذي سيكون عليه هيكل التطبيق الجديد. يمكنك رؤية النتيجة في الصورة أدناه:

تمامًا مثل الطريقة الأولى، يمكن تقسيم هذه البنية إلى طبقتين للبيانات والعرض. تتضمن طبقة البيانات مدير البيانات ومجموعة من المساعدين. تتكون طبقة العرض من مكونات إطار عمل Android مثل الأنشطة والأجزاء ومجموعات العرض وما إلى ذلك.
تتحمل الفصول المساعدة (العمود الثالث في الصورة) مسؤوليات محددة للغاية وتقوم بتنفيذها وإكمالها بطريقة مدمجة وموجزة. على سبيل المثال، تحتوي معظم المشاريع على مساعدين للوصول إلى واجهات برمجة تطبيقات REST التي تقرأ البيانات من قواعد البيانات أو تتفاعل مع حزم SDK التابعة لجهات خارجية . PreferenceHelper : يقرأ البيانات ويحفظها في SharedPreferences .
مساعد قاعدة البيانات: التعامل مع الوصول إلى قواعد بيانات SQLite .
خدمات التعديل التحديثي : تنفيذ الاستدعاءات إلى REST APIs . تم استخدام التعديل التحديثي بدلاً من Volley بسبب دعم Java RX .
تقوم العديد من الأساليب العامة ضمن فئات مساعد RxJava بإرجاع العناصر القابلة للملاحظة.
مدير البيانات هو العقل المدبر وراء هذه البنية، والذي يستخدم عوامل RX الخاصة بـ Java لدمج البيانات المستردة من العديد من الفئات المساعدة وتصفيتها وتعديلها. الهدف من مدير البيانات هو تقليل مهام الأنشطة والأجزاء . ويتم ذلك من خلال توفير بيانات جاهزة للعرض ولا تتطلب أي تغييرات.
لمعرفة المزيد حول طريقة إدارة البيانات، انتبه إلى الرموز التالية. كيف تعمل هذه الطريقة:
اتصل بخدمة التحديثية لتحميل قائمة منشورات المدونة من REST API .
احفظ المنشورات في قاعدة البيانات المحلية لأغراض التخزين المؤقت باستخدام Database Helper.
تصفية منشورات المدونة المكتوبة اليوم لأن هذه هي المنشورات الوحيدة التي تنوي طبقة العرض إظهارها.

تستدعي المكونات الموجودة في طبقة العرض، مثل الأنشطة أو الأجزاء ، هذه الطريقة وتدعم Observable الذي تم إرجاعه . بمجرد انتهاء الدعم، تتم إضافة منشورات الإخراج بواسطة Observable مباشرة إلى محول ليتم عرضه في RecyclerView أو ما شابه.
العنصر الأخير في هذا الهيكل هو ناقل الحدث . يتيح لنا هذا العنصر نشر الأحداث التي تحدث في طبقة البيانات، بحيث يمكن للمكونات المتعددة في طبقة العرض دعم هذه الأحداث. على سبيل المثال، يمكن لطريقة تسجيل الخروج () في مدير البيانات نشر حدث عند اكتمال الملاحظة ، وبالتالي يمكن للأنشطة التي تدعم هذا الحدث تغيير واجهة المستخدم الخاصة بها لعرض عبارة تسجيل الخروج .
لماذا هذا النهج أفضل؟
تعمل عناصر Java RX Observables والمشغلون على التخلص من الحاجة إلى عمليات الاسترجاعات المتداخلة.
يتحكم مدير البيانات في المسؤوليات التي كانت تشغلها طبقة العرض سابقًا. بهذه الطريقة، تصبح الأنشطة والشظايا أخف.
يؤدي نقل الرموز من الأنشطة والأجزاء إلى مدير البيانات والمساعدين إلى تسهيل كتابة اختبارات الوحدة.
إن الفصل الواضح بين المسؤوليات ووجود مدير البيانات كنقطة التفاعل الوحيدة مع طبقة البيانات يجعل اختبار هذا الهيكل سهلاً. يتم اختبار فئات المساعد أو مدير البيانات بسهولة.
ولكن ما هي المشاكل التي لا تزال موجودة؟
بالنسبة للمشاريع الكبيرة والمعقدة للغاية، يصبح مدير البيانات كبيرًا جدًا ويصعب صيانته.
على الرغم من أن مكونات طبقة العرض، مثل الأنشطة والأجزاء، أصبحت أخف وزنًا، إلا أنها لا تزال بحاجة إلى التعامل مع قدر كبير من اشتراكات إدارة JavaRX، والأخطاء، والتحليلات، وما إلى ذلك .
الانتهاء من عرض نموذج مقدم العرض
في العام الماضي، أصبحت العديد من أنماط الهندسة المعمارية مثل MVP أو MVVM شائعة بين مجتمع Android. بعد فحص هذه الأنماط في نموذج مشروع ومقال، وجدنا أن نمط MVP يجلب تحسينات قيمة لنهجنا الحالي. نظرًا لأن هيكلنا الحالي مقسم إلى طبقتين (العرض والبيانات)، فإن إضافة نمط MVP إليه يبدو أمرًا طبيعيًا. ولهذا الغرض يكفي إضافة طبقة جديدة من المقدم إليها ونقل جزء من الرموز من طبقة العرض إلى المقدمين .

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

عرض MVP هو مكون العرض الذي يدعمه هذا المقدم . عادةً ما يكون عرض MVP مثالاً على نشاط أو جزء أو مجموعة عرض .
مثل البنية السابقة، تتضمن طبقة العرض مكونات إطار عمل قياسية مثل مجموعة العرض أو الجزء أو الأنشطة. يكمن الاختلاف الرئيسي بينهما في المكونات التي لا تدعم العناصر القابلة للملاحظة بشكل مباشر . وبدلاً من ذلك، يقومون بتطبيق واجهة عرض MVP ويقدمون قائمة بالطرق المختصرة مثل عرض الأخطاء أو عرض مؤشر التقدم. بالإضافة إلى ذلك، فإن مسؤولية التعامل مع تفاعلات المستخدم مثل أحداث النقر هي مسؤولية عرض المكونات ويتصرفون وفقًا لها من خلال استدعاء الطريقة الصحيحة في المقدم . على سبيل المثال، إذا كان لدينا زر لتحميل قائمة المنشورات، فإن نشاطنا سوف يستدعي Presenter.load Today Posts() .
لماذا هذا النهج أفضل؟
الأنشطة والأجزاء خفيفة جدًا ومسؤولياتنا الوحيدة هي تحديث/ إعداد واجهة المستخدم والتعامل مع أحداث المستخدم. لذلك يصبح من الأسهل صيانتها والحفاظ عليها.
أصبحت اختبارات وحدة الكتابة للمقدم أسهل. في السابق، كان هذا الرمز جزءًا من طبقة العرض، لذلك لم يكن من الممكن اختباره بوحدة. الهيكل بأكمله سهل الاختبار للغاية.
إذا أصبح مدير البيانات كبيرًا جدًا ، فسنحل هذه المشكلة عن طريق نقل بعض الرموز إلى مقدم العرض .
المشاكل التي لا تزال موجودة
قد يظل وجود مصدر بيانات واحد فقط يمثل مشكلة عندما تصبح قاعدة التعليمات البرمجية كبيرة جدًا ومعقدة. وبطبيعة الحال، لم نصل بعد إلى النقطة التي تصبح فيها هذه القضية مشكلة حقيقية، ولكننا نعلم أننا سنواجه هذه المشكلة في المستقبل.
ومن الضروري أن نذكر أن هذا الهيكل ليس مثاليا ولا تشوبه شائبة. وفي الواقع، فإن الاعتقاد بأن هناك نهجًا فريدًا ومثاليًا يحل جميع المشكلات إلى الأبد هو فكرة فظة وكاذبة. يستمر نظام Android البيئي في النمو والتطور بوتيرة سريعة، وعلينا أن نستمر في الاستكشاف والدراسة والتجربة لإيجاد طرق أفضل لإنشاء تطبيقات Android فريدة.