Creating a news feed service

In this practical example, we will put together all the knowledge acquired during the first three recipes in order to create a news feed service. This news feed service will first populate a ListView element of news, and then will periodically check if fresh news is available regardless of whether the activity is focused or not.

Getting ready

To follow this recipe, create a new project named NewsFeedService.

How to do it...

  1. Create a layout named Welcome.xml and update it to the following:
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android= "http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent">
      <ListView
        android:id="@+id/feedItemsListView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>
    </LinearLayout>
  2. Update the Main layout to the following (see Chapter 6, Populating Your GUI with Data, for the details):
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="100dp"
      android:gravity="center_vertical"
      android:orientation="horizontal">
      <LinearLayout
        android:layout_width="100dp"
        android:layout_height="fill_parent"
        android:gravity="center">
      <ImageView
        android:id="@+id/image"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:gravity="center"
        android:src="@drawable/Icon"
        android:contentDescription=""
        android:layout_gravity="center"/>
      </LinearLayout>
      <LinearLayout
        android:layout_width="0dip"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:orientation="vertical"
        android:gravity="center_vertical">
      <TextView
        android:id="@+id/title"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Title"
        android:textColor="#ffffff"
        android:textSize="16dp"
        android:gravity="top"
        android:textStyle="bold"/>
      <TextView
        android:id="@+id/creator"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Author"
        android:textSize="12dp"
        android:textColor="#808080"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp"/>
      <TextView
        android:id="@+id/pubDate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Date"
        android:textSize="12dp"
        android:padding="2dp"/>
      </LinearLayout>
    </LinearLayout>
  3. In the MainActivity class, update the method OnCreate()in order to bind to a service using an intent filter "ca.services.NewsService" and display a progress dialog. The method OnStart() populates the ListView instance of news using the NewsParser.ParseMethod() method, and it owns a static method to refresh the list view:
    namespace NewsFeedService {
      [Activity (Label = "NewsFeedService", MainLauncher=true)]
      public class MainActivity : Activity {
        //private variables
        private NewsServiceBinder _myBinder;
        private Boolean _isBound;
        private ProgressDialog _progressDialog;
        private static List<NewsItem> news = new List<NewsItem>();
        private ListView newsItemsListView;
        NewsFeedServiceConnection cnx;
    
        //getter and setter
        public NewsServiceBinder Binder {
          get { return_myBinder; }
          set { _myBinder=value; }
        }
    
        public Boolean IsBound {
          get { return_isBound;}
          set { _isBound=value; }
        }
    
        protected override void OnCreate (Bundle bundle) {
          base.OnCreate (bundle);
    
          //Set our view from the "Welcome" layout resource
          SetContentView(Resource.Layout.Welcome);
          newsItemsListView = this.FindViewById<ListView>(Resource.Id.feedItemsListView);
    
          //Start and bind to the service
          Intent myServiceIntent = new Intent("ca.services.NewsService");
          StartService (myServiceIntent);
          cnx = new NewsFeedServiceConnection (this);
          BindService (myServiceIntent, cnx, Bind.AutoCreate);
    
          //Display a progress dialog
          this.progressDialog = new ProgressDialog(this);
          this.progressDialog.SetMessage("PleaseWait...");
          this.progressDialog.Show();
        }
    
        protected override void OnStart() {
          base.OnStart ();
          this.BootStrap ();
          this.progressDialog.Dismiss ();
        }
    
        public void boostrap() {
    
          Log.Debug ("Main", "bootstrap");
    
          news = NewsParser.parse ();
    
          //Populate the adapter with the results from theparser
          var adapter = new NewsAdapter(this, news);
          newsItemsListView.Adapter = adapter;
          newsItemsListView.ItemClick += OnListViewItemClick;
          Log.Debug ("Main", "bootstrapend");
        }
    
        //accepts new items
        public static void Refresh(List<NewsItem> fresh_news) {
          news = fresh_news;
        }
    
        protected void OnListViewItemClick(object sender,AdapterView.ItemClickEventArgs e) {
          //Do Something
          var t = news[e.Position];
          Android.Widget.Toast.MakeText(this, t.Link,Android.Widget.ToastLength.Short).Show();
        }
    
      }
    }
  4. Create a NewsItem class to represent the news:
    public class NewsItem {
    
      public NewsItem() {
      }
    
      public string Title { get;set; }
    
      public string Link { get;set; }
    
      public DateTime PubDate { get;set; }
    
      public string Creator { get;set; }
    
      public string Category { get;set; }
    
      public string Description { get;set; }
    
      public string Content { get;set; }
    
    }
  5. Create NewsAdapter to map NewsItem into ListView:
    namespace NewsFeedService {
      //Details in Chapter - Populating your GUI with Data
      public class NewsAdapter : BaseAdapter<NewsItem> {
        protected Activity context = null;
        protected List<NewsItem> feedsList = new List<NewsItem>();
    
        public NewsAdapter (Activity context, List<NewsItem> feedsList)
        : base() {
          this.context = context;
          this.feedsList = feedsList;
        }
    
        public override NewsItem this[int position] {
          get { return this.feedsList[position]; }
        }
    
        public override long GetItemId(int position) {
          return position;
        }
    
        public override int Count {
          get { return this.feedsList.Count; }
        }
    
        public override View GetView(int position, View convertView, ViewGroup parent) {
          var feedItem = this.feedsList[position];
    
          var view = (convertView ?? context.LayoutInflater.Inflate(Resource.Layout.Main, parent, false))asLinearLayout;
    
          //Cut the title if too long
          view.FindViewById<TextView>(Resource.Id.title).Text = feedItem.Title.Length<51?feedItem.Title : feedItem.Title.Substring(0, 53)+"...";
          view.FindViewById<TextView>(Resource.Id.creator).Text = feedItem.Creator;
          view.FindViewById<TextView>(Resource.Id.pubDate).Text = feedItem.PubDate.ToString("dd/MM/yyyyHH:mm");
          return view;
        }
      }
    }
  6. Create a NewsParser class to parse an rss feed and retrieve the news:
    namespace NewsFeedService {
      public class NewsParser {
        //Private variables
        private static List<NewsItem> news = new List<NewsItem>();
        private static DateTime last = System.DateTime.Now;
    
        //Check if a fresh news is available
        public static Boolean CheckItem(DateTime date) {
          //XMl Document
          XmlDocument xmlDocument = new XmlDocument();
          Stream stream = getStream();
          xmlDocument.Load (stream);
    
          //Retrieve the nodes
          XmlNodeList itemNodes = xmlDocument.SelectNodes("rss/channel/item");
          XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDocument.NameTable);
          nsmgr.AddNamespace("dc", xmlDocument.DocumentElement.GetNamespaceOfPrefix("dc"));
          nsmgr.AddNamespace("content", xmlDocument.DocumentElement.GetNamespaceOfPrefix("content"));
    
          //If the first news is newer than the last batch
          if (System.DateTime.Compare (Convert.ToDateTime(itemNodes [0].SelectSingleNode ("pubDate")),last)<0) {
            parse (stream);
            return true;
          }
          else {
            return false;
          }
    
        }
    
        //Construct a web request and provide a stream of data out of it
        private static Stream GetStream() {
    
          WebRequest webRequest = WebRequest.Create("http://feeds.feedburner.com/canadiannews");
          WebResponse webResponse = webRequest.GetResponse();
    
          return webResponse.GetResponseStream();
        }
    
        public static List<NewsItem> Parse(Stream stream = null) {
          XmlDocument xmlDocument = new XmlDocument();
    
          if (stream == null) {
            stream = getStream();
          }
    
          //Assign the variable of NewsItem with the data of the stream.
          for (int i = 0; i < itemNodes.Count; i++) {
            NewsItem newsItem = new NewsItem();
    
            if (itemNodes[i].SelectSingleNode("title") != null){
              newsItem.Title = itemNodes[i].SelectSingleNode("title").InnerText;
            }
    
            if (itemNodes[i].SelectSingleNode("link") != null){
              newsItem.Link = itemNodes[i].SelectSingleNode("link").InnerText;
            }
    
            [……]
    
            newsItem.Content = itemNodes[i].SelectSingleNode("content:encoded", nsmgr).InnerText;
          }
          else {
            newsItem.Content = newsItem.Description;
          }
    
          news.Add(newsItem);
        }
    
      return news;
    
      }
    }
  7. Create a NewsService class:
    namespace NewsFeedService {
      [Service]
      [IntentFilter(newString[]{"ca.services.NewsService"})]
      public class NewsService : Service {
    
        IBinder _myBinder = null;
    
        //invoked on Start of the service
        public override StartCommandResult OnStartCommand(Android.Content.Intentintent, StartCommandFlagsflags, int startId) {
          Log.Debug ("NewsFeedService", "StartCommandResult");
          this._myBinder = new NewsServiceBinder (this);
          check_update ();
          return StartCommandResult.Sticky;
        }
    
        public void CheckUpdate() {
          //Recursivly check (every5seconds) if a fresh news need to be displayed
          Thread t = new Thread(()=> {
            Thread.Sleep (5000);
            if (NewsParser.check_item(System.DateTime.Now)) {
    
              var nMgr = (NotificationManager) GetSystemService(NotificationService);
    
              //Details of notification in previous recipe
              Notification.Builderbuilder = new Notification.Builder (this)
              .SetContentTitle ("AvailableNews")
              .SetTicker ("AvailableNews")
              .SetContentText ("Checkitout")
              .SetSmallIcon (Resource.Drawable.Icon);
    
              nMgr.Notify (0, builder.Notification);
            }
          });
          t.Start();
        }
    
        public override IBinder OnBind (Intent intent) {
          Log.Debug ("NewsFeedService", "NewClient");
          return _myBinder;
        }
    
      }
    
    }
  8. Create a NewsServiceBinder and a NewsServiceConnection:
    public class NewsServiceBinder : Binder {
      private NewsService _myBoundService;
    
      public NewsService NewService {
        get{ return_myBoundService; }
      }
    
      public NewsServiceBinder (NewsService _myBoundService) {
        this._myBoundService = _myBoundService;
        Log.Debug ("MyBoundServiceBinder", "NewBinder");
      }
    }
    
    public class NewsFeedServiceConnection : Java.Lang.Object,IServiceConnection {
      MainActivity _myActivity;
    
      public NewsFeedServiceConnection (MainActivity activity){
        this._myActivity = activity;
      }
    
      public void OnServiceConnected (ComponentName name,IBinder service) {
        Log.Debug ("NewsFeedServiceConnection", "IncomingConnection");
        var boundServiceBinder = service as NewsServiceBinder;
        if (boundServiceBinder != null) {
          _myActivity.Binder = boundServiceBinder;
          _myActivity.isBinded = true;
          _myActivity.boostrap ();
        }
      }
    
      public void OnServiceDisconnected (ComponentName name) {
        _myActivity.isBinded = false;
      }
    }
  9. Start your application. You'll receive a notification, shown as follows:
    How to do it...
  10. Open the notification, and the news will be displayed:
    How to do it...

How it works...

The NewsService instance is a combination of what you learned so far. Therefore, we don't have any technical explanation here. However, the sequence of calls and the orchestration between the numerous objects requires some details.

The NewsService project displays a custom ListView of NewsItem (see Chapter 6, Populating Your GUI with Data) for a reminder. This Listview element is first populated with the construction of a web request, which allows us to create an XML document. Every child of the document will be parsed by NewsParser and will be transformed into a NewsItem. After this starting phase, NewsService, which is bound to the MainActivity via NewsServiceBinder and NewsServiceConnection, really kicks in. Indeed, the NewsService project owns an infinite loop based on a thread that will check, every 50 seconds, whether fresh news is available:

public void check_update() {
  //Recursivly check (every 5 seconds) if a fresh news need to be displayed
  Thread t = new Thread(()=> {
    Thread.Sleep (5000);
    if (NewsParser.check_item(System.DateTime.Now)) {

      var nMgr = (NotificationManager) GetSystemService(NotificationService);

      //Details of notification in previous recipe
      Notification.Builderbuilder = new Notification.Builder (this)
      .SetContentTitle ("AvailableNews")
      .SetTicker ("AvailableNews")
      .SetContentText ("Checkitout")
      .SetSmallIcon (Resource.Drawable.Icon);

      nMgr.Notify (0, builder.Notification);
    }
  });
  t.Start();
}

If so, the NewsParser instance will send a notification to the user. The ParseNews class will parse the news again and update the ListView of the MainActivity class.

See also

See the next chapter to learn how to use intents.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.218.230