Data Points

RSS Feeds on a Smartphone

John Papa

Code download available at: Data Points 2006_12.exe(172 KB)

Contents

Preparing for Mobile Development
The News Reader Application
Asynchronous Feed Requests
Loading a DataSet
Loading the List of Feeds
Sorting with Anonymous Delegates
Displaying with the WebBrowser Control
Deploying to a Smartphone
Conclusion

Mobile applications are a lot like other applications. They rely on data, they often communicate data over a connection, and the data needs to be managed. As mobile applications become more sophisticated, so do the tools we use for developing them. In fact, when developing data-centric mobile apps using the Microsoft® .NET Compact Framework 2.0, you rely on many of the same skills that you use to develop Windows® Forms apps. With the .NET Compact Framework, developers can use ADO.NET and other familiar libraries to manage data in a mobile device.

Developing for a Smartphone introduces yet another level of design consideration, such as accounting for the small screen size. And its built-in Internet connectivity influences application design as the ability to send and receive data lends itself to hosting applications that communicate with Web servers.

This month I will demonstrate how to build a Windows Mobile® 5.0 application that reads RSS feeds and loads them into an ADO.NET DataSet. I will start out by showing how to request an RSS feed from a URL asynchronously and then demonstrate how to read the RSS feed and load it into a DataSet. Next, I will show you how to navigate the DataSet's hierarchy to find the appropriate data containing each feed's items. Then I will discuss how to use anonymous delegates to sort the data, how to check for the existence of a field in the RSS data using ADO.NET, and how to use the WebBrowser control to navigate to a URL or display HTML. Finally, I will demonstrate how to deploy the application to a mobile device using a CAB project. The full source code for this sample application is available from the MSDN®Magazine Web site.

Preparing for Mobile Development

Developing mobile applications with the .NET Compact Framework 2.0 and Visual Studio® 2005 is very similar in many aspects to developing ASP.NET or Windows Forms applications. Before diving into the code, however, you'll need to do some setting up. Assuming you already have Visual Studio 2005 installed, you should make sure you also have the following installed, or download them as necessary from the Microsoft Web site:

Even if you are not going to use an actual Smartphone device during development and instead will rely on the emulator, you still need to install the latest version of ActiveSync®. This is used for such tasks as deploying the application to the emulator and allowing the emulator to connect to the Internet. The Windows Mobile SDK 5.0 for Smartphone adds a few project types to Visual Studio 2005 that are specific to Smartphone development. (There is also an SDK for Pocket PC development.) Finally, you should install the emulator that most closely resembles the target devices. (My application is targeting Smartphone devices with a 320×240 landscape screen.)

A word about emulators. If you have a mobile device, I highly recommend testing your application on it, rather than relying entirely on the emulator. While an emulator is a great tool, the best test is always to run the application on a physical device. When I develop a mobile application, I use the emulator but also deploy the application to a physical device at certain checkpoints to make sure everything is running smoothly in both environments.

Before running the code, you need to start the Device Emulator Manager from the Tools menu of Visual Studio. A list of available emulators will appear. Choose the 320×240 (Landscape) Windows Mobile 5.0 Smartphone Emulator. If you haven't downloaded this option, you can make do with another Windows Mobile 5.0 Smartphone emulator. The code in this column is written for a 320×240 landscape screen, but it could easily be modified to fit other resolutions.

Once you choose the emulator, right-click on it and select Connect. The emulator will then begin to boot the Smartphone. Once the Smartphone is running in the emulator, right-click the item in the Device Manager again and select Cradle. Cradling the emulator's Smartphone will allow it to talk to ActiveSync, which in turn allows it to connect to your network so it can access the Internet and grab the RSS feeds. Finally, in Visual Studio, select the 320×240 emulator from the dropdown list in the Device toolbar. This tells Visual Studio which emulator image to use when you debug and deploy.

The News Reader Application

Before diving into code, let's look at what the application does from a high level. When the application launches, it requests the RSS feeds asynchronously from their respective Web sites and loads a ListView control that provides the title of each feed and the number of items that each feed returns (see Figure 1).

Figure 1 List of RSS Feeds

Figure 1** List of RSS Feeds **

Figure 2 Feed Item List

Figure 2** Feed Item List **

Figure 3 Item Description

Figure 3** Item Description **

