Building an End-to-End Windows Store App - Part 1

In previous posts, I’ve alluded to one of our key focus areas for Visual Studio 2012 being the theme of connected devices and continuous services.  This includes creating a top-notch set of tools in Visual Studio 2012 to both design and build amazing Windows apps and the services that back them.

With Windows 8 and Visual Studio 2012 released, I’ve decided to explore and document the end-to-end development of a basic Windows Store app that uses services on the backend.  For this task I’ve chosen to use Visual Studio Express 2012 for Windows 8.

My primary goal here is to highlight just how straightforward it is now to build a modern, connected experience.  For the sake of simplicity and minimizing the coding involved, I’ll build a simple RSS reader: “News by Soma.” In this two-part blog post, I’ll document the experience of building this app.  Hopefully my notes are detailed enough that you can follow along and build (and then expand upon) your own version of this app, as well.

Getting Started

Since my goal here is to showcase the full end-to-end experience, I’m starting from a Windows 8 system without Visual Studio installed.  I download the Visual Studio Express 2012 for Windows 8 installer, click through the setup wizard, and begin the installation:

Within a few minutes, I have my development environment up and running:

From File | New Project, I create a new Windows Store “Grid App (XAML)” using C#:

The Grid App template maps nicely to the scenario I have in mind, that of being able to have multiple RSS feeds (the “groups”), each of which contains multiple posts (the “items” in each group).  The template provides all of the infrastructure necessary to build such an app very quickly.  After the project is created, I press F5 to see my (not yet modified) app in action:

With the basic structure in place, I can now begin customizing it for my specific needs.

Configuring Basic App Properties

I of course want my app to look nice, so I spend a little time in Visual Studio customizing its properties via the Package.appxmanifest file.  Opening this file presents a detailed editor in Visual Studio:

Here I configure both my app’s tile (for the Windows Start page) and splash screen (displayed when the app starts) the way I want them.  This involves creating several images, which I do by starting with a picture of myself, and creating various cuts of it in Paint to fit the sizes specified in the configuration editor (e.g. 150x150 for the Logo, 310x150 for the Wide Logo, etc.):

This results in a nice tile experience when the app is pinned to my Start screen, whether using the Small tile:

or the Wide tile:

My app also now has a nice splash screen experience:

Getting Data

The Grid App template gets all of its data from a SampleDataSource type (in the file DataModel\SampleDataSource.cs) that exposes an AllGroups property which returns an ObservableCollection<SampleDataGroup>.  This enables the UI to data bind to a collection of groups, each represented by the generic data model type SampleDataGroup.  SampleDataGroup in turn contains a collection of SampleDataItem instances.

In my app, SampleDataGroup maps to RSS feeds, and SampleDataItem maps to the entries in a feed.  Rather than replace SampleDataGroup and SampleDataItem with my own custom data types, for the sake of simplicity I simply repurpose them.  The template includes on these types enough relevant properties so I don’t actually need to modify them at all; rather, I just need to modify SampleDataSource to populate and return instances of these with the right data.

There’s a fair amount of code in the SampleDataSource type included with the template, much of which is about populating the “lorem ipsum” nonsensical text items shown in the previous screenshots.  I delete all of that, and replace the AllGroups property with a simple static declaration (fixing up all of the references in the process):

public static readonly ObservableCollection<SampleDataGroup> AllGroups =
    new ObservableCollection<SampleDataGroup>();

My UI can continue binding to AllGroups, which is initially empty.  As new groups (RSS feeds) are added to AllGroups, the UI will be notified automatically of the addition and will update itself accordingly.  Therefore, I need to expose a method to add groups:

public static async Task<bool> AddGroupForFeedAsync(string feedUrl)
{
    if (SampleDataSource.GetGroup(feedUrl) != null) return false;

    var feed = await new SyndicationClient().RetrieveFeedAsync(new Uri(feedUrl));

    var feedGroup = new SampleDataGroup(
        uniqueId: feedUrl,
        title: feed.Title != null ? feed.Title.Text : null,
        subtitle: feed.Subtitle != null ? feed.Subtitle.Text : null,
        imagePath: feed.ImageUri != null ? feed.ImageUri.ToString() : null,
        description: null);

    foreach (var i in feed.Items)
    {
        string imagePath = GetImageFromPostContents(i);
        if (imagePath != null && feedGroup.Image == null)
            feedGroup.SetImage(imagePath);
        feedGroup.Items.Add(new SampleDataItem(
            uniqueId: i.Id, title: i.Title.Text, subtitle: null, imagePath: imagePath,
            description: null, content: i.Summary.Text, @group: feedGroup));
    }

    AllGroups.Add(feedGroup);
    return true;
}

Using the SyndicationClient class from Windows Runtime (WinRT), and the new async/await keywords in C#, I asynchronously download the feed at the requested URL.  I then create a SampleDataGroup to represent the feed, populating it with information about the feed from the SyndicationFeed I was handed.  And then for each item in the syndication feed, I map its properties into a new SampleDataItem.  These items are all added to the group, and then the group is added to the AllGroups collection.  With that, I’m almost done teaching the app how to get all of the data it needs.

The one remaining piece of code here has to do with images. The UI knows how to bind to SampleDataGroup and SampleDataItem, including showing an image for every group and item.  Typically, RSS feed items aren’t associated with an image, but I want something appropriate and interesting to show up in the UI for each feed item whenever possible.  As such, I have one more function that parses the RSS item looking for PNG and JPG images, returning the first one with a fully-qualified path it finds:

private static string GetImageFromPostContents(SyndicationItem item)
{
    return Regex.Matches(item.Summary.Text,
            "href\\s*=\\s*(?:\"(?<1>[^\"]*)\"|(?<1>\\S+))", 
            RegexOptions.None)
        .Cast<Match>()
        .Where(m =>
        {
            Uri url;
            if (Uri.TryCreate(m.Groups[1].Value, UriKind.Absolute, out url))
            {
                string ext = Path.GetExtension(url.AbsolutePath).ToLower();
                if (ext == ".png" || ext == ".jpg") return true;
            }
            return false;
        })
        .Select(m => m.Groups[1].Value)
        .FirstOrDefault();
}

Finally, before I can really run the app, I need one more change: to modify the GroupedItemsPage.LoadState method (in the file GroupedItemsPage.xaml.cs) to use this new SampleDataSource.AddGroupForFeedAsync method.  I replace the LoadState from the template with one line to hook up AllGroups to the UI, and add a few additional lines to initially populate the UI with a few blogs:

protected override async void LoadState(
    object navigationParameter, Dictionary<string, object> pageState)
{
    this.DefaultViewModel["Groups"] = SampleDataSource.AllGroups;

    // temporary hardcoded feeds
    await SampleDataSource.AddGroupForFeedAsync("https://blogs.msdn.com/b/somasegar/rss.aspx");
    await SampleDataSource.AddGroupForFeedAsync("https://blogs.msdn.com/b/jasonz/rss.aspx");
    await SampleDataSource.AddGroupForFeedAsync("https://blogs.msdn.com/b/visualstudio/rss.aspx");
}

And that’s it.  I’m now able to F5 again to see RSS data populated into my app:

Main grouped-items page:

Group page (when I click on a group header on the main page):

Item page (when I click on an item on the main or group pages):

One thing to note here is that I haven’t modified the default template for the item page yet, and it uses a RichTextBlock to display the post’s contents.  As a result, the HTML from the RSS item is displayed as the HTML source rather than as rendered content.

To make this a bit nicer, I can update the template to render the HTML.  The ItemDetailPage.xaml displays the SampleDataItem using a FlipView control, with a DataTemplate that uses a UserControl for the template item.  I replace the contents of that UserControl (which in the original code contains the RichTextBlock-related controls) with the following XAML that uses a WebView control:

<UserControl Loaded="StartLayoutUpdates" Unloaded="StopLayoutUpdates">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBlock Margin="10,10,10,10" Text="{Binding Title}"
                   Style="{StaticResource SubheaderTextStyle}" 
                   IsHitTestVisible="false" Grid.Row="0" />
        <WebView local:WebViewExtension.HtmlSource="{Binding Content}" Grid.Row="1"/>
    </Grid>
</UserControl>

The WebView control itself doesn’t have a property that allows me to bind the WebView directly to the HTML string contentI already have, but I found code from Tim Heuer for an HtmlSource extension property that uses WebView’s NavigateToString method to achieve the same thing.  And with that addition to my project, I now see the feed item rendered nicely in the app:

