Share via



July 2013

Volume 28 Number 7

MVVM - Leveraging Windows 8 Features with MVVM

By Brent Edwards

Windows 8 brings many new features that developers can leverage to create compelling applications and a rich UX. Unfortunately, these capabilities aren’t always very unit-test friendly. Features such as sharing and secondary tiles can make your app more interactive and enjoyable, but also less testable.

In this article, I’ll look at different ways to let an application use features such as sharing, settings, secondary tiles, application settings and application storage. Using the Model-View-ViewModel (MVVM) pattern, dependency injection and some abstraction, I’ll show you how to leverage these features while keeping the presentation layer unit-test friendly.

About the Sample App

To illustrate the concepts I’ll be talking about in this article, I’ve used MVVM to write a sample Windows Store app that lets a user view blog posts from the RSS feed of his favorite blogs. The app illustrates how to:

  • Share information about a blog post with other apps via the Share charm
  • Change which blogs the user wants to read with the Settings charm
  • Pin a favorite blog post to the Start screen for reading later with secondary tiles
  • Save favorite blogs to view across all devices with roaming settings

In addition to the sample app, I’ve taken the specific Windows 8 functionality I’ll be talking about in this article and abstracted it into an open source library called Charmed. Charmed can be used as a helper library or just as a reference. The goal of Charmed is to be a cross-platform MVVM support library for Windows 8 and Windows Phone 8. I’ll be talking more about the Windows Phone 8 side of the library in a future article. Check out the progress of the Charmed library at bit.ly/17AzFxW.

My goal with this article and the sample code is to demonstrate my approach to testable applications with MVVM, using some of the new features that Windows 8 offers.

MVVM Overview

Before diving into the code and specific Windows 8 features, I’ll take a quick look at MVVM. MVVM is a design pattern that has gained enormous popularity in recent years for XAML-based technologies, such as Windows Presentation Foundation (WPF), Silverlight, Windows Phone 7, Windows Phone 8 and Windows 8 (the Windows Runtime, or WinRT). MVVM breaks an application’s architecture into three logical layers: Model, View Model and View, as shown in Figure 1.

The Three Logical Layers of Model-­View-ViewModel
Figure 1 The Three Logical Layers of Model-­View-ViewModel

The Model layer is where the business logic of the application lives—business objects, data validation, data access and so forth. In reality, the Model layer is typically broken up into more layers and possibly even multiple tiers. As Figure 1 shows, the Model layer is the logical bottom, or foundation, of the application.

The View Model layer holds the presentation logic of the appli­cation, which includes data to be displayed, properties to help enable UI elements or make them visible, and methods that will interact with both the Model and the View layers. Basically, the View Model layer is a view-agnostic representation of the current state of the UI. I say “view-agnostic” because it merely provides data and methods for the view to interact with, but it doesn’t dictate how the view will represent that data or allow the user to interact with those methods. As Figure 1shows, the View Model layer logically sits between the Model layer and the View layer and can interact with both. The View Model layer contains code that would previously be in the codebehind of the View layer.

The View layer contains the actual presentation of the application. For XAML-based applications, such as those for the Windows Runtime, the View layer consists mostly, if not entirely, of XAML. The View layer leverages the powerful XAML data-binding engine to bind to properties on the view model, applying a look-and-feel to data that would otherwise have no visual representation. As Figure 1 shows, the View layer is the logical top of the application. The View layer interacts directly with the View Model layer, but has no knowledge of the Model layer.

The main purpose of the MVVM pattern is to separate an application’s presentation from its functionality. Doing so makes the application more conducive to unit tests because the functionality now lives in Plain Old CLR Objects (POCOs), rather than in views that have their own lifecycles.

Contracts

Windows 8 introduces the concept of contracts, which are agreements among two or more apps on a user’s system. These contracts provide consistency across all apps, allowing developers to leverage functionality from any app that supports them. An app can declare what contracts it supports in the Package.appxmanifest file, as shown in Figure 2.

Contracts in the Package.appxmanifest File
Figure 2 Contracts in the Package.appxmanifest File

While it’s optional to support contracts, it’s generally a good idea. There are three contracts in particular that an application should support—Share, Settings and Search—because they’re always available via the charms menu, shown in Figure 3.

The Charms Menu
Figure 3 The Charms Menu

I’ll focus on two contract types: Sharing and Settings.

Sharing

The Share contract enables an app to share context-specific data with other apps on the user’s system. There are two sides to the Share contract: the source and the target. The source is the app that’s doing the sharing. It provides some data to be shared, in whatever format is necessary. The target is the app that receives the shared data. Because the Share charm is always available to the user via the charms menu, I want the sample app to be a share source, at the very least. Not every app needs to be a share target because not every app has a need to accept input from other sources. However, there’s a pretty good chance that any given app will have at least one thing that’s worth sharing with other apps. So, a majority of apps will likely find it useful to be a share source.

When the user presses the Share charm, an object called the Share Broker begins the process of taking the data an app shares (if any) and sending it to the share target as specified by the user. There’s an object called the DataTransferManager that I can use to share data during that process. The DataTransferManager has an event called DataRequested, which is raised when the user presses the Share charm. The following code shows how to get a reference to the DataTransferManager and subscribe to the DataRequested event:

public void Initialize()
{
  this.DataTransferManager = DataTransferManager.GetForCurrentView();
  this.DataTransferManager.DataRequested += 
    this.DataTransferManager_DataRequested;
}
private void DataTransferManager_DataRequested(
  DataTransferManager sender, DataRequestedEventArgs args)
{
  // Do stuff ...
}

Calling DataTransferManager.GetForCurrentView returns a reference to the active DataTransferManager for the current view. While it’s possible to put this code in a view model, it creates a hard dependency on the DataTransferManager, a sealed class that can’t be mocked in unit tests. Because I really want my app to stay as testable as possible, this isn’t ideal. A better solution is to abstract the DataTransferManager interaction out into a helper class and define an interface for that helper class to implement.

Before abstracting this interaction, I must decide what parts really matter. There are three parts of the interaction with the DataTransferManager I care about:

  1. Subscribing to the DataRequested event when my view is activated.
  2. Unsubscribing from the DataRequested event when my view is deactivated.
  3. Being able to add shared data to the DataPackage.

With those three points in mind, my interface materializes:

public interface IShareManager
{
  void Initialize();
  void Cleanup();
  Action<DataPackage> OnShareRequested { get; set; }
}

Initialize should get a reference to the DataTransferManager and subscribe to the DataRequested event. Cleanup should unsubscribe from the DataRequested event. OnShareRequested is where I can define what method gets called when the DataRequested event has been raised. Now I can implement IShareManager, as shown in Figure 4.

Figure 4 Implementing IShareManager

public sealed class ShareManager : IShareManager
{
  private DataTransferManager DataTransferManager { get; set; }
  public void Initialize()
  {
    this.DataTransferManager = DataTransferManager.GetForCurrentView();
    this.DataTransferManager.DataRequested +=
      this.DataTransferManager_DataRequested;
  }
  public void Cleanup()
  {
    this.DataTransferManager.DataRequested -=
      this.DataTransferManager_DataRequested;
  }
  private void DataTransferManager_DataRequested(
    DataTransferManager sender, DataRequestedEventArgs args)
  {
    if (this.OnShareRequested != null)
    {
      this.OnShareRequested(args.Request.Data);
    }
  }
  public Action<DataPackage> OnShareRequested { get; set; }
}

When the DataRequested event is raised, the event args that come through contain a DataPackage. That DataPackage is where the actual shared data needs to be placed, which is why the Action for OnShareRequested takes a DataPackage as a parameter. With my IShareManager interface defined and ShareManager implementing it, I’m now ready to include sharing in my view model, without sacrificing the unit testability for which I’m aiming.

Once I’ve used my Inversion of Control (IoC) container of choice to inject an instance of IShareManager into my view model, I can put it to use, as shown in Figure 5.

Figure 5 Wiring up IShareManager

public FeedItemViewModel(IShareManager shareManager)
{
  this.shareManager = shareManager;
}
public override void LoadState(
  FeedItem navigationParameter, Dictionary<string, 
  object> pageState)
{
  this.shareManager.Initialize();
  this.shareManager.OnShareRequested = ShareRequested;
}
public override void SaveState(Dictionary<string, 
  object> pageState)
{
  this.shareManager.Cleanup();
}

LoadState is called when the page and view model are activated, and SaveState is called when the page and view model are deactivated. Now that the ShareManager is all set up and ready to handle sharing, I need to implement the ShareRequested method that will be called when the user initiates sharing. I want to share some info about a particular blog post (FeedItem), as shown in Figure 6.

Figure 6 Populating the DataPackage on ShareRequested

private void ShareRequested(DataPackage dataPackage)
{
  // Set as many data types as possible.
  dataPackage.Properties.Title = this.FeedItem.Title;
  // Add a Uri.
  dataPackage.SetUri(this.FeedItem.Link);
  // Add a text-only version.
  var text = string.Format(
    "Check this out! {0} ({1})", 
    this.FeedItem.Title, this.FeedItem.Link);
  dataPackage.SetText(text);
  // Add an HTML version.
  var htmlBuilder = new StringBuilder();
  htmlBuilder.AppendFormat("<p>Check this out!</p>", 
    this.FeedItem.Author);
  htmlBuilder.AppendFormat(
    "<p><a href='{0}'>{1}</a></p>", 
    this.FeedItem.Link, this.FeedItem.Title);
  var html = HtmlFormatHelper.CreateHtmlFormat(htmlBuilder.ToString());
  dataPackage.SetHtmlFormat(html);
}

I chose to share several different data types. This is generally a good idea because you have no control over what apps a user has on his system or what data types those apps support. It’s important to remember that sharing is essentially a fire-and-forget scenario. You have no idea what app the user will choose to share to and what that app will do with the shared data. To share with the broadest possible audience, I provide a title, a URI, a text-only version and an HTML version.

Settings

The Settings contract allows the user to change context-specific settings in an app. These can be settings that affect the app as a whole, or just specific items that relate to the current context. Users of Windows 8 will become conditioned to using the Settings charm to make changes to the app, and I want the sample app to support it because it’s always available to the user via the charms menu. In fact, if an app declares Internet capability via the Package.appxmanifest file, it must implement the Settings contract by providing a link to a Web-based privacy policy somewhere in the Settings menu. Because apps using Visual Studio 2012 templates automatically declare Internet capability right out of the box, this is something that should not be overlooked.

When a user presses the Settings charm, the OS begins dynamically building the menu that will be displayed. The menu and the associated flyout are controlled by the OS. I can’t control what the menu and flyout look like, but I can add options to the menu. An object called SettingsPane will notify me when the user selects the Settings charm via the CommandsRequested event. Getting a reference to the SettingsPane and subscribing to the CommandsRequested event is quite straightforward:

public void Initialize()
{
  this.SettingsPane = SettingsPane.GetForCurrentView();
  this.SettingsPane.CommandsRequested += 
    SettingsPane_CommandsRequested;
}
private void SettingsPane_CommandsRequested(
  SettingsPane sender, 
  SettingsPaneCommandsRequestedEventArgs args)
{
  // Do stuff ...
}

The catch with this is another hard dependency. This time the dependency is SettingsPane, which is another class that can’t be mocked. Because I want to be able to unit test the view model that uses SettingsPane, I need to abstract out references to it, just as I did for references to DataTransferManager. As it turns out, my interactions with SettingsPane are very similar to my interactions with DataTransferManager:

  1. Subscribing to the CommandsRequested event for the current view.
  2. Unsubscribing from the CommandsRequested event for the current view.
  3. Adding my own SettingsCommand objects when the event is raised.

So, the interface I need to abstract looks a lot like the IShare­Manager interface:

public interface ISettingsManager
{
  void Initialize();
  void Cleanup();
  Action<IList<SettingsCommand>> OnSettingsRequested { get; set; }
}

Initialize should get a reference to the SettingsPane and subscribe to the CommandsRequested event. Cleanup should unsubscribe from the CommandsRequested event. OnSettingsRequested is where I can define what method gets called when the CommandsRequested event has been raised. Now I can implement ISettings­Manager, as shown in Figure 7.

Figure 7 Implementing ISettingsManager

public sealed class SettingsManager : ISettingsManager
{
  private SettingsPane SettingsPane { get; set; }
  public void Initialize()
  {
    this.SettingsPane = SettingsPane.GetForCurrentView();
    this.SettingsPane.CommandsRequested += 
      SettingsPane_CommandsRequested;
  }
  public void Cleanup()
  {
    this.SettingsPane.CommandsRequested -= 
      SettingsPane_CommandsRequested;
  }
  private void SettingsPane_CommandsRequested(
    SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)
  {
    if (this.OnSettingsRequested != null)
    {
      this.OnSettingsRequested(args.Request.ApplicationCommands);
    }
  }
  public Action<IList<SettingsCommand>> OnSettingsRequested { get; set; }
}

When the CommandsRequested event is raised, the event args ultimately give me access to the list of SettingsCommand objects that represent the Settings menu options. To add my own Settings menu options, I just need to add a SettingsCommand instance to that list. A SettingsCommand object doesn’t ask for much, just a unique identifier, label text and code to execute when the user selects the option.

I use my IoC container to inject an instance of ISettingsManager to my view model, then set it up to initialize and clean up, as shown in Figure 8.

Figure 8 Wiring up ISettingsManager

public ShellViewModel(ISettingsManager settingsManager)
{
  this.settingsManager = settingsManager;
}
public void Initialize()
{
  this.settingsManager.Initialize();
  this.settingsManager.OnSettingsRequested = 
    OnSettingsRequested;
}
public void Cleanup()
{
  this.settingsManager.Cleanup();
}

I’ll be using the Settings to allow users to change which RSS feeds they can view with the sample app. This is something I want the user to be able to change from anywhere in the app, so I’ve included the ShellViewModel, which is instantiated when the app starts up. If I wanted the RSS feeds to be changed only from one of the other views, I’d include the settings code in the associated view model.

Built-in functionality for creating and maintaining a flyout for settings is lacking in the Windows Runtime. There’s a lot more manual coding required than there should be to get functionality that’s supposed to be consistent across all apps. Luckily, I’m not the only one who feels this way. Tim Heuer, a program manager on the Microsoft XAML team, has created an excellent framework called Callisto, which helps with this pain point. Callisto is available on GitHub (bit.ly/Kijr1S) and on NuGet (bit.ly/112ehch). I use it in the sample app and I recommend checking it out.

Because I have the SettingsManager all wired up in my view model, I just need to provide the code to execute when the settings are requested, as shown in Figure 9.

Figure 9  Showing the SettingsView on SettingsRequested with Callisto

private void OnSettingsRequested(IList<SettingsCommand> commands)
{
  SettingsCommand settingsCommand =
    new SettingsCommand("FeedsSetting", "Feeds", (x) =>
  {
    SettingsFlyout settings = new Callisto.Controls.SettingsFlyout();
    settings.FlyoutWidth =
      Callisto.Controls.SettingsFlyout.SettingsFlyoutWidth.Wide;
    settings.HeaderText = "Feeds";
    var view = new SettingsView();
    settings.Content = view;
    settings.HorizontalContentAlignment = 
      HorizontalAlignment.Stretch;
    settings.VerticalContentAlignment = 
      VerticalAlignment.Stretch;
    settings.IsOpen = true;
  });
  commands.Add(settingsCommand);
}

I create a new SettingsCommand, giving it the id “FeedsSetting” and the label text “Feeds.” The lambda I use for the callback, which gets called when the user selects the “Feeds” menu item, leverages Callisto’s SettingsFlyout control. The SettingsFlyout control does the heavy lifting of where to put the flyout, how wide to make it, and when to open and close it. All I have to do is tell it whether I want the wide or narrow version, give it some header text and the content, then set IsOpen to true to open it up. I also recommend setting the HorizontalContentAlignment and the VerticalContent­Alignment to Stretch. Otherwise, your content won’t match the size of the SettingsFlyout.

Message Bus

One important point when dealing with the Settings contract is that any changes to settings are expected to be applied to and reflected in the app immediately. There are a number of ways you can accomplish broadcasting the settings changes made by the user. The method I prefer to use is a message bus (also known as an event aggregator). A message bus is an app-wide message publication system. The concept of the message bus is not built in to the Windows Runtime, which means I have to either create one or use one from another framework. I’ve included a message bus implementation that I’ve used on several projects with the Charmed framework. You can find the source at bit.ly/12EBHrb. There are several other good implementations out there as well. Caliburn.Micro has the EventAggregator and MVVM Light has the Messenger. All implementations typically follow the same pattern, providing a way to subscribe to, unsubscribe from and publish messages.

Using the Charmed message bus in the settings scenario, I configure my MainViewModel (the one that displays the feeds) to subscribe to a FeedsChangedMessage:

this.messageBus.Subscribe<FeedsChangedMessage>((message) =>
  {
    LoadFeedData();
  });

Once MainViewModel is set up to listen for changes to the feeds, I configure SettingsViewModel to publish the FeedsChanged­Message when the user adds or removes an RSS feed:

this.messageBus.Publish<FeedsChangedMessage>(new FeedsChangedMessage());

Whenever a message bus is involved, it’s important that every part of the app uses the same message bus instance. So, I made sure to configure my IoC container to give a singleton instance for every request to resolve an IMessageBus.

Now the sample app is set up to let the user make changes to the RSS feeds displayed via the Settings charm and update the main view to reflect these changes.

Roaming Settings

Another cool thing Windows 8 introduced is the concept of roaming settings. Roaming settings allow app developers to transition small amounts of data among all of a user’s devices. This data must be less than 100KB and should be limited to bits of information needed to create a persistent, customized UX across all devices. In the case of the sample app, I want to be able to persist the RSS feeds the user wants to read across all his devices.

The Settings contract I talked about earlier typically goes hand-in-hand with roaming settings. It only makes sense that the customizations I allow the user to make using the Settings contract be persisted across devices with roaming settings.

Gaining access to roaming settings, like the other issues I’ve looked at so far, is pretty straightforward. The ApplicationData class gives access to both LocalSettings and RoamingSettings. Putting something into RoamingSettings is as simple as providing a key and an object:

ApplicationData.Current.RoamingSettings.Values[key] = value;

While ApplicationData is easy to work with, it’s another sealed class that can’t be mocked in unit tests. So, in the interest of keeping my view models as testable as I can, I need to abstract the interaction with ApplicationData. Before defining an interface to abstract the roaming settings functionality behind, I need to decide what I want to do with it:

  1. See if a key exists.
  2. Add or update a setting.
  3. Remove a setting.
  4. Get a setting.

Now I have what I need to create an interface I’ll call ISettings:

public interface ISettings
{
  void AddOrUpdate(string key, object value);
  bool TryGetValue<T>(string key, out T value);
  bool Remove(string key);
  bool ContainsKey(string key);
}

With my interface defined, I need to implement it, as Figure 10 shows.

Figure 10 Implementing ISettings

public sealed class Settings : ISettings
{
  public void AddOrUpdate(string key, object value)
  {
    ApplicationData.Current.RoamingSettings.Values[key] = value;
  }
  public bool TryGetValue<T>(string key, out T value)
  {
    var result = false;
    if (ApplicationData.Current.RoamingSettings.Values.ContainsKey(key))
    {
      value = (T)ApplicationData.Current.RoamingSettings.Values[key];
      result = true;
    }
    else
    {
      value = default(T);
    }
    return result;
  }
  public bool Remove(string key)
  {
    return ApplicationData.Current.RoamingSettings.Values.Remove(key);
  }
  public bool ContainsKey(string key)
  {
    return ApplicationData.Current.RoamingSettings.Values.ContainsKey(key);
  }
}

TryGetValue will first check whether a given key exists and assign the value to the out parameter if it does. Rather than throw an exception if the key isn’t found, it returns a bool indicating whether the key was found. The rest of the methods are fairly self-explanatory.

Now I can let my IoC container resolve ISettings and give it to my SettingsViewModel. Once I do, the view model will use the settings to load the user’s feeds to be edited, as shown in Figure 11.

Figure 11 Loading and Saving the User’s Feeds

public SettingsViewModel(
  ISettings settings,
  IMessageBus messageBus)
{
  this.settings = settings;
  this.messageBus = messageBus;
  this.Feeds = new ObservableCollection<string>();
  string[] feedData;
  if (this.settings.TryGetValue<string[]>(Constants.FeedsKey, out feedData))
  {
    foreach (var feed in feedData)
    {
      this.Feeds.Add(feed);
    }
  }
}
public void AddFeed()
{
  this.Feeds.Add(this.NewFeed);
  this.NewFeed = string.Empty;
  SaveFeeds();
}
public void RemoveFeed(string feed)
{
  this.Feeds.Remove(feed);
  SaveFeeds();
}
private void SaveFeeds()
{
  this.settings.AddOrUpdate(Constants.FeedsKey, this.Feeds.ToArray());
  this.messageBus.Publish<FeedsChangedMessage>(new FeedsChangedMessage());
}

One thing to note about the code in Figure 11 is that the data I actually save in the settings is a string array. Because roaming settings are limited to 100KB, I need to keep things simple and stick to primitive types.

Secondary Tiles

Developing apps that engage users can be enough of a challenge. But how do you keep users coming back once they install your app? One thing that can help with this challenge is secondary tiles. A secondary tile provides the ability to deep link into an application, letting the user bypass the rest of the app and go straight to what he cares about most. A secondary tile gets pinned to the user’s home screen, with an icon of your choosing. Once tapped, the secondary tile launches your app with arguments that tell the app exactly where to go and what to load. Providing secondary tile functionality to your users is a good way to let them customize their experience, making them want to come back for more.

Secondary tiles are more complicated than the other topics I cover in this article, because there are several things that have to be implemented before the full experience of using secondary tiles will work correctly.

Pinning a secondary tile involves instantiating the SecondaryTile class. The SecondaryTile constructor takes several parameters that help it determine what the tile will look like, including a display name, a URI to the logo image file to use for the tile, and string arguments that will be given to the app when the tile is pressed. Once the SecondaryTile has been instantiated, I must call a method that will ultimately show a little pop-up window asking the user for permission to pin the tile, as shown in Figure 12.

SecondaryTile Requesting Permission to Pin a Tile to the Start Screen
Figure 12 SecondaryTile Requesting Permission to Pin a Tile to the Start Screen

Once the user has pressed Pin to Start, the first half of the work is done. The second half is configuring the app to actually support the deep linking by using the arguments the tile provides when it’s pressed. Before I get into the second half, let me talk about how I’ll implement the first half in a testable way.

Because SecondaryTile uses methods that interact directly with the OS—which, in turn, shows UI components—I can’t use it directly from my view models without sacrificing testability. So, I’ll abstract out another interface, which I’ll call ISecondaryPinner (it should allow me to pin and unpin a tile, and check if a tile has already been pinned):

public interface ISecondaryPinner
{
  Task<bool> Pin(FrameworkElement anchorElement,
    Placement requestPlacement, TileInfo tileInfo);
  Task<bool> Unpin(FrameworkElement anchorElement,
    Placement requestPlacement, string tileId);
  bool IsPinned(string tileId);
}

Notice that both Pin and Unpin return Task<bool>. That’s because the SecondaryTile uses async tasks to prompt the user to pin or unpin a tile. It also means that my ISecondaryPinner Pin and Unpin methods can be awaited.

Also notice that both Pin and Unpin take a FrameworkElement and a Placement enumeration value as parameters. The reason is that the SecondaryTile needs a rectangle and a Placement to tell it where to put the pin request pop-up. I plan to have my implementation of SecondaryPinner calculate that rectangle based on the FrameworkElement that’s passed in.

Finally, I create a helper class, TileInfo, to pass around the required and optional parameters used by SecondaryTile, as shown in Figure 13.

Figure 13 The TileInfo Helper Class

public sealed class TileInfo
{
  public TileInfo(
    string tileId,
    string shortName,
    string displayName,
    TileOptions tileOptions,
    Uri logoUri,
    string arguments = null)
  {
    this.TileId = tileId;
    this.ShortName = shortName;
    this.DisplayName = displayName;
    this.Arguments = arguments;
    this.TileOptions = tileOptions;
    this.LogoUri = logoUri;
    this.Arguments = arguments;
  }
  public TileInfo(
    string tileId,
    string shortName,
    string displayName,
    TileOptions tileOptions,
    Uri logoUri,
    Uri wideLogoUri,
    string arguments = null)
  {
    this.TileId = tileId;
    this.ShortName = shortName;
    this.DisplayName = displayName;
    this.Arguments = arguments;
    this.TileOptions = tileOptions;
    this.LogoUri = logoUri;
    this.WideLogoUri = wideLogoUri;
    this.Arguments = arguments;
  }
  public string TileId { get; set; }
  public string ShortName { get; set; }
  public string DisplayName { get; set; }
  public string Arguments { get; set; }
  public TileOptions TileOptions { get; set; }
  public Uri LogoUri { get; set; }
  public Uri WideLogoUri { get; set; }
}

TileInfo has two constructors that can be used, depending on the data. Now, I implement ISecondaryPinner, as shown in Figure 14.

Figure 14 Implementing ISecondaryPinner

public sealed class SecondaryPinner : ISecondaryPinner
{
  public async Task<bool> Pin(
    FrameworkElement anchorElement,
    Placement requestPlacement,
    TileInfo tileInfo)
  {
    if (anchorElement == null)
    {
      throw new ArgumentNullException("anchorElement");
    }
    if (tileInfo == null)
    {
      throw new ArgumentNullException("tileInfo");
    }
    var isPinned = false;
    if (!SecondaryTile.Exists(tileInfo.TileId))
    {
      var secondaryTile = new SecondaryTile(
        tileInfo.TileId,
        tileInfo.ShortName,
        tileInfo.DisplayName,
        tileInfo.Arguments,
        tileInfo.TileOptions,
        tileInfo.LogoUri);
      if (tileInfo.WideLogoUri != null)
      {
        secondaryTile.WideLogo = tileInfo.WideLogoUri;
      }
      isPinned = await secondaryTile.RequestCreateForSelectionAsync(
        GetElementRect(anchorElement), requestPlacement);
    }
    return isPinned;
  }
  public async Task<bool> Unpin(
    FrameworkElement anchorElement,
    Placement requestPlacement,
    string tileId)
  {
    var wasUnpinned = false;
    if (SecondaryTile.Exists(tileId))
    {
      var secondaryTile = new SecondaryTile(tileId);
      wasUnpinned = await secondaryTile.RequestDeleteForSelectionAsync(
        GetElementRect(anchorElement), requestPlacement);
    }
    return wasUnpinned;
  }
  public bool IsPinned(string tileId)
  {
    return SecondaryTile.Exists(tileId);
  }
  private static Rect GetElementRect(FrameworkElement element)
  {
    GeneralTransform buttonTransform =
      element.TransformToVisual(null);
    Point point = buttonTransform.TransformPoint(new Point());
    return new Rect(point, new Size(
      element.ActualWidth, element.ActualHeight));
  }
}

Pin will first make sure the requested tile doesn’t already exist, then it will prompt the user to pin it. Unpin will first make sure the requested tile does exist, then it will prompt the user to unpin it. Both will return a bool indicating whether the pin or unpin was successful.

Now I can inject an instance of ISecondaryPinner into my view model and put it to use, as shown in Figure 15.

Figure 15 Pinning and Unpinning with ISecondaryPinner

public FeedItemViewModel(
  IShareManager shareManager,
  ISecondaryPinner secondaryPinner)
{
  this.shareManager = shareManager;
  this.secondaryPinner = secondaryPinner;
}
public async Task Pin(FrameworkElement anchorElement)
{
  var tileInfo = new TileInfo(
    FormatSecondaryTileId(),
    this.FeedItem.Title,
    this.FeedItem.Title,
    TileOptions.ShowNameOnLogo | TileOptions.ShowNameOnWideLogo,
    new Uri("ms-appx:///Assets/Logo.png"),
    new Uri("ms-appx:///Assets/WideLogo.png"),
    this.FeedItem.Id.ToString());
    this.IsFeedItemPinned = await this.secondaryPinner.Pin(
    anchorElement,
    Windows.UI.Popups.Placement.Above,
    tileInfo);
}
public async Task Unpin(FrameworkElement anchorElement)
{
  this.IsFeedItemPinned = !await this.secondaryPinner.Unpin(
    anchorElement,
    Windows.UI.Popups.Placement.Above,
    this.FormatSecondaryTileId());
}

In Pin, I create a TileInfo helper instance, giving it a uniquely formatted id, the feed title, URIs to the logo and wide logo, and the feed id as the launch argument. Pin takes the button that was clicked as the anchor element to base the location of the pin request pop-up. I use the result of the SecondaryPinner.Pin method to determine whether the feed item has been pinned.

In Unpin, I give the uniquely formatted id of the tile, using the inverse of the result to determine whether the feed item is still pinned. Again, the button that was clicked is passed to Unpin as the anchor element for the unpin request pop-up.

After I have this in place and I use it to pin a blog post (FeedItem) to the Start screen, I can tap the newly created tile to launch the app. However, it will launch the app in the same way as before, taking me to the main page, displaying all blog posts. I want it to take me to the specific blog post that I pinned. That’s where the second half of the functionality comes into play.

The second half of the functionality goes into app.xaml.cs, from which the app is launched, as shown in Figure 16.

Figure 16 Launching the App

protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
  Frame rootFrame = Window.Current.Content as Frame;
  if (rootFrame.Content == null)
  {
    Ioc.Container.Resolve<INavigator>().
      NavigateToViewModel<MainViewModel>();
  }
  if (!string.IsNullOrWhiteSpace(args.Arguments))
  {
    var storage = Ioc.Container.Resolve<IStorage>();
    List<FeedItem> pinnedFeedItems =
      await storage.LoadAsync<List<FeedItem>>(Constants.PinnedFeedItemsKey);
    if (pinnedFeedItems != null)
    {
      int id;
      if (int.TryParse(args.Arguments, out id))
      {
        var pinnedFeedItem = pinnedFeedItems.FirstOrDefault(fi => fi.Id == id);
        if (pinnedFeedItem != null)
        {
          Ioc.Container.Resolve<INavigator>().
            NavigateToViewModel<FeedItemViewModel>(
            pinnedFeedItem);
        }
      }
    }
  }
  Window.Current.Activate();
}

I add some code to the end of the overridden OnLaunched method to check whether arguments have been passed in during the launch. If arguments have been passed, I parse the arguments into an int to be used as the feed id. I get the feed with that id from my saved feeds and pass it to FeedItemViewModel to be displayed. One thing to note is that I make sure the app already has the main page displayed, and I navigate to it first if it hasn’t been displayed. That way the user can press the back button and land on the main page whether or not he was already running the app.

Wrapping Up

In this article, I talked about my approach to implementing a testable Windows Store app using the MVVM pattern, while still leveraging some of the cool new features that Windows 8 brings to the table. Specifically, I looked at abstracting sharing, settings, roaming settings and secondary tiles into helper classes that implement mockable interfaces. Using this technique, I’m able to unit test as much of my view model functionality as possible.

In future articles, I’ll dive into more of the specifics of how I can actually write unit tests for these view models now that I’ve set them up to be more testable. I’ll also explore how these same techniques can be applied to make my view models cross-platform with Windows Phone 8, while keeping them testable.

With a little planning, you can create a compelling application with an innovative UX that leverages new key features of Windows 8—and do so without sacrificing best practices or unit tests.


Brent Edwards is an associate principal consultant for Magenic, a custom application development firm that focuses on the Microsoft stack and mobile application development. He’s also a cofounder of the Twin Cities Windows 8 User Group in Minneapolis, Minn. Reach him at brente@magenic.com.

THANKS to the following technical expert for reviewing this article: Rocky Lhotka (Magenic)
Rockford Lhotka is the CTO of Magenic, and is the creator of the widely used CSLA .NET development framework. He is the author of numerous books, and regularly speaks at major conferences around the world. Rockford is a Microsoft Regional Director and MVP. Magenic (www.magenic.com) is a company that specializes in planning, designing, building and maintaining your enterprise’s most mission critical systems. For more information go to www.lhotka.net.