اذهب إلى المحتوى

بناء تطبيق جهات الاتصال باستخدام Xamarin - الجزء الثاني


حسام برهان

سنتابع في هذا الدرس من سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms العمل الذي بدأناه في الدرس السابق والمتمثّل ببناء تطبيق جهات الاتصال. قد أنهينا في الدرس السابق بناء نموذج المستودع من خلال التصريح عن  الواجهة IContactsRepository وتحقيقها من خلال الصنف MemoryContactsRepository. كما أنشأنا الصنف Contacts الذي يمثّل حجر البناء الأساسي في التطبيق.

main.png

سنضيف في هذا الدرس النواحي الوظيفيّة للصنف المستودع MemoryContactsRepository لكي يصبح تطبيقنا قابلًا للعمل.

تجهيز النواحي الوظيفيّة للمستودع

افتح الملف MemoryContactsRepository.cs واحرص على أن تكون محتوياته على الشكل التالي:

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)
        {
            return await Task.Factory.StartNew(() =>
            {
                IEnumerable<Contact> result = from contact in contacts where
                                                contact.FirstName
                                                    .ToUpper()
                                                    .Contains(firstName.ToUpper()) &&
                                                contact.LastName
                                                    .ToUpper()
                                                    .Contains(lastName.ToUpper())
                                              select contact;

                ObservableCollection<Contact> tmp = new ObservableCollection<Contact>(result);
                return tmp;
            });
        }
        public async Task<bool> AddContactAsync(Contact contactToAdd)
        {
            return await Task.Factory.StartNew(() =>
            {
                contactToAdd.Id = contacts.Count() + 1;
                contacts.Add(contactToAdd);
                return true;
            });
        }
        public async Task<bool> UpdateContactAsync(Contact contactToUpdate)
        {
            return await Task.Factory.StartNew(() =>
            {
                Contact result = (from contact in contacts
                                  where contact.Id == contactToUpdate.Id
                                  select contact).FirstOrDefault();

                if (result != null)
                {
                    contactToUpdate.FirstName = result.FirstName;
                    contactToUpdate.LastName = result.LastName;
                    contactToUpdate.EMail = result.EMail;
                    contactToUpdate.Tel = result.Tel;
                    contactToUpdate.Hobbies = result.Hobbies;

                    return true;
               }
               else
                {
                    return false;
                }
            });
        }
        public async Task<bool> DeleteContactAsync(Contact contactToDelete)
        {
            return await Task.Factory.StartNew(() =>
            {
                var result = (from contact in contacts
                              where contact.Id != contactToDelete.Id
                              select contact);

                if (result != null)
                {
                    contacts = new ObservableCollection<Contact>(result);

                    contactToDelete = null;
                    return true;
                }
                else
                {
                    return false;
                }
            });
        }
    }
}

 

الجديد هنا أنّنا قد أسندنا شيفرة برمجيّة لكل من التوابع الأساسيّة الموجودة في المستودع. انظر إلى الفقرات التالية التي تشرح عمل الشيفرة البرمجيّة ضمن كل تابع.

تابع البحث GetContactsAsync

فيما يلي التابع GetContactsAsync والذي يتطلّب وسيطين من النوع string للبحث حسب الاسم والكنية لجهات الاتصال:

public async Task<ObservableCollection<Contact>> GetContactsAsync(string firstName, string lastName)
{
    return await Task.Factory.StartNew(() =>
    {
        IEnumerable<Contact> result = from contact in contacts where
                                        contact.FirstName
                                            .ToUpper()
                                            .Contains(firstName.ToUpper()) &&
                                        contact.LastName
                                            .ToUpper()
                                            .Contains(lastName.ToUpper())
                                        select contact;
        ObservableCollection<Contact> tmp = new ObservableCollection<Contact>(result);
        return tmp;
    });
}

واضح أنّ هذه الشيفرة تقوم بإنشاء مهمة جديدة (تابع على شكل تعبير lambda) عن طريق التابع Task.Factory.StartNew (ستحتاج إلى إنعاش ذاكرتك بهذا الدرس) وذلك باستخدام تقنية البرمجة غير المتزامنة لمنع جمود التطبيق كما نعلم. المتغيّر result الذي تراه في الشيفرة السابق هو من النوع IEnumerable<Contact> أي مجموعة قابلة للعد عناصرها من النوع Contact، وهو يحصل على هذه المجموعة من خلال استعلام LINQ To Objects بسيط:

from contact in contacts where

        contact.FirstName

            .ToUpper()

            .Contains(firstName.ToUpper()) &&

        contact.LastName

            .ToUpper()

            .Contains(lastName.ToUpper())

        select contact;

تعمل هذه الشيفرة ببساطة على الاستعلام عن جميع جهات الاتصال الموجودة ضمن المتغير contacts والتي تحتوي على عبارتي البحث الخاصّتين بالاسم والكنية بنفس الوقت. إذا كان لديك خبرة باستعلامات SQL فستجد LINQ to Objects مألوفة.

لاحظ وجود عبارتي return ضمن هذا التابع GetContactsAsync. العبارة التي تأتي أولًا هي العبارة المسؤولة عن إرجاع كائن من النوع Task<ObservableCollection<Contact>> أمّا العبارة الثانيّة التي تأتي في الأسفل فهي المسؤولة عن إرجاع كائن من النوع Task<ObservableCollection<Contact>> من التابع الداخلي وهو عبارة عن تعبير lambda والذي يتم تنفيذه من خلال التابع Task.Factory.StartNew كما هو واضح.

في آخر سطرين من التابع الداخلي (تعبير lambda) نعمل على تقديم نتيجة البحث على شكل مجموعة عموميّة وهي ObservableCollection<Contact> حيث يتم تحويلها ضمنيًّا إلى كائن من النوع Task<ObservableCollection<Contact>>.

 

اقتباس

لمعرفة المزيد عن تقنية LINQ to Objects ألق نظرة على هذا المقال.

 

تابع إضافة جهة اتصال جديد AddContactAsync

التابع AddContactAsync يتطلّب وسيطًا واحدًا فقط من النوع Contact ويُرجع كائن من النوع Task<bool> للإشارة إلى نجاح عمليّة الإضافة:

public async Task<bool> AddContactAsync(Contact contactToAdd)
{
    return await Task.Factory.StartNew(() =>
    {
        contactToAdd.Id = contacts.Count() + 1;
        contacts.Add(contactToAdd);
        return true;
    });
}

تعمل هذه الشيفرة على إضافة جهة اتصال جديدة إلى جهات الاتصال الموجودة مسبقًا ضمن المتغيّر contacts مع الانتباه إلى هذه العبارة البرمجيّة:

contactToAdd.Id = contacts.Count() + 1;

 

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

تابع الحذف DeleteContactAsync

التابع DeleteContactAsync  يتطلّب وسيطًا واحدًا فقط من النوع Contact ويُرجع كائن من النوع Task<bool> للإشارة إلى نجاح عمليّة الحذف:

public async Task<bool> DeleteContactAsync(Contact contactToDelete)
{
    return await Task.Factory.StartNew(() =>
    {
        var result = (from contact in contacts
                        where contact.Id != contactToDelete.Id
                        select contact);
        if (result != null)
        {
            contacts = new ObservableCollection<Contact>(result);
            contactToDelete = null;
            return true;
        }
        else
        {
            return false;
        }
    });
}

تعمل الشيفرة السابقة في الواقع على فلترة جهة الاتصال المراد حذفها مما يعني ببساطة أنّ استعلام LINQ to Objects سيعمل على إرجاع جميع جهات الاتصال باستثناء تلك التي يكون قيمة الخاصية Id لها مطابقة لقيمة الخاصيّة Id لجهة الاتصال التي نرغب بحذفها. ومن ثمّ يتم إسناد النتيجة إلى المتغيّر contacts مما يعني فعليًّا أنّنا قد حذفنا جهة الاتصال هذه من الذاكرة.

تابع التحديث UpdateContactAsync

التابع UpdateContactAsync والذي يتطلّب وسيطًا واحدًا فقط من النوع Contact ويُرجع كائن من النوع Task<bool> للإشارة إلى نجاح عمليّة الحذف:

public async Task<bool> UpdateContactAsync(Contact contactToUpdate)
{
    return await Task.Factory.StartNew(() =>
    {
        Contact result = (from contact in contacts
                            where contact.Id == contactToUpdate.Id
                            select contact).FirstOrDefault();
        if (result != null)
        {
            contactToUpdate.FirstName = result.FirstName;
            contactToUpdate.LastName = result.LastName;
            contactToUpdate.EMail = result.EMail;
            contactToUpdate.Tel = result.Tel;
            contactToUpdate.Hobbies = result.Hobbies;
            return true;
        }
        else
        {
            return false;
        }
    });
}

تخضع هذه الشيفرة البرمجيّة لنفس المبدأ: يبحث استعلام LINQ to Objects عن جهة الاتصال مطلوبة ضمن جهات الاتصال الموجودة ضمن المتغيّر contacts حسب قيمة الخاصيّة Id لجهة الاتصال، وبعد الحصول عليها، يتم تحديث قيم الخصائص الأخرى كما هو واضح.

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

الخلاصة

تناولنا في هذا الدرس كيفيّة بناء نموذج المستودع الذي يمثّله الصنف MemoryContactsRepository والذي يحقّق كما نعلم الواجهة IContactsRepository حيث تعرّفنا على التوابع الأربعة ضمنه التي تسمح لمكوّنات التطبيق بالتعامل مع البيانات دون الاهتمام بمكان وجود هذه البيانات. اطلعنا أيضًا على كيفيّة توظيف تقنيّة الاستعلام LINQ to Objects ضمن التوابع الأربعة لتنفيذ المهام المطلوبة من هذا المستودع. سنبدأ في الدرس التالي ببناء واجهات التطبيق.

 

حقوق الصورة البارزة محفوظة لـ Freepik


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...