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.
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>
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>
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(); } } }
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; } }
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; } } }
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; } }
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; } } }
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; } }
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.
18.223.108.105