عندما نخوض غمار عالم تطوير تطبيقات أندرويد، نجد أن إحدى أكثر المشكلات شيوعًا هي كيفية جعل شاشتين، أو جزأين، يتواصلان مع بعضهما البعض دون إحداث فوضى عارمة في الشيفرة. لطالما حاولنا تمرير البيانات عبر حزم أو واجهات معقدة، ولكن الحقيقة هي أن التواصل السلس بين المكونات إنه الأساس الذي يجعل التطبيق يبدو احترافياً ولا يتجمد عند تدوير الهاتف المحمول.
لحل هذه المشكلة، قدمت لنا جوجل ViewModel، وهو عنصر أساسي في بنية Jetpack يعمل كجسر بيانات. فبدلاً من أن يكون كل جزء جزيرة معزولة، يمكننا إنشاء مخزن المعلومات المشترك والتي تبقى قائمة بعد تغييرات التكوين، مما يمنع المستخدم من فقدان ما كتبه أو حدده لمجرد أنه قام بتحويل الشاشة إلى الوضع الأفقي.
مفهوم نموذج العرض المشترك
يكمن سحر ViewModel في دورة حياته، التي تدوم لفترة أطول من دورة حياة Fragment. إذا عرّفنا ViewModel بنطاق النشاط، فبإمكان جميع أجزاء التطبيق المُستضافة داخله الوصول إلى نفس النسخة. هذا يعني أنه إذا خزّن Fragment A اسم مستخدم، فبإمكان Fragment B قراءته فورًا لأن كلاهما إنهم ينظرون إلى نفس الشيء في ذكرى.
لتحقيق ذلك في لغة كوتلن، يُستخدم عادةً تفويض الخصائص. بينما viewModels() يقوم بإنشاء نموذج عرض فريد للجزء، الطريقة activityViewModels() يبحث عن نسخة موجودة مرتبطة بالنشاط. هذه هي أسرع طريقة لتنفيذ قناة اتصال دون أن تحتاج الأجزاء إلى معرفة بعضها البعض، وبالتالي الحفاظ على مبدأ استقلالية المكونات.
إدارة البيانات باستخدام LiveData والتحويلات
لا يكفي مجرد تخزين البيانات؛ بل يجب أن تتفاعل الواجهة عند تغيير تلك البيانات. وهنا يأتي دور LiveData. فبدلاً من استخدام متغيرات بسيطة، نستخدم MutableLiveData لتعديل الحالة نقوم بتخزين البيانات داخليًا في نموذج العرض (ViewModel) ونعرض بيانات حية غير قابلة للتغيير للعرض. هذا يضمن عدم قدرة أي شخص خارج النموذج على تغيير البيانات بشكل تعسفي.
أحيانًا، لا تكون البيانات الأولية الموجودة في ViewModel هي ما نريد عرضه. على سبيل المثال، قد يكون السعر رقمًا عشريًا، لكن المستخدم يحتاج إلى رؤيته مع رمز العملة المحلية. ولتحقيق ذلك، نستخدم Transformations.map()يُمكّننا هذا من تحويل قيمة LiveData إلى تنسيق آخر في الوقت الفعلي. وبهذه الطريقة، تبقى منطق التنسيق في ViewModel، ويقتصر دور العرض على معالجة التنسيق فقط. عرض النص النهائي.
المزامنة التلقائية مع ربط البيانات
لتجنب ملء الكود باستدعاءات متكررة إلى setText()يُعد ربط البيانات الأداة المثالية. فمن خلال ربط متغير ViewModel مباشرةً في ملف XML، يتم تحديث واجهة المستخدم تلقائيًا. وتكمن النقطة الحاسمة هنا في... lifecycleOwnerإذا لم نقم بتعيين مالك دورة الحياة لكائن الربط، فلن تعرف LiveData متى يجب إخطار العرض، ولن تنعكس التغييرات.
بالإضافة إلى ذلك، يمكننا استخدام روابط كائنات الاستماع بحيث تؤدي نقرات الأزرار إلى استدعاء الدوال في نموذج العرض مباشرةً. هذا يُحسّن بشكل كبير من كفاءة الكود في الأجزاء البرمجية، حيث لم تعد هذه الأجزاء تتعامل مع منطق العمل، بل أصبحت مجرد معالجات أحداث بسيطة. يتم تفعيل الإجراءات في النموذج.
استراتيجيات النطاق المتقدمة
على الرغم من أن استخدام النشاط كمالك لنموذج العرض هو الممارسة الأكثر شيوعًا، إلا أنه ليس دائمًا الأكثر كفاءة. إذا كان لدينا جزء رئيسي يحتوي على عدة أجزاء فرعية (كما هو الحال في TabLayout)، فمن الناحية المثالية... الجزء الأصل هو المالك من ViewModelStore. وبالتالي، فإن العناصر الفرعية لتلك المجموعة فقط هي التي تتشارك المعلومات، مما يمنع ViewModel من أن يصبح عنصرًا فرديًا عالميًا يستهلك الذاكرة بلا داعٍ.
إذا كنا نستخدم مكتبة التنقل، فلدينا خيار أكثر دقة: النطاق بناءً على NavBackStackEntryيسمح هذا لنموذج العرض (ViewModel) بالبقاء فقط أثناء تنقل المستخدم عبر مسار محدد من الشاشات، حيث يتم تدميره تلقائيًا عندما يغادر المستخدم مخطط التنقل هذا، وهو جوهرة لتحسين الأداء من الموارد.
التطور نحو Jetpack Compose
من المهم الإشارة إلى أن النهج القائم على التجزئة و XML أصبح قديمًا. مع Jetpack Compose، لم تعد التجزئة تعتمد على مكونات ضخمة، بل على وظائف قابلة للتركيب. وقد تم حل مشكلة استمرارية البيانات الآن من خلال... رفع الدولة واستخدام بنية مرئية مشتركة للتحكم في حالة واجهة المستخدم.
في Compose، نقوم برفع الحالة إلى ViewModel مشترك، وتتفاعل واجهة المستخدم ببساطة مع التغييرات في StateFlowلا حاجة بعد الآن لإدارة معاملات الأجزاء أو التعامل مع مدير الأجزاء. أصبح التواصل مباشرًا: يُطلق أحد المكونات حدثًا، ويُحدّث نموذج العرض حالته، و تمت إعادة رسم الواجهة بالكامل. يتم ذلك تلقائياً بناءً على تلك القيمة الجديدة.
بدائل لتحقيق نتائج محددة
لسنا بحاجة دائمًا إلى ViewModel. إذا أردنا فقط تمرير البيانات بسرعة من جزء إلى آخر (مثل نتيجة مسح رمز الاستجابة السريعة)، فمن الأفضل استخدام واجهة برمجة تطبيقات نتائج الأجزاءتتيح لك هذه الأداة تكوين مستمع في الجزء المستقبل وإرسال حزمة من المرسل.
بخلاف ViewModel، الذي يُستخدم للبيانات الدائمة، فإن واجهة برمجة التطبيقات هذه مخصصة لـ الاتصالات المؤقتة وذات الاتجاه الواحديعتمد على نظام المفتاح والقيمة الذي يديره FragmentManager، مما يمنعنا من الاضطرار إلى إنشاء فئات ViewModel ثقيلة الوزن للمهام التي هي في الأساس رسالة بسيطة ذهابًا وإيابًا.