User Interaction

In the previous section on Getting Data, I simply hardcoded which feeds I wanted the app to display.  However, I want to allow the user to enter such their own choices of feeds manually, so I’ll augment the template UI slightly to enable this user interaction.

One of the common design elements of a Windows Store app is an “app bar.” I’ll use an AppBar control to allow the user to enter a URL into a TextBox and click an Add button to get the feed included in the app.  I drag an AppBar control from the Visual Studio Toolbox onto the designer for my GroupedItemsPage.xaml file:

I then move the resulting XAML into a Page.BottomAppBar element so that the app bar shows up at the bottom of my app:

<Page.BottomAppBar>
    <AppBar>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <StackPanel Orientation="Horizontal"/>
            <StackPanel Grid.Column="1" HorizontalAlignment="Right" Orientation="Horizontal"/>
        </Grid>
    </AppBar>
</Page.BottomAppBar>

And I add three controls to the left-aligned StackPanel:

<TextBox x:Name="txtUrl" VerticalAlignment="Center" />
<Button x:Name="btnAddFeed" Style="{StaticResource AddAppBarButtonStyle}" Click="btnAddFeed_Click" />
<ProgressRing x:Name="prAddFeed" IsActive="false" 
    Foreground="{StaticResource ApplicationForegroundThemeBrush} "/>

(Note that AddAppBarButtonStyle, along with ~140 other styles for AppBar buttons, is defined in the StandardStyles.xaml file included in the Grid App template, but it’s commented out by default.  I just uncomment it so that I can use it here.)

To complete the experience, I need to implement the btnAddFeed_Click method (in the GroupedItemsPage.xaml.cs file), wiring it up to the SampleDataSource.AddGroupForFeedAsync method I previously wrote (and, of course, removing the three lines I previously hardcoded in LoadState):

async void btnAddFeed_Click(object sender, RoutedEventArgs e)
{
    await AddFeedAsync(txtUrl.Text);
}

async Task AddFeedAsync(string feed)
{
    txtUrl.IsEnabled = false;
    btnAddFeed.IsEnabled = false;
    prAddFeed.IsActive = true;
    try
    {
        await SampleDataSource.AddGroupForFeedAsync(feed);
    }
    catch (Exception exc)
    {
        var dlg = new MessageDialog(exc.Message).ShowAsync();
    }
    finally
    {
        txtUrl.Text = string.Empty;
        txtUrl.IsEnabled = true;
        btnAddFeed.IsEnabled = true;
        prAddFeed.IsActive = false;
    }
}

With that in place, when a user brings up the app bar, types in a URL, and clicks the Add button, the feed will be added, and for the duration of the add operation, the app will display the progress ring and prevent the user from adding additional feeds.

Enabling Live Tiles

Windows 8 provides multiple mechanisms for creating live tiles on the Start screen.  For the purposes of my app, I want to update my live tile to list the current feeds in the app. 

To do this, in GroupedItemPage.xaml.cs I create a function to generate the template XML expected by the TileUpdateManager, and I use a TileUpdater to push the visuals to the tile:

private void UpdateTile()
{
    var groups = SampleDataSource.AllGroups.ToList();
    var xml = new XmlDocument();
    xml.LoadXml(
        string.Format(
            @"<?xml version=""1.0"" encoding=""utf-8"" ?>
            <tile>
                <visual branding=""none"">
                    <binding template=""TileWideText01"">
                        <text id=""1"">News by Soma</text>
                        <text id=""2"">{0}</text>
                        <text id=""3"">{1}</text>
                        <text id=""4"">{2}</text>
                    </binding>
                    <binding template=""TileSquarePeekImageAndText01"">
                        <image id=""1"" src=""ms-appx:///Assets/Logo.png"" alt=""alt text""/>
                        <text id=""1"">News by Soma</text>
                        <text id=""2"">{0}</text>
                        <text id=""3"">{1}</text>
                        <text id=""4"">{2}</text>
                    </binding>  
                </visual>
            </tile>", 
            groups.Count > 0 ? groups[0].Title : "", 
            groups.Count > 1 ? groups[1].Title : "",
            groups.Count > 2 ? groups[2].Title : ""));
    TileUpdateManager.CreateTileUpdaterForApplication().Update(new TileNotification(xml));
}

(For your own apps, the “App tiles and badges” Windows SDK Sample includes some helpful code for working with tiles.)

Then I modify GroupedItemsPage.LoadState to call this UpdateTile method after successfully adding a feed:

if (await SampleDataSource.AddGroupForFeedAsync(feed))
{
    UpdateTile();
}

Now, after starting my app and adding my blog feed and Jason Zander’s blog feed, I can see this information available on my tile:

Enabling Search

Having integrated with live tiles, the next feature I want to integrate is Search.  Windows 8 provides the Search charm that enables users to search relevant apps from anywhere in the system.

To start this, I right-click on my project in Visual Studio and select Add | New Item…, picking “Search Contract” as the item to be added:

This does a few things:

  • It updates my package manifest to declare Search:
  • It adds a new SearchResultsPage.xaml to my project.
  • It augments my App.xaml.cs with the necessary OnSearchActivated method override to correctly connect a Search request from the system with my new SearchResultsPage.xaml. 

The added SearchResultsPage.xaml (and associated SearchResultsPage.xaml.cs) already contains most of the UI and logic necessary to make this scenario work.  So as with the other templates we’ve seen Visual Studio create, I just need to plug in the logic specific to my application and its data.

The SearchResultsPage.xaml.cs file includes a simple view model type called Filter.  This type is used by the template to represent a group of search results, such that a user can see and select from multiple categories of results to quickly narrow down their choices. To simplify coding a bit, I first modify this Filter to make it Filter<T> and add a Results property to it… that way, I can perform one search and populate all of the filters, such that as the user chooses different filter categories in the UI, I don’t have to keep re-searching:

private sealed class Filter<T> : News_by_Soma.Common.BindableBase
{
    ...
    private List<T> _results;

    public Filter(string name, IEnumerable<T> results, bool active = false)
    {
        ...
        this.Results = results.ToList();
    }

    public int Count { get { return _results.Count; } }

    public List<T> Results
    {
        get { return _results; }
        set { if (this.SetProperty(ref _results, value)) this.OnPropertyChanged("Description"); }
    }
    ...
}

In the page’s LoadState override, I replace the hardcoded search results filter group that was provided in the template:

var filterList = new List<Filter>();
filterList.Add(new Filter("All", 0, true));

with code to actually do the search across each RSS feed, creating a filter for each feed, and then creating an “All” filter with aggregation of all of the results:

var filterList = new List<Filter<SampleDataItem>>(
    from feed in SampleDataSource.AllGroups
    select new Filter<SampleDataItem>(feed.Title,
        feed.Items.Where(item => (item.Title != null && item.Title.Contains(queryText)) || 
                                     (item.Content != null && item.Content.Contains(queryText))),
        false));
filterList.Insert(0, 
    new Filter<SampleDataItem>("All", filterList.SelectMany(f => f.Results), true));

Next, in the Filter_SelectionChanged, I store the results into the DefaultViewModel:

this.DefaultViewModel["Results"] = selectedFilter.Results;

I then add an ItemClick event handler to the resultsListView control from the template.  This navigates to the selected item when the user clicks on it:

private void resultsListView_ItemClick(object sender, ItemClickEventArgs e)
{
    var itemId = ((SampleDataItem)e.ClickedItem).UniqueId;
    this.Frame.Navigate(typeof(ItemDetailPage), itemId);
}

Finally, the SearchResultsPage.xaml page contains two lines of XAML that should be deleted. The Grid App template already includes the application name in the App.xaml file, so we don’t need to also configure that here:

<!-- TODO: Update the following string to be the name of your app -->
<x:String x:Key="AppName">App Name</x:String>

With that, search is functioning in my application:

As it stands, this will only search the feeds that have been loaded into the app when the app’s main page loads.  That works great if the search is performed while the app is running.  If, however, the app isn’t running, its main page won’t have executed, and groups won’t have populated.  If I were to persist feed information, then if the app hadn’t been running when the search request arrived, I could update the search logic to first load the feeds that had been persisted.

What's Next?

In this first of two posts, I’ve explored getting up and running with Visual Studio Express 2012 for Windows 8, and using it to build a basic app that integrates with live tiles and search.  In my next post, I’ll focus on extending this application via multiple backend services. Stay tuned…

Namaste!