feeds-reader تطبيق عملي: تطبيق قارئ الخلاصات - الجزء الأوّل


حسام برهان

سنتناول في هذا الدرس من سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms تطبيقًا عمليًا آخرًا وهو تطبيق قارئ الخلاصات من موقع أكاديمية حسوب. تندرج مثل هذه التطبيقات تحت النوع rss feed reader ولها مزايا كثيرة ومتنوّعة. سيكون تطبيقنا بسيطًا وعمليًّا ويوضّح مبدأ العمل كما جرت العادة.

main2.png


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

ماهي الخلاصات؟

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

fig01.png

لاحظ أنّني أستعرض هذه الخلاصات عن طريق متصفّح Chrome عن طريق الرابط:
https://academy.hsoub.com/programming/?rss=1
وهو الرابط الذي توفّره الأكاديميّة للحصول على خلاصات مقالات البرمجة. من الواضح أنّ هذا الأسلوب غير عملي في الحصول على آخر الخلاصات، لذلك يلجأ المستخدمون عادةً إلى تطبيقات متنوّعة لعرض هذه الخلاصات بشكل مريح ومقروء.

تطبيق قارئ الخلاصات

سنبني في هذا الدرس تطبيق قارئ خلاصات وظيفته قراءة الخلاصات الموجودة في قسم مقالات البرمجة في موقع أكاديميّة حسّوب. وسنعمل في الدرس التالي على تحسين هذا التطبيق بإتاحة الإمكانيّة لتصفّح الخلاصات من جميع الأقسام.
أنشئ مشروعًا جديدًا من النوع Blank App (Xamarin.Forms Portable) وسمّه BasicFeedReader ثم أبق فقط على المشروعين BasicFeedReader (Portable) و BasicFeedReader.Droid كما وسبق أن فعلنا في هذا الدرس.

الصنف FeedItem

من نافذة مستكشف الحل Solution Explorer انقر بزر الفأرة الأيمن على المشروع BasicFeedReader واختر من القائمة التي ستظهر الخيار Add ثم من القائمة الفرعية الخيار New Folder لإضافة مجلّد جديد. سمّ هذا المجلّد بالاسم Entities، وبعد أن يظهر في نافذة الحل Solution Explore انقر عليه بزر الفأرة الأيمن واختر الخيار Add ومن القائمة الفرعية اختر Class. ستظهر نافذة تسمح لك بتعيين اسم لهذا الصنف. اختر الاسم FeedItem له. هذا الصنف هو حجر البناء الأساسي لهذا البرنامج. احرص على جعل محتويات الملف FeedItem.cs كما يلي:

namespace BasicFeedReader.Entities
{
    public class FeedItem
    {
        public string Link { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }

        public override string ToString()
        {
            return Title;
        }
    }
}

الخصائص Link و Title و Description في هذا الصنف تقابل العناصر link و title و description في مستند XML. أمّا التابع ToString فهو للحصول على تمثيل نصّي لأي كائن من النوع FeedItem.

الواجهات

أضف مجلّدًا جديدًا للمشروع BasicFeedReader كما فعلنا قبل قليل، وسمّه Pages. ثم أضف إليه صفحتي محتوى تعتمدان رماز XAML بحيث يكون اسم الصفحة الأولى هو FeedDetailsPage وهي الواجهة الرئيسيّة، واسم الصفحة الثانية SectionFeedsPage وهي واجهة التفاصيل.
الصفحة SectionFeedsPage هي الواجهة التي ستعرض خلاصات قسم مقالات البرمجة في أكاديمّية حسوب. أمّا الواجهة FeedDetailsPage فهي لعرض الخلاصة التي يتم اختيارها من الواجهة SectionFeedsPage.

الواجهة SectionFeedsPage

انتقل إلى الملف SectionFeedsPage.xaml واحرص على أن تكون محتوياته على الشكل التالي:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="BasicFeedReader.Pages.SectionFeedsPage">
  <StackLayout>
    <ListView x:Name="lsvFeeds"
              ItemTapped="lsvFeeds_ItemTapped"/>
  </StackLayout>