When you select a feed and either click the enter soft key (the middle key on the Smartphone) or choose Menu | Select, the selected feed's list of items is displayed in another form's list view, sorted with the most recent first. If the RSS feed contains the item's publication date (which is an optional element in the RSS schema), it will be displayed in the second column of the list view (see Figure 2). If there is no publication date, then the column is omitted and only the title of the item is displayed.

Once you select an item, its description is displayed in another screen. Any content within the item's description element is displayed in a WebBrowser control (see Figure 3).

The item description can contain hyperlinks or images since it reads the description element from the RSS feed. If it contains a URL, the user can click the URL and navigate to that address. If the user clicks the Select menu item with the soft key while viewing the item's description, the application switches to another screen and navigates to the URL of the selected item, loading the content into a WebBrowser control so the entire post can be viewed. The user can always navigate backwards to a previous screen by clicking the Back soft key. And from the Feed Item List screen, the user can select Done from the menu using the Left soft key to exit the application.

This application demonstrates the basic abilities you need to include in an application of this type. However, there are several features and enhancements you can include to make the app more sophisticated, such as progress bars, icons, and the ability to manage the feed list. Feel free to download the code and add your own enhancements.

Asynchronous Feed Requests

Now that I have shown how the application functions, let's take a look under the hood at the code. When the application loads, it first requests three RSS feeds (for brevity, I have hardcoded these URLs in a List<string>), looping through the registered URLs and requesting the feed for each one by calling the BeginScanFeeds method:

   List<string> urlList = new List<string>();
   urlList.Add(
     "http://msdn.microsoft.com/msdnmag/rss/        rss.aspx?Sub=Data+Points");
   urlList.Add("http://blogs.msdn.com/MainFeed.aspx");
   urlList.Add("http://blogs.technet.com/MainFeed.aspx");

   ClearList();
   foreach (string url in urlList) BeginScanFeeds(url);
 

The BeginScanFeeds method creates an instance of the HttpWebRequest class for the specified URL. The request is then made asynchronously for the given URL using the HttpWebRequest's BeginGetResponse method. This method accepts an asynchronous callback delegate (which in this case is the EndScanFeeds method) and the request:

   public void BeginScanFeeds(string url)
   {
       HttpWebRequest request = 
          (HttpWebRequest)WebRequest.Create(url);
       IAsyncResult asyncResult = request.BeginGetResponse(
           new AsyncCallback(EndScanFeeds), request);
   }
   

Each request is sent and when the response returns, the EndScanFeeds method is invoked. The EndScanFeeds method has a single argument of type IAsyncResult, which can be used to retrieve the feed. Figure 4 shows a snippet of the code from this method that puts the response from the resulting HttpWebResponse object into a string named feedData using a StreamReader object. The feedData string contains the RSS feed's XML and is passed to the LoadFeedList method, which loads the feed data into a ListView control. Because the feeds are read asynchronously and the LoadFeedList method accesses the ListView control, the method needs to be called using the Control.Invoke method.

Figure 4 Reading the Feeds

public void EndScanFeeds(IAsyncResult result)
{
    string feedData;
    HttpWebRequest request = (HttpWebRequest)result.AsyncState;
    using(HttpWebResponse response = 
        (HttpWebResponse)request.EndGetResponse(result))
    using(StreamReader streamReader = 
        new StreamReader(response.GetResponseStream()))
    {
        feedData = streamReader.ReadToEnd();
    }

    Invoke(new LoadFeedListDelegate(LoadFeedList), feedData);
}

Loading a DataSet

Once the feed has been retrieved, its contents must be parsed. While there are several ways to read an XML string, I have chosen to load the data into a DataSet, using the following code:

   DataSet ds = new DataSet();
   XmlTextReader xmlRdr = 
      new XmlTextReader(new StringReader(rssXmlData));
   ds.ReadXml(xmlRdr);

I've opted for this approach because it allows me to utilize such capabilities as checking for the existence of columns and navigating the RSS feed's hierarchy.

The feed's contents are loaded into a StringReader object, which is then used to create an XmlTextReader. The XmlTextReader can then be read by the DataSet's ReadXml method, which loads the contents of the feed into the DataSet. The RSS schema includes three main elements in the hierarchy: rss, channel, and item. (Some schemas contain more elements, such as an images element.)

The rss element is the root of the RSS feed, which contains one or more channel elements. The channel elements represent the feed and can contain a series of child elements that describe the feed (such as title, link, description, and item). The item element is a required child of the channel element. (This, by the way, is not the same as the main item element.) Item elements represent each item in the feed. For example, in the MSDN Blog, the channel is the MSDN Blog while the items represent the posts.

When an RSS feed is loaded into a DataSet, the main elements-rss, channel, and item-are loaded as tables. Each level of the hierarchy becomes a DataTable and can be read and navigated using the DataTable's features. The simple child elements that describe each main element are loaded as columns in the respective DataTable. For example, the channel DataTable contains a DataColumn for the item, description, and link elements (all three of these elements are required).

Finally, a DataRelation is created between the channel DataTable and the item DataTable representing how a channel contains zero or more items. I exploit this relationship so the application can locate a specific channel's items when a user selects a specific feed.

Loading the List of Feeds

Once the feed is loaded into a DataSet, I locate the channel table and grab the channel's title and the number of items contained in each channel. I store this information in an array and load it into the list view as the feeds are received (see Figure 5).

Figure 5 Loading the List of Feeds

foreach (DataRow channelRow in ds.Tables["channel"].Rows)
{
    string title = channelRow["title"].ToString();
    int itemCount = int.Parse(channelRow.GetChildRows(
        "channel_item").Length.ToString());

    string[] displayInfo = new string[2];
    displayInfo[0] = title;
    displayInfo[1] = itemCount.ToString();
    
    ListViewItem item = new ListViewItem(displayInfo); 
    item.Tag = channelRow;
    lvFeed.Items.Add(item); 
}

The title of the channel is accessed using the standard ADO.NET syntax to read the value from a DataRow's column. To get the total number of items in each channel I use the DataRow's GetChildRows method to return an array of DataRow objects for the current channel row's items. The GetChildRows method accepts the name of the DataRelation, which by default is the concatenation of the channel DataTable's name and the item DataTable's name, with an underscore between them. Notice that I store a reference to the channel's DataRow in the tag of the ListViewItem. This makes it easy to grab the feed's information when a user selects a particular feed.

Sorting with Anonymous Delegates

When a user selects a feed, the DataRow is passed to the FeedItemList form, which then loads the list of items into a ListView control. I load the title of the item as well as the publication date, if it is provided. Since the pubDate element is not a required element of the RSS 2.0 schema, I first check to see if the column exists in the item DataTable. Since I have an array of DataRow objects that represent the feed's items, I can use that to find the DataRows' DataTable object and then use the DataTable.Columns.Contains method to check whether the pubDate column exists. It may sound like a lot to do, but the code is quite simple, as shown here:

bool containsPubDate = (itemRows[0].Table.Columns.Contains("pubDate"));

Once I determine whether the pubDate exists, I can then determine how to sort and display the items in the list view. If the pubDate exists, I sort the items by date in descending order. If there is no pubDate, I sort the items by title and hide the publication date column in the list view.

The following code executes if the pubDate exists:

colNewsItemName.Width = 170;
colPubDate.Width = 140;

Array.Sort(itemRows, delegate(DataRow row1, DataRow row2)
{
    DateTime pubDate1 = ParseDateTime(row1["pubDate"].ToString());
    DateTime pubDate2 = ParseDateTime(row2["pubDate"].ToString());
    return pubDate2.CompareTo(pubDate1);
});

It sets the width of the columns so the app will display both the item's title and publication date. It then uses the Array.Sort method to sort the array of DataRow objects. Before I can sort the dates, I have to convert them to a valid DateTime. Once this conversion is done I use a custom comparer delegate to sort the DataRow array.

The custom comparer delegate is a nice, short way to code the sorting routine. I could have created an explicit routine to handle the sorting, but since I wanted to minimize the lines of code, the custom comparer delegate was a good option. The comparison of the dates is executed for each element comparison in the array. In this example I create an anonymous delegate that accepts two DataRow objects and compares them by evaluating the values in their pubDate columns.

If the pubDate column does not exist, I hide the second column in the list view and expand the first column to fill the screen width. Then I sort the DataRow array by the item title in ascending order. I use the same technique in this case, except this time I compare string values:

colNewsItemName.Width = 310;
colPubDate.Width = 0;

Array.Sort(itemRows, delegate(DataRow row1, DataRow row2)
{
    return row1["title"].ToString().CompareTo(row2["title"].ToString());
});

The anonymous method provides a great way to help reduce code when you do not need to reuse the comparer functionality.

Once the DataRow array is sorted, I loop through the items and grab the title and the pubDate (if it exists), as shown in Figure 6. If the pubDate exists, I format it accordingly and load the title and pubDate into a string array, which I then use to load the list view. At this point, the list looks like the one shown in Figure 2.

Figure 6 Loading the Item List

foreach (DataRow itemRow in itemRows)
{
    string title = itemRow["title"].ToString();
    string[] displayInfo = new string[2];
    displayInfo[0] = title;
    if (containsPubDate)
    {
        string pubDateString = 
            itemRow.Table.Columns.Contains("pubDate") ? 
                itemRow["pubDate"].ToString() : string.Empty;
        DateTime pubDate = ParseDateTime(pubDateString);
        displayInfo[1] = pubDate.ToString("MM/dd/yyyy HH:mm");
    }

    ListViewItem item = new ListViewItem(displayInfo);
    item.Tag = itemRow;
    lvFeed.Items.Add(item);
}

Displaying with the WebBrowser Control

When the user selects an item, the selected ListViewItem is examined and the DataRow of the selected item is retrieved from its Tag property. The DataRow that contains the selected item is passed to the constructor for the ItemSummary form, which displays the description of the selected item. Note that I did not use a label control to display the description because the descriptions of an item in an RSS feed often contains embedded HTML. Thus I used the WebBrowser control to display the description.

Figure 3 shows the summary of an item's description in the WebBrowser control. You can see that the summary description includes hyperlinks and other HTML elements. To render the HTML, I set the WebBrowser control's DocumentText property to the item's description and the Form's Text property to the title of the item:

this.Text = itemRow["title"].ToString();
webSummary.DocumentText = itemRow["description"].ToString();

In this application, the WebBrowser control serves two purposes. It is used by the ItemSummary form to display the description of the item, which may or may not contain HTML. It is also used by the Item form, which navigates to the link specified in the item's DataRow. This is the more conventional way to use the WebBrowser control:

this.Text = itemRow["title"].ToString();
webItem.Navigate(new Uri(itemRow["link"].ToString()));

Here, I have set the title of the Item form to the item's title, and I navigate to the item's URL in the WebBrowser control using the Navigate method. The link is then located and the page is loaded into the WebBrowser control.

Deploying to a Smartphone

Once the application is built, it is time to deploy it to an actual Smartphone device. At this point, I usually shut down the emulator window and the Device Emulator Manager so I can fully test the deployment to a Smartphone.

Some notes about deploying the application. First, you should create a Smartphone Device CAB project and add it to the current solution. Also, to run an application built using the .NET Compact Framework 2.0, the Framework must be installed on the mobile device. Once these two steps have been taken care of, you can copy the CAB file to the mobile device and install the application directly.

I created the CAB project, named MSDN200611, and added content files and primary output. The content files pick up the icon file I used in the Smartphone device application. I created a shortcut to the primary output, added the Start Menu folder to the CAB file project, and dragged the shortcut to the Start Menu folder-this puts the application in the Smartphone's list of applications in its Start Menu. I also renamed the shortcut to a friendlier and shorter name.

Conclusion

When developing datacentric mobile applications with the .NET Compact Framework 2.0, you'll use many of the same skills that you use to develop Windows Forms applications. This month, I have demonstrated how ADO.NET can be used to load RSS feeds, navigate the feeds and their items, and check for the existence of RSS information. Anonymous methods and asynchronous invocation assist in the development, but the core of the application is still the data itself-whether it be the RSS feed, the DataSet, an array of DataRow objects, or the HTML that is rendered in the WebBrowser control for the item description.

Send your questions and comments for John to  mmdata@microsoft.com.

John Papa is a senior .NET Consultant with ASPSOFT (aspsoft.com) and a baseball fanatic who spends most of his summer nights rooting for the Yankees with his family and his faithful dog, Kadi. John, a C# MVP, has authored several books on ADO, XML, and SQL Server, and can often be found speaking at industry conferences such as VSLive.