Platinum Partner
dotnet,mobile,how-to,microsoft,silverlight,visual studio,c-sharp,windows-phone-7

Building a RSS reader for Windows Phone 7 – Developing the functionality

UPDATE: If you are experiencing feed problems caused by an invalid date, make sure you read this post and download the updated project.

In my previous article I showed the way I designed the basic UI and logical skeleton for the feed reader. In this article I am going to show how the code is organized that makes this application work the way it should. You need to read the previous article in order to apply the methods shown below.

Adding and removing feed URLs

As you know, the application supports multiple feeds. Therefore, the content is aggregated from more than one feed at once. So, in the ManageFeeds page, you should add an event handler for the Add button that will allow the user to add multiple URLs. If you look at the existing XAML markup, the event handler is already declared in the element markup, so all you have to do is actually implement it:

private void Button_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(urlHolder.Text.Trim()))
{
if (!App.Data.FeedList.Contains(urlHolder.Text))
{
if (Uri.IsWellFormedUriString(urlHolder.Text, UriKind.Absolute))
{
App.Data.FeedList.Add(urlHolder.Text);
urlHolder.Text = string.Empty;
}
}
}
}

I am verifying if the URL passed is well-formatted (this will avoid issues with invalid URLs) via Uri.IsWellFormattedUriString. I thought about using a web request to check whether the URL works, but since it is a phone, this could be too expensive, traffic-wise.

Since FeedList is an ObservableCollection and it is bound to the ListBox that contains the URLs, the ListBox will re-bind automatically and you will see the item added to the list without additional code.

For that reason, item removal is tied only to removing the actual item from the list.

private void Remove_Click(object sender, RoutedEventArgs e)
{
if (MainList.SelectedItem != null)
{
App.Data.FeedList.Remove(MainList.SelectedItem.ToString());
}
}

Preserving feed URLs for later re-use

This is done by implementing a Save method inside the App class:

void SaveList()
{
IsolatedStorageSettings.ApplicationSettings.Clear();

if (App.Data.FeedList.Count != 0)
{
foreach (string item in App.Data.FeedList)
{
IsolatedStorageSettings.ApplicationSettings.Add(item, item);
}
}

IsolatedStorageSettings.ApplicationSettings.Save();
}

This will update the application settings with the items currently present in the collection. Later on, this method should be called from Application_Closing (triggered when the application is closed) and Application_Deactivated (triggered when the application is sent to the background).

// Code to execute when the application is deactivated (sent to background)
// This code will not execute when the application is closing
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
SaveList();
}

// Code to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
SaveList();
}

Getting feed contents

I was surprised by this, but by default I could not access System.ServiceModel.Syndication. Even when trying to add a reference, the library wasn’t on the list. The solution to this is simple – all you have to do is use the same Add Reference dialog, navigate to <SystemDrive>:\Program Files\Microsoft SDKs\Silverlight\v4.0\Libraries\Client and select System.ServiceModel.Syndication.dll:

Once done, in my FeedHelper class I am creating a GetItems() method:

public static void GetItems()
{
App.Model.FeedItems.Clear();

foreach (string item in App.Data.FeedList)
{
WebClient client = new WebClient();
client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
client.DownloadStringAsync(new Uri(item));
}
}

First of all, I am clearing the existing feed items cache and then initializing a WebClient for each registered feed. A separate WebClient instance is created for each feed because one instance cannot handle multiple downloads at once. As you see, this method is bound to DownloadStringCompletedEventHandler for the WebClient instance that is used inside of it. The event handler looks like this:

static void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Result))
{
XmlReader reader = XmlReader.Create(new StringReader(e.Result));
SyndicationFeed feed = SyndicationFeed.Load(reader);

foreach (SyndicationItem sItem in feed.Items)
{
if ((sItem != null) && (sItem.Summary != null) && (sItem.Title != null))
{
App.Model.FeedItems.Add(
new ViewModel.ItemModel()
{
ItemDetails = sItem.Summary.Text,
ItemTitle = sItem.Title.Text
}
);
}
}
}
}

Here I am checking whether there is a result for the URL used for feed content retrieval. If there is, then I am creating a XmlReader instance that will get the contents in XML format and then passing it to a SyndicationFeed instance that makes it much easier to handle RSS feeds. If the item is not null (and so are its main used properties), then a new instance of ItemModel is created for each item and added to the item collection.

NOTE: To avoid NullReference exceptions, in the App constructor add the following line:

Model.FeedItems = new ObservableCollection<ViewModel.ItemModel>();

In the same ManageFeeds page, I am overriding the OnNavigatedFrom method:

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
FeedHelper.FeedData.GetItems();
base.OnNavigatedFrom(e);
}

This way, the feed contents are updated when the user navigates away from the feed list.

On the main page, for the ‘reload feeds’ menu item in the application bar, you need to implement the same functionality. Since there is an event handler declared in XAML, all you need is to provide a method call in the code-behind:

private void reloadFeeds_Click(object sender, EventArgs e)
{
FeedHelper.FeedData.GetItems();
}

If you launch the application and add a few valid feeds, then when the list is reloaded, you will see the content aggregated there.

Opening posts

Now, the application seems to be useful, but not to the extent it could be. Each syndication item is pointing to an actual published post. So why not open that post in a new page?

To do this, add a new Portrait Page and name it DetailsView.xaml.

Now, you need to modify the existing ItemModel class to include one more property –ItemLink, that will store an assigned post link. The property along with the associated private field should look like this:

private string itemLink;
public string ItemLink
{
get { return itemLink; }
set
{
if (value != itemLink)
{
itemLink = value;
NotifyPropertyChanged("ItemLink");
}
}
}

Add it to ItemModel and once done, go to the client_DownloadStringCompleted event handler (n FeedHelper) and modify the added instance of ItemModel to add one more property – ItemLink:

App.Model.FeedItems.Add(
new ViewModel.ItemModel()
{
ItemDetails = sItem.Summary.Text,
ItemTitle = sItem.Title.Text,
ItemLink=sItem.Links[0].Uri.ToString()
}
);

Now, on the page that you recently created (DetailsView), inside the internal grid, add a WebBrowser control. My grid markup looks like this:

<Grid x:Name="LayoutRoot" Background="Transparent">   
<phone:WebBrowser Name="webBrowser1"/>
</Grid>

All good by this point, but there is an event handler needed for the ListBox on the main page, so that when one item is clicked, it navigates to the page with the web browser and opens the assigned page. In order to do this, make sure that the ListBox on the main page has an associated SelectionChanged event handler:

<ListBox x:Name="MainList" ItemsSource="{Binding FeedItems}" Margin="13,0,13,0" SelectionChanged="ListBox_SelectionChanged">

The actual event handler:

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MainList.SelectedItems.Count != 0)
{
NavigationService.Navigate(new Uri("/DetailsView.xaml?item=" + MainList.SelectedIndex, UriKind.Relative));
}
}

I am passing a parameter with the currently selected item index, so on the page with the web browser I need to read this parameter and get the URL from the item collection. I can do this simply by using the Loaded event handler for that page:

void DetailsView_Loaded(object sender, RoutedEventArgs e)
{
string index = "";
if (NavigationContext.QueryString.TryGetValue("item", out index))
{
int _index = int.Parse(index);
webBrowser1.Navigate(new Uri(App.Model.FeedItems[_index].ItemLink));
}
}

And that is it. Once you launch the application, reload the feeds and click on any item. You will see the details page open and the associated link navigated to in the WebBrowser control:

If you want to download the project, you can do so by clicking here [ZIP file – Visual Studio 2010 solution]. Of course, Windows Phone 7 Development Tools are required.

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}