سنبدأ في هذا الدرس من سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms ببناء تطبيق عملي بمعايير تقنيّة عالية. حيث سنستخدم المعارف التي حصلنا عليها من الدروس السابقة في بناء تطبيق جهات اتصال بسيط لكنّه يستخدم تقنيّات ومفاهيم متقدّمة نسبيًّا.
سنتناول هذا التطبيق على ثلاثة أجزاء متتالية، إليك وصف مختصر لمحتوى كلّ منها:
- الجزء الأوّل: شرح الغاية من التطبيق، وتوضيح فكرة نموذج المستودع Repository في بناء التطبيقات، مع بناء الهيكل العام للتطبيق، وهذا هو محتوى هذا الدرس.
- الجزء الثاني: تجهيز النواحي الوظيفيّة للمستودع وجعله قابلًا للاستخدام.
- الجزء الثالث: تنفيذ واجهتي التطبيق الرئيسية والفرعيّة الخاصّة بعرض التفاصيل. وتنفيذ عمليّة التنقّل بين الواجهتين
الغاية من التطبيق وكيف يعمل
فكرة التطبيق بسيطة للغاية، تتلخّص بعرض جهات اتصال موجودة مسبقًا وإمكانية البحث ضمنها، مع إمكانيّة إضافة جهات اتصال جديدة وتحريرها وحذفها. يعتمد التطبيق على وجود واجهتين. الواجهة الأولى هي الواجهة الرئيسيّة وتحتوي على قسم خاص بالبحث حسب الاسم أو الكنية عن أيّ جهة اتصال موجودة مسبقًا، بالإضافة إلى قائمة لعرض جهات الاتصال الناتجة عن عمليّة البحث، وأخيرًا زر خاص بإضافة جهات اتصال جديدة. انظر الشكل التالي الذي ينتج عند ضغط زر البحث FIND عند عدم تحديد أي معيار للبحث:
عندما يقوم المستخدم بنقر زر البحث FIND دون أن يحدّد أي معيار، سيقوم التطبيق بعرض جميع جهات الاتصال الموجودة لديه، والتي ستكون في هذه النسخة من البرنامج عبارة عن بيانات وهمية موجودة ضمن ذاكرة التطبيق. أمّا عند تحديد المستخدم للاسم أو الكنيّة فسيعمل التطبيق على البحث مستخدمًا منطق AND.
أمّا الواجهة الثانية، فتظهر عندما يلمس المستخدم إحدى جهات الاتصال من القائمة السابقة، حيث تعرض هذه الواجهة بيانات تفصيليّة حول جهة الاتصال هذه: الاسم والكنية ورقم الهاتف وعنوان البريد الإلكتروني والهوايات. انظر إلى الشكل التالي:
من الممكن تعديل أيّ من هذه البيانات ثم ينقر المستخدم زر الحفظ لحفظها، أو أن ينقر زر الرجوع إلى الواجهة السابقة الموجود في الأعلى بجانب أيقونة البرنامج في حال لم يرغب بتعديل البيانات. كما يمكن للمستخدم أن يحذف جهة الاتصال هذه بنقره على زر الحذف Delete كما يظهر من الشكل السابق. وهذه ببساطة فكرة التطبيق.
نموذج المستودع Repository
عندما تكبر التطبيقات وتتنوّع المهام المطلوبة منها تبرز الحاجة لوسيلة لتنظيم العمل داخل التطبيق. في الحقيقة توجد العديد من النماذج التي تدعمها Xamarin لهذه الغاية مثل نموذج MVVM الذي يستخدم بفعالية ضمن Xamarin لتنظيم وفصل الأجزاء المسؤولة عن الواجهات عن الأجزاء المسؤولة عن منطق العمل عن تلك المسؤولة عن التعامل مع مزودات البيانات البعيدة أو المحلية باختلاف أنواعها.
من النماذج التي أفضلها شخصيًّا هو نموذج المستودع Repository الذي أستخدمه على نحو واسع في جميع أنواع التطبيقات التي أعمل عليها. فهو أسلوب جميل ومنطقي ويسمح بتطوير التطبيق بشكل سلس وسريع للعمل في مختلف أنواع البيئات، وهو متوافق للعمل مع نموذج MVVM.
يسمح نموذج المستودع بعزل الشيفرة البرمجيّة المسؤولة عن التعامل مع البيانات عن منطق البرنامج business logic. وفي هذا الأمر عدة فوائد من أهمّها:
- تنظيم البرنامج، وجعله أكثر قابليّة للفهم والتطوير.
- إجراء تطوير على أسلوب التعامل مع البيانات دون إجراء أي تغيير في منطق عمل البرنامج.
- إمكانيّة إجراء تغيير جذري لنوع الخدمة التي نستخدمها لتخزين البيانات دون تغيير يُذكر في منطق العمل.
سأخوض مباشرةً في كيفية اعتماد هذا النموذج في تطبيقنا هذا. حيث سنحتاج إلى استخدام واجهة واحدة Interface مع صنف واحد يُحقّقها. لتنعش ذاكرتك حول الواجهات انظر هذا الدرس. لنبدأ الآن في بناء هذا التطبيق وذلك في الفقرة التالية.
بناء التطبيق
ابدأ بإنشاء مشروع جديد من النوع Blank App (Xamarin.Forms Portable) وسمّه ContactsApp ثم أبق فقط على المشروعين ContactsApp (Portable) و ContactsApp.Droid كما وسبق أن فعلنا في هذا الدرس.
من نافذة مستكشف الحل Solution Explorer انقر بزر الفأرة الأيمن على المشروع ContactsApp واختر من القائمة التي ستظهر الخيار Add ثم من القائمة الفرعية الخيار New Folder لإضافة مجلّد جديد. سمّ هذا المجلّد بالاسم Entities، وبعد أن يظهر في نافذة الحل Solution Explore انقر عليه بزر الفأرة الأيمن واختر الخيار Add ومن القائمة الفرعية اختر Class. ستظهر نافذة تسمح لك بتعيين اسم لهذا الصنف. اختر الاسم Contact له. هذا الصنف هو حجر البناء الأساسي لهذا البرنامج والذي يمثّل منطق العمل فيه. احرص على جعل محتويات الملف Contact.cs كما يلي:
namespace ContactsApp.Entities { public class Contact { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Tel { get; set; } public string EMail { get; set; } public string Hobbies { get; set; } public override string ToString() { return string.Concat(FirstName, " ", LastName); } } }
يحتوي الصنف Contact كما يظهر من الشكل السابق على البيانات الأساسيّة التي تحتاجها أيّة جهة اتصال، بالإضافة إلى خاصيّة الهوايات Hobbies التي قد تبدو غريبة قليلًا بالنسبة لجهة اتصال.
انقر مرّة أخرى بزر الفأرة الأيمن على المشروع ContactsApp ثم اختر من القائمة التي ستظهر الخيار Add ثم من القائمة الفرعية الخيار New Folder لإضافة مجلّد جديد. سمّ هذا المجلّد بالاسم Abstract، وبعد أن يظهر في نافذة الحل Solution Explore انقر عليه بزر الفأرة الأيمن واختر الخيار Add ومن القائمة الفرعية اختر New Item. ستظهر نافذة تسمح لك بتعيين نوع العنصر المراد إضافته. اختر واجهة Interface وعيّن الاسم IContactsRepository لها. واحرص على أن تكون محتويات الملف IContactsRepository.cs كما يلي:
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using ContactsApp.Entities;
namespace ContactsApp.Abstract
{
public interface IContactsRepository
{
Task<ObservableCollection<Contact>> GetContactsAsync(string firstName, string lastName);
Task<bool> AddContactAsync(Contact contactToAdd);
Task<bool> UpdateContactAsync(Contact contactToUpdate);
Task<bool> DeleteContactAsync(Contact contactToDelete);
}
}
تُستَخدَم الواجهات عمومًا عندما نرغب بتجريد Abstraction الأمور وجعلها عموميّةً وفي ذلك فائدة كبيرة في جعل الشيفرة البرمجيّة أكثر قابليّة للفهم ولإعادة الاستخدام. وهذا سبب إضافة هذه الواجهة إلى المجلّد Abstract. لا تحتوي الواجهات على أيّة شيفرة برمجيّة كما نعلم، فكل ما تحتويه هو عبارة عن تصاريح لتوابع يجب تحقيقها ضمن أيّ صنف يرغب بتحقيق هذه الواجهة. تحتوي هذه الواجهة باختصار على العمليّات الأساسيّة التي يحتاجها تطبيقنا لإنجاز المهام المنوطة به وهي: الحصول على جهات الاتصال حسب الاسم والكنية GetContactsAsync، وإضافة جهة اتصال جديدة AddContactAsync، وتحديث جهة اتصال موجودة مسبقًا UpdateContactAsync، وحذف جهة اتصال DeleteContactAsync. أمّا سبب وجود الكلمة Async في كلّ من هذه التوابع فهو للإشارة إلى أنّه يُفترض بها أن تستخدم تقنيّة البرمجة غير المتزامنة Asynchronous Programming التي تحدثنا عنها في هذا الدرس. المثير في الأمر أنّ هذه الواجهة لا تهتم بمكان وجود البيانات أو كيفيّة الحصول عليها والتعامل معها. إنّما تهتم فقط بما يحتاجه التطبيق وبشكل مجرّد.
سنكرّر الآن نفس العمليّة لإضافة مجلّد جديد ضمن المشروع ContactsApp.cs واسمه Concrete وهو الذي سيحتوي على الصنف الذي سيحقّق الواجهة IContactsRepository السابقة. انقر بزر الفأرة الأيمن على هذا المجلّد واختر Add. ومن القائمة الفرعية اختر Class. سمّ هذا الصنف بالاسم MemoryContactsRepository واحرص على أن تكون محتوياته كما يلي:
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using ContactsApp.Abstract;
using ContactsApp.Entities;
namespace ContactsApp.Concrete
{
public class MemoryContactsRepository : IContactsRepository
{
private ObservableCollection<Contact> contacts;
public MemoryContactsRepository()
{
contacts = new ObservableCollection<Contact>()
{
new Contact()
{
Id=1,
FirstName = "Ahmad",
LastName="Saeed",
Tel="123456",
EMail="admin@example.com",
Hobbies="Swimming"
},
new Contact()
{
Id=2,
FirstName = "Mahmood",
LastName="Maktabi",
Tel="852136",
EMail="info@example.com",
Hobbies="Reading"
},
new Contact()
{
Id=3,
FirstName = "Mazen",
LastName="Najem",
Tel="987456",
EMail="it@example.com",
Hobbies="Swimming"
},
new Contact()
{
Id=4,
FirstName = "Sawsan",
LastName="Hilal",
Tel="741258",
EMail="sales@example.com",
Hobbies="Writing, Reading"
},
new Contact()
{
Id=5,
FirstName = "Musab",
LastName="Aga",
Tel="357159",
EMail="admin@example.com",
Hobbies="Sport"
}
};
}
public async Task<ObservableCollection<Contact>> GetContactsAsync(string firstName, string lastName)
{
throw new System.NotImplementedException();
}
public async Task<bool> AddContactAsync(Contact contactToAdd)
{
throw new System.NotImplementedException();
}
public async Task<bool> UpdateContactAsync(Contact contactToUpdate)
{
throw new System.NotImplementedException();
}
public async Task<bool> DeleteContactAsync(Contact contactToDelete)
{
throw new System.NotImplementedException();
}
}
}
الأمر الملفت للنظر هنا أنّ بيانات جهات الاتصال موجودة ضمن هذا الصنف بالفعل وتحديدًا ضمن بانيته. وهي مخزّنة ضمن المتغيّر contacts وهو معرّف على مستوى الصنف ومن النوع العمومي ObservableCollection الذي سنتحدّث عنه في الدرس التالي. يكفي الآن أن تعرف أنّه عبارة عن مجموعة عناصرها كائنات من النوع Contact وهي تُفيد في التطبيقات التي تُستخدَم فيها البرمجة غير المتزامنة. من غير الواقعي بكل تأكيد وجود البيانات مخزّنة في الصنف بهذه الطريقة، فالمفترض أن تكون ضمن قاعدة بيانات محليّة أو بعيدة أو حتى ضمن ملف عادي. في الواقع تبرز هنا قوّة نموذج المستودع Repository في عزل أسلوب الحصول على البيانات عن البرنامج الفعلي.
من الواضح أيضًا أنّ التوابع الموجودة هنا تحتوي في الحقيقة على شيفرة برمجيّة تؤدّي إلى رمي الاستثناء NotImplementedException وذلك لتذكرينا بعدم جاهزيتها بعد.
سيبدو مستكشف الحل في نهاية المطاف شبيهًا بالشكل التالي:
الخلاصة
هذا الدرس هو المقدّمة لتطبيق جهات الاتصال الذي سنتابع بناءه على مدى الدرسين التاليين. تناولنا في هذا الدرس فكرة التطبيق الأساسيّة، وتوضيح فكرة نموذج المستودع Repository من خلال بناء واجهة تمثّله بالإضافة إلى صنف يحقّقها. سيمكننا من خلال هذا الصنف (صنف المستودع) التعامل مع بيانات موجودة ضمن ذاكرة التطبيق فقط. ورغم كون هذا الأسلوب غير واقعي، إلَّا أنّه ضروري في تبسيط الأمور وجعلها أسهل للفهم. كما أنشأنا صنف يمثّل جهة الاتصال في التطبيق. ورأينا ماهية العلاقة بينه وبين صنف المستودع، من خلال الاستدعاءات إلى التوابع الموجودة ضمن الصنف الأخير.
حقوق الصورة البارزة محفوظة لـ Freepik
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.