</ContentPage>

لاحظ كم هو بسيط هذا الرماز، فهو لا يحتوي سوى عنصر قائمة lsvFeeds لعرض خلاصات قسم مقالات البرمجة، حيث سنربط الحدث ItemTapped الخاص بها بالمعالج lsvFeeds_ItemTapped. انتقل الآن إلى ملف الشيفرة البرمجيّة الموافق SectionFeedsPage.xaml.cs واحرص على أن تكون محتوياته على الشكل التالي:

using BasicFeedReader.Entities;
using System.Linq;
using System.Xml.Linq;
using Xamarin.Forms;

namespace BasicFeedReader.Pages
{
    public partial class SectionFeedsPage : ContentPage
    {
        public SectionFeedsPage()
        {
            InitializeComponent();

            LoadFeeds("https://academy.hsoub.com/programming/?rss=1");

            Title = "مقالات البرمجة";
        }

        private async void lsvFeeds_ItemTapped(object sender, ItemTappedEventArgs e)
        {
            FeedItem selectedFeed = (FeedItem)e.Item;
            FeedDetailsPage feedDetailsPage = new
                FeedDetailsPage(selectedFeed);

            await Navigation.PushAsync(feedDetailsPage);
        }

        private void LoadFeeds(string resource)
        {

            XDocument doc = XDocument.Load(resource);

            var feeds = from newsItem in doc.Descendants("item")
                       select new FeedItem
                       {
                           Title = newsItem.Element("title").Value,
                           Description = newsItem.Element("description").Value,
                           Link = newsItem.Element("link").Value
                       };

            lsvFeeds.ItemsSource = feeds;
        }
    }
}

عند يتم إنشاء كائن من هذه الصفحة لعرضها للمستخدم، يتم تنفيذ بانيتها. حيث يعمل التطبيق على استدعاء التابع LoadFeeds والذي يتطلّب وسيطًا واحدًا من النوع string ويمثّل عنوان المصدر المزوّد للخلاصات. في الحقيقة ما فعلناه هنا هو أمر غير جيّد من الناحية العمليّة، فلا ينبغي وضع مثل هذا الاستدعاء هنا في البانية، سيما وأنّ التابع LoadFeeds لا يستخدم تقنية البرمجة غير المتزامنة، مما سيسبب جمودًا مزعجًا في التطبيق عند أوّل تشغيله. سنحل هذه المشكلة لاحقًا في الجزء الثاني.
انظر الآن إلى تعريف التابع LoadFeeds من الشيفرة السابقة. ستلاحظ أنّه يستخدم تقنيّة ممتازة يوفرها إطار العمل .NET من خلال الصنف XDocument الذي يمثّل مستند XML بشكل كائني، حيث يعمل السطر الأوّل من هذا التابع على إنشاء كائن جديد من الصنف XDocument من خلال تحميل مستند XML مباشرةً من الانترنت عن طريق التابع الساكن Load.
بعد ذلك يتم إعراب مستند XML المحمّل من الإنترنت بسرعة وفعاليّة عاليتين. حيث تحتاج إلى عدد قليل من الأسطر البرمجيّة على شكل استعلام LINQ to XML لكي تقوم بالمطلوب. لقد اطلعنا على تقنيّة شبيهة في درس سابق حيث استخدمنا LINQ to Objects. يمكنك معرفة المزيد حول هذا الموضوع بقراءة هذا المقال.
بعد استخلاص المعلومات من مستند XML المُحمّل، وإنشاء كائن من النوع FeedItem لكل خلاصة موجودة في هذا المستند، يتم إسناد المجموعة المنشأة من هذه الكائنات إلى الخاصيّة ItemsSource من القائمة lsvFeeds ليتم عرضها للمستخدم.
أمّا بالنسبة لمعالج الحدث lsvFeeds_ItemTapped من الشيفرة السابقة. فوظيفته بسيطة، وهي تنحصر في الحصول على كائن FeedsItem الموجود ضمن العنصر الذي تمّ نقره (لمسه) ضمن القائمة lsvFeeds ومن ثمّ الانتقال إلى الصفحة FeedDetailsPage لعرض محتوى هذه الخلاصة.

الواجهة FeedDetailsPage

انتقل إلى الملف FeedDetailsPage.xaml واحرص على أن تكون محتوياته على الشكل التالي:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="BasicFeedReader.Pages.FeedDetailsPage">

  <StackLayout Orientation="Vertical">

    <WebView x:Name="wvDescription"
             VerticalOptions="FillAndExpand"
             HorizontalOptions="FillAndExpand"/>
  </StackLayout>

</ContentPage>

لاحظ مرّة أخرى كم هو بسيط هذا الرماز. حيث يقتصر على استخدام عنصر واحد، هو العنصر WebView ويُستخدم لعرض محتوى مستند HTML. وسبب استخدامي لهذا العنصر، هو أنّ الخلاصات التي ترد من موقع أكاديميّة حسّوب تحتوي على المحتوى كاملًا وهو منسّق بتنسيق HTML. وهذا أمر لا تجده في كثير من مزوّدات خدمة الخلاصات. لذلك فوجدت أنّ أفضل طريقة هو عرض هذا المحتوى مباشرةً ضمن عنصر WebView.
انتقل الآن إلى ملف الشيفرة البرمجيّة الموافق FeedDetailsPage.xaml.cs واحرص على أن يكون كما في الشكل التالي:

using Xamarin.Forms;
using BasicFeedReader.Entities;

namespace BasicFeedReader.Pages
{
    public partial class FeedDetailsPage : ContentPage
    {
        public FeedDetailsPage(FeedItem feedItem)
        {
            InitializeComponent();

            this.Title = feedItem.Title;

            var descriptionHtmlSource = new HtmlWebViewSource();
            descriptionHtmlSource.Html = @"<html dir='rtl'><body>" + feedItem.Description + "</body></html>";
            wvDescription.Source = descriptionHtmlSource;
        }
    }
}

كل الشيفرة البرمجيّة موجودة ضمن البانية التي تتطلّب وسيطًا واحدًا من النوع FeedItem الذي يحتوي على بيانات الخلاصة المراد عرض تفاصيلها. نعمل على وضع عنوان هذه الصفحة ليكون مطابقًا لعنوان الخلاصة، ثمّ ننشئ كائنًا من النوع HtmlWebViewSource نسنده ضمن المتغيّر descriptionHtmlSource الذي سيمثّل الكائن المحتوى للعنصر wvDescription (عنصر WebView الذي صرّحنا ضمن ملف الرماز). لاحظ كيف أسندنا للخاصيّة Html لهذا المتغيّر محتوى الخاصيّة Description لكائن الخلاصة، وهو كما أشرنا قبل قليل عبارة عن مستند HTML ينقصه فقط الوسمين و اللذان أضفناهما يدويًّا. ثمّ نُسند المتغيّر descriptionHtmlSource بدوره إلى الخاصيّة Source للعنصر wvDescription مما يؤدّي إلى ظهور مستند HTML كما هو مخطّط له.

ملف التطبيق App.cs

انتقل إلى ملف التطبيق App.cs واحرص على أن تكون بانية الصنف App على الشكل التالي:

public App()
{
    // The root page of your application
    MainPage = MainPage = new NavigationPage(new SectionFeedsPage());
}

احرص على استخدام فضاء الاسم Pages لكي تستطيع الوصول إلى الصفحة SectionFeedsPage كما يلي:

using BasicFeedReader.Pages;

ستضع السطر السابق أوّل الملف App.cs كما هو معلوم. نفّذ البرنامج، ستحصل على شكل شبيه بما يلي:

fig02.png

وهي الواجهة التي تحتوي على الخلاصات الموجودة ضمن قسم مقالات البرمجة. جرّب اختيار أحد هذه الخلاصات لتحصل على شكل شبيه بما يلي:

fig03.png

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

الخلاصة

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





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


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



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن