Add multiple feeds to the RSS reader app template

Early March, I posted a Windows Phone RSS reader app template that provided a basic template for a news app. The template also provided a code sharing mechanism to easily build for Windows Phone 7 and Windows Phone 8 natively, based on my Windows Phone starting template. One of the questions that I’ve received is how to add multiple feeds to the template, which is what this post will cover. We’ll change the main page of the app to contain a Pivot control to cycle through the different feeds and will implement loading of data only when the user cycles through the pivot. I’ll use the English template throughout this post, so please change MyMultiOSRSSApp to MijnMultiOSRSSApp if you use the Dutch template.

Configuration data

The first thing we need to do is implement a mechanism to configure multiple feeds in the app. The current template uses a Configuration.resx file for its configuration. The great thing about the resource file is that it gives us a strongly typed static class, which we can use in the app directly. Because we want to stay flexible in the amount of feeds we can add, we’ll switch over to a more dynamic way of configuration. We can do this with an XML file, but our XAML based app has the possibility of using a ResourceDictionary, which will also help in directly using our configuration with databinding. Right-click the Resources folder in the Common project and select Add > New item… From here, we’ll select XML File, but we’ll name the file Configuration.xaml. Once we’ve added the file, paste in the following code to make this a ResourceDictionary:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
</ResourceDictionary>

We’ll need to add data to this ResourceDictionary and to do so we’ll create a ViewModel and a Model to use. First, right-click the ViewModels folder in the Common project and select Add > Class…. We’ll name the ViewModel FeedViewModel and will move some code from the existing MainViewModel over:

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Syndication;
using MyMultiOSRSSApp.Common.Services;

namespace MyMultiOSRSSApp.Common.ViewModels
{
    public class FeedViewModel : BaseViewModel
    {
        public string Title { get; set; }
        public string Url { get; set; }

        private List<SyndicationItem> _items;
        public List<SyndicationItem> Items
        {
            get { return _items; }
            set
            {
                if (value == _items) return;
                _items = value;
                RaisePropertyChanged("Items");
            }
        }

        private bool _hasErrorOccurred;
        public bool HasErrorOccurred
        {
            get { return _hasErrorOccurred; }
            set
            {
                if (value == _hasErrorOccurred) return;
                _hasErrorOccurred = value;
                RaisePropertyChanged("HasErrorOccurred");
            }
        }

        public async void LoadData()
        {
            ViewModelLocator.Main.IsDataLoaded = false;

            HasErrorOccurred = false;

            try
            {
                var items = await RssService.GetItems(Url);
                Items = items.ToList();
            }
            catch (Exception e)
            {
                HasErrorOccurred = true;
            }

            ViewModelLocator.Main.IsDataLoaded = true;
        }
    }
}

We’ll need to modify the MainViewModel to hold a reference to our selected feed, so we can refresh only the selected feed when using the app bar button to refresh content. Our new MainViewModel (minus the moved code to FeedViewModel and including the selected feed reference) will now look like:

using MyMultiOSRSSApp.Common.Commands;
using System.ServiceModel.Syndication;

namespace MyMultiOSRSSApp.Common.ViewModels
{
    public class MainViewModel : BaseViewModel
    {
        public FeedViewModel SelectedFeed { get; set; }

        private SyndicationItem _selectedItem;
        public SyndicationItem SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                if (value == _selectedItem) return;
                _selectedItem = value;
                RaisePropertyChanged("SelectedItem");
            }
        }

        public AboutCommand About { get; set; }
        public SettingsCommand Settings { get; set; }
        public BrowseUrlCommand BrowseUrl { get; set; }
        public RefreshCommand Refresh { get; set; }
        public ShareCommand Share { get; set; }

        public MainViewModel()
        {
            About = new AboutCommand();
            Settings = new SettingsCommand();
            BrowseUrl = new BrowseUrlCommand();
            Refresh = new RefreshCommand();
            Share = new ShareCommand();
        }
    }
}

Now right-click the Models folder in the Common project and select Add > Class… Name the class FeedData and use the following code:

using System.Collections.Generic;
using MyMultiOSRSSApp.Common.ViewModels;

namespace MyMultiOSRSSApp.Common.Models
{
    public class FeedData
    {
        public List<FeedViewModel> Feeds { get; set; }

        public FeedData()
        {
            Feeds = new List<FeedViewModel>();
        }
    }
}

Now we have a class that will hold a list of feeds, based on FeedViewModel. We’re ready to use all of this in our Configuration.xaml ResourceDictionary now. So it will look something like:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:m="clr-namespace:MyMultiOSRSSApp.Common.Models"
    xmlns:vm="clr-namespace:MyMultiOSRSSApp.Common.ViewModels">

    <m:FeedData x:Key="FeedData">
        <m:FeedData.Feeds>
            <vm:FeedViewModel Title="Microsoft" Url="http://www.microsoft.com/en-us/news/rss/rssfeed.aspx?ContentType=FeatureStories" />
            <vm:FeedViewModel Title="WP" Url="http://blogs.windows.com/windows_phone/b/windowsphone/rss.aspx" />
            <vm:FeedViewModel Title="WP Dev" Url="http://blogs.windows.com/windows_phone/b/wpdev/rss.aspx" />
        </m:FeedData.Feeds>
    </m:FeedData>
    
</ResourceDictionary>

I’ve included three feeds here, but you can use any feeds of course. Something to be aware of is that if there’s an ampersand (&) in the feed URL, the XAML will break. To fix this, replace any ampersands with &amp;, which will turn into an ampersand at runtime. We can now delete our Configuration.resx file, as we will no longer use it.

Lastly, we need to reference our new ResourceDictionary in both our app projects. Open the App.xaml file in both WP71 and WP8 projects and add the following XAML just below the <Application.Resources> tag on line 13:

<ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/MyMultiOSRSSApp.Common;component/Resources/Configuration.xaml" />
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

Changing the refresh logic

To change the refresh logic, we’ll use the SelectedFeed reference we created in our MainViewModel. Open up the RefreshCommand.cs file under the Commands folder in the Common project and change the line of code on line 23 to:

ViewModelLocator.Main.SelectedFeed.LoadData();

Changing MainPage

Last, but not least, we’ll change MainPage in both our app projects to contain a Pivot instead of the current implementation. As we’re using the ListBox control on WP71 and the LongListSelector on WP8, we’ll need to make the change in both projects. First open the MainPage.xaml file under the Views folder in the WP71 project and replace the LayoutRoot Grid and everything in between:

<Grid x:Name="LayoutRoot" Background="Transparent">
[...]
</Grid>

with:

<h:Pivot Title="{Binding Title, Source={StaticResource Assembly}, Converter={StaticResource StringCaseConverter}, ConverterParameter=u}" ItemsSource="{Binding Source={StaticResource FeedData}, Path=Feeds}" SelectionChanged="Pivot_OnSelectionChanged">
    <h:Pivot.HeaderTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Title, Converter={StaticResource StringCaseConverter}, ConverterParameter=l}" />
        </DataTemplate>
    </h:Pivot.HeaderTemplate>
    <h:Pivot.ItemTemplate>
        <DataTemplate>
            <Grid Margin="12,0">
                <TextBlock Text="{Binding LocalizedResources.ErrorMessage, Source={StaticResource LocalizedStrings}}" Style="{StaticResource PhoneTextLargeStyle}" TextWrapping="Wrap" Visibility="{Binding HasErrorOccurred, Converter={StaticResource BooleanToVisibilityConverter}}" Margin="0" />
                <ListBox Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="FeedListBox_SelectionChanged" Visibility="{Binding HasErrorOccurred, Converter={StaticResource BooleanToInverseVisibilityConverter}}">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <Grid Margin="12,0,0,17">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                <Canvas Background="Gray" Width="48" Height="48" VerticalAlignment="Top" Margin="0,12,0,0" />
                                <Image Source="{Binding ., Converter={StaticResource SyndicationToImageConverter}}" Width="48" Height="48" VerticalAlignment="Top" Stretch="UniformToFill" Margin="0,12,0,0" />
                                <StackPanel Grid.Column="1">
                                    <TextBlock Text="{Binding Title.Text, Converter={StaticResource StripHtmlConverter}}" TextWrapping="Wrap" Style="{StaticResource PhoneTextLargeStyle}"/>
                                    <TextBlock Text="{Binding PublishDate.DateTime, Converter={StaticResource PublishDateConverter}}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                                </StackPanel>
                            </Grid>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
            </Grid>
        </DataTemplate>
    </h:Pivot.ItemTemplate>
</h:Pivot>

As you inspect the code, notice that we’re actually re-using the ContentPanel grid from the initial implementation, but we’re now using it as the template for each pivot item in the pivot control. We’re also binding to our FeedData resource from our ResourceDictionary in the Pivot ItemsSource. We also need to make some changes in the code-behind of the MainPage, as we no longer have the FeedListBox object to use and we’ll need to implement code to load the feed data when the user cycles through the Pivot items. Press F7 when editing the MainPage.xaml file or open the MainPage.xaml.cs file manually from the Solution Explorer. In the FeedListBox_SelectionChanged method, add the following line as the first line of code in the method (on line 24):

var FeedListBox = sender as ListBox;

This will create a reference to the ListBox where the tap came from, so we can re-use our existing item selection logic. We’ll also need to change the MainPage_Loaded method to no longer load the data, which will be handled when the pivot item changes. Replace the MainPage_Loaded method with the following code:

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    ReviewNotification.TriggerReviewNotification();
}

// Load data for the ViewModel Items
private void Pivot_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (e.AddedItems.Count == 0) return;

    ViewModelLocator.Main.SelectedFeed = e.AddedItems[0] as FeedViewModel;
    if (ViewModelLocator.Main.SelectedFeed != null) ViewModelLocator.Main.SelectedFeed.LoadData();
}

The very last thing we’ll do is remove the data loading functionality from App.xaml.cs when our application is activated in the Application_Activated method. Simply remove the code from that method.

Now when we run our code, we’ll see the three feeds in our pivot and the data will be loaded when the user cycles through the pivot items! As usual, download the full code below with some additional minor tweaks to the detail page included and let me know any thoughts, comments, suggestions, etc. through the comments section below or on Twitter!

Downloads

English Windows Phone RSS reader app with multiple feeds - MyMultiOSRSSApp-MultiRSS.zip (350 downloads)

5 thoughts on “Add multiple feeds to the RSS reader app template

  1. Hi Rajen,

    Great app template, thanks! I really like the way you share code between the WP7 and WP8 project. I currently building my own based on your code, some things that I have added:

    – Default image for when feed doesn’t supply article image
    – Added splashscreen
    – PublishDateConverter improvement: remove hardcoded en-US (when CultereInfo is not supplied, I will still use it)
    – Support for older RDF feeds (don’t know why people still you these)
    – DetailsPage (switched app title and article title (make it wrappable)
    – Added mTiks integration (measure app usage)

    (Not saying you have to add this, just FYI..)

    Thanks a lot for sharing,
    Guus

    • Hi Guus,

      That’s great! Glad you could build off of it and improve upon it, which is exactly the point of the template! Do you intend on sharing your improvements somewhere so others can learn and benefit from it?

      Rajen

  2. Pingback: RSS readers: resources and lessons learned | Doctor App MD's Blog.

Leave a Reply