(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Advanced MVVM concepts using Contoso Cookbook: An analysis of a complete MVVM application: Part 3

(2 votes)
Gill Cleeren
>
Gill Cleeren
Joined Apr 02, 2010
Articles:   63
Comments:   6
More Articles
1 comments   /   posted on Mar 05, 2013
Tags:   mvvm , windows-8 , gill-cleeren
Categories:   Windows 8
Tweet

Welcome to the last part of this deep-dive on MVVM. In this last part, we are going to continue our exploration of the MVVM implementation of the ContosoCookbook application. We are going to look at tiles and push notifications in combination with an MVVM architecture first. Secondly, we’ll take a look at managing the state, again from an MVVM perspective. Finally, we are going to write unit tests for the viewmodels.

As a reminder, please take a look at the Advanced MVVM webinar we’ve done here at SilverlightShow here.

Tiles and push notifications in an MVVM application

Tiles are a vital part of a good Windows 8 store app. They allow us to create activity of the application without the application running at all. This engages the user to open the application more often, resulting in more runs overall of your application. The tile should be seen as an extension of the application, not just a shortcut that sits on the Start screen, waiting to be tapped. It’s much more.

There are a couple of ways to update the tiles so they become live tiles. The first one is local (local tile updates). A local tile update is created by the running application, meaning that the app itself has to run and while running, it will send updates to its tile (primary or secondary, badge or toasts). Of course, this type of update is then only working when the app is running, not when it’s not the foreground application. Local tile updates can also be scheduled or expiring, but code-wise, that’s exactly the same (it’s just an extra property).

Secondly, we can create periodic updates. A periodic update is an update that’s scheduled by Windows: your application instructs Windows to poll a service that will return templated XML every so often (the PeriodicRecurrence enumeration lists the possible values). Your application only has to set up this call with Windows, the rest is handled for you.

Thirdly, we can create push notifications. These allow server-side events to trigger tile updates, badge updates or toast notifications to appear on the client, pushed from the server-side/cloud using WNS (Windows Notification Service). Your app has to register with a service, the rest is again outside the scope of the application itself.

I’m first going to show you how I’ve created local tile updates in my application. These are to be created from within the application. You might start getting the idea: it’s probably going to be controlled by the ViewModel when they appear but the actual management of the tile updates is going to be done using a separate service, the TileService. I could in this service create all the tile XML data manually. However, I opt to do this using the NotificationExtensions library, which is available from the Windows 8 SDK. This library gives you a typed wrapper around the otherwise error-prone XML strings you’d have to create.

My TileService class uses this to create tile updates. Here’s the code for this class.

public class TileService: ITileService
{
    public void SendSimpleTextUpdate(string text)
    {
        ITileWideText03 tileContent = TileContentFactory.CreateTileWideText03();
        tileContent.TextHeadingWrap.Text = text;
 
        ITileSquareText04 squareContent = TileContentFactory.CreateTileSquareText04();
        squareContent.TextBodyWrap.Text = text;
        tileContent.SquareContent = squareContent;
 
        // send the notification
        TileUpdateManager.CreateTileUpdaterForApplication().Update(tileContent.CreateNotification());
    }
 
    public void SendImageAndTextUpdate(string text, string imageUrl)
    {
        ITileWideImageAndText01 tileContent = TileContentFactory.CreateTileWideImageAndText01();
 
        tileContent.TextCaptionWrap.Text = text;
        tileContent.Image.Src = "ms-appx:///Images/chinese/AsianNoodleBowl.jpg";
        tileContent.Image.Alt = "Recipe of the day";
 
        ITileSquareImage squareContent = TileContentFactory.CreateTileSquareImage();
        squareContent.Image.Src = "ms-appx:///Images/chinese/AsianNoodleBowl.jpg";
        squareContent.Image.Alt = "Recipe of the day";
        tileContent.SquareContent = squareContent;
 
        TileUpdateManager.CreateTileUpdaterForApplication().Update(tileContent.CreateNotification());
    }
 
}

Now how do I then trigger a tile update? The scenario here is (remember, demo) that when we navigate to a recipe detail page, we update the tile. So, in the RecipeDetailViewModel, I have the following code.

public IRecipe SelectedRecipe
{
    get
    {
        return selectedRecipe;
    }
    set
    {
        selectedRecipe = value;
        shareService.SharedRecipe = selectedRecipe;
        tileService.SendSimpleTextUpdate(selectedRecipe.Title);
        RaisePropertyChanged("SelectedRecipe");
    }
}

The sample also contains a ToastService which is pretty similar.

Now what about push notifications? To work with push notifications, I have to send to a service my ChannelURI. This can be retrieved using the PushNotificationChannelManager class. This URI is the unique identification for this app on this device for this user. Using this URI, WNS can send an update to this device. The sending is the only this my app itself has to do, the rest isn’t important here since all that happens outside of the context of the application itself. You can find on SilverlightShow some interesting articles that cover push notifications.

For our application, I have a service that accepts the ChannelURI and then could save it to the database. This is a WCF service; I’ve created the service reference in the ContosoCookbook.ServiceClient project. In the ContosoCookbook.Services, you can find the PushNotificationService. This is a very simple class that gets the ChannelUri and then connects with the service to send this URI to. The code is shown below.

public class PushNotificationService: IPushNotificationService
{
    public string ChannelUri { get; set; }
 
    public async void RegisterChannelUriWithService()
    {
        var channelOperation =
                await Windows.Networking.PushNotifications.PushNotificationChannelManager.
                CreatePushNotificationChannelForApplicationAsync();
        ChannelUri = channelOperation.Uri;
 
        ContosoCookbook.ServiceClient.ChannelUriService.ChannelUriServiceClient client = new ServiceClient.ChannelUriService.ChannelUriServiceClient();
        await client.SendChannelUriAsync(ChannelUri.ToString());
    }
}

To “enable” push notifications in the application, I’ve created a button in the AppBar. This button will cause a dialog (managed by the DialogService!) to appear, asking the user if he wants to enable push notifications.

If the user agrees, the following code is executed.

private void OnYesClicked(IUICommand command)
{
    var toastService = InstanceFactory.GetInstance<IPushNotificationService>();
    toastService.RegisterChannelUriWithService();
}

From then on, the app is PN-enabled. PN will arrive from the WNS when our own service posts them to WNS.

State management

Windows 8 is changing the way processes run entirely. We’re used to the concept of having users close their applications themselves. In Windows 8, the process lifecycle is entirely managed by the system. When an application isn't the foreground application anymore, it will be suspended. From the suspended state, it can be resumed which brings it back to the running state. Alternatively, it can also be terminated, meaning that it will removed from memory entirely. If you want to save the state of the application, you’ll have to do so in the suspending event, which is the last point in time you can use to actually run code that saves the state of the application.

It should be a goal of any app to restore state is termination has occurred. The user shouldn’t know that his application was terminated; if the app was terminated, the state needs to be restored so that the user can continue where he left off.

Saving state we can do for example using the options we have in the Application Data API (so using the LocalSettings/RoamingSettings or the LocalFolder/RoamingFolder). This state is persistent across application reboots so also across termination of the application. In the application launch eventargs, we can check if the application was terminated before and if so, we can restore state by reading out values from the Application Data API.

Saving the state is something that I’ve also opted to place in a separate service. It’s probably going to be triggered from the ViewModels. The implementation I’ve created is not complete, it now only allows the saving of the last page if that page is a recipe detail page. It doesn’t restore the navigation history so there’s room for improvement here. Here’s the StateService.

public class StateService : IStateService
{
    ApplicationDataContainer localSettings = null;
    public string Parameter { get; set; }
    public string ViewName { get; set; }
 
    public StateService()
    {
        localSettings = ApplicationData.Current.LocalSettings;
 
    }
 
    public void AddState(string viewName, string parameter)
    {
        this.ViewName = viewName;
        this.Parameter = parameter;
    }
 
    public void SaveState()
    {
        localSettings.Values["viewName"] = ViewName;
        localSettings.Values["parameter"] = Parameter;
    }
 
    public void LoadState()
    {
        ViewName = localSettings.Values["viewName"].ToString();
        Parameter = localSettings.Values["parameter"].ToString();
    }
 
}

From the RecipeDetailViewModel, when we navigate to this view, we are going to save this in the state (so this is certainly incremental state saving).

public IRecipe SelectedRecipe
{
    get
    {
        return selectedRecipe;
    }
    set
    {
               
        stateService.AddState(PageNames.RecipeDetailView, selectedRecipe.UniqueId);
        RaisePropertyChanged("SelectedRecipe");
    }
}

Now if the application gets terminated, we would like to arrive again on the detail page. This we can do from the OnLaunched override in the App.xaml.cs. We can check here if the app was terminated before and if so, navigate again to the last saved page.

protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
    Frame rootFrame = Window.Current.Content as Frame;
    var navigationService = InstanceFactory.GetInstance<INavigationService>();
    var stateService = InstanceFactory.GetInstance<IStateService>();
 
    var dataService = InstanceFactory.GetInstance<IRecipeDataService>();
    await dataService.GetAllRecipeGroups();
 
    SearchPane.GetForCurrentView().QuerySubmitted += App_QuerySubmitted;
 
    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active
    if (rootFrame == null)
    {
        // Create a Frame to act as the navigation context and navigate to the first page
        rootFrame = new Frame();
 
                
        navigationService.Frame = rootFrame;
 
        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            stateService.LoadState();
            if (stateService.Parameter != null && stateService.ViewName != null)
            {
                navigationService.Navigate(stateService.ViewName, stateService.Parameter);    
            }
        }
 
        // Place the frame in the current Window
        Window.Current.Content = rootFrame;
    }
}

Unit testing

The final topic I’d like to touch upon in this article is unit testing. We’ve set as one of the goals of the application the separation of concerns since this will result in much better testability. Also the fact that we’ve been using a container to manage our dependencies will help us out a lot in terms of making it easier to create unit tests and create mocks in the tests. In this last part, we are going to look at how can unit test a viewmodel. More specifically, we are going to unit test the Initialize of the RecipeGroupDetailViewModel. Just for your convenience, here’s the method.

public async void Initialize(object parameter)
{
    selectedRecipeGroupId = parameter.ToString();
 
    var recipeGroups = await recipeDataService.GetAllRecipeGroups();
    SelectedRecipeGroup = recipeGroups.Where(r => r.UniqueId == selectedRecipeGroupId).FirstOrDefault();
}

In the constructor of this class, we are using 2 services: the RecipeDataService and the NavigationService.

public RecipeGroupDetailViewModel()
{
    recipeDataService = InstanceFactory.GetInstance<IRecipeDataService>();
    navigationService = InstanceFactory.GetInstance<INavigationService>();
    InitializeCommands();
}

We’ll have to create a mock implementation for these to be able to test the VM class.

We are going to create a test using the Microsoft Unit Test Framework. In the test class, we are going to create an Initialize() method that will be used to set up the environment for the unit test. In there, I’m going to register my mock services classes with the container.

[ClassInitialize]
public static void Initialize(TestContext context)
{
    InstanceFactory.RegisterType<IRecipeDataService, FakeRecipeDataService>();
    InstanceFactory.RegisterType<INavigationService, FakeNavigationService>();
}

Of course, we haven’t written these classes yet, we’ll do that now. In fact, I’m just going to create a class that implements the interface but doesn’t do anything (since my method I’ll be testing doesn’t actually use them, that’ll be OK). Here’s the FakeNavigationService class first.

public class FakeNavigationService: INavigationService
{
    public Windows.UI.Xaml.Controls.Frame Frame
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            throw new NotImplementedException();
        }
    }
 
    public void Navigate(Type type)
    {
        throw new NotImplementedException();
    }
 
    public void Navigate(Type type, object parameter)
    {
        throw new NotImplementedException();
    }
 
    public void Navigate(string type)
    {
        throw new NotImplementedException();
    }
 
    public void Navigate(string type, object parameter)
    {
        throw new NotImplementedException();
    }
 
    public void GoBack()
    {
        throw new NotImplementedException();
    }
}

Of course, you could implement these a bit more.

Next, the FakeRecipeDataService. This will be needed in my unit test, so I’m going to create a FakeRepository as well that’ll be used by my service. In the repository, I’m using some hard-coded data, which is OK for the method I’m going to test here.

public class FakeRecipeRepository
{
    public ObservableCollection<IRecipe> Recipes;
    public ObservableCollection<IRecipeGroup> RecipeGroups;
 
    public FakeRecipeRepository()
    {
        CreateRecipes();
        CreateRecipeGroups();
    }
 
    private void CreateRecipeGroups()
    {
        RecipeGroups = new ObservableCollection<IRecipeGroup>();
            
        IRecipeGroup g1 = new FakeRecipeGroup();
        g1.UniqueId = "Chinese";
        g1.Title = "Chinese";
        g1.ShortTitle = "Chinese";
        g1.Description  = "….";
 
        IRecipeGroup g2 = new FakeRecipeGroup();
        g2.UniqueId = "Italian";
        g2.Title = "Italian";
        g2.ShortTitle = "Italian";
        g2.Description = "….";
 
        IRecipeGroup g3 = new FakeRecipeGroup();
        g3.UniqueId = "Mexican";
        g3.Title = "Mexican";
        g3.ShortTitle = "Mexican";
        g3.Description = "….";
            
        RecipeGroups.Add(g1);
        RecipeGroups.Add(g2);
        RecipeGroups.Add(g3);
    }
 
    private void CreateRecipes()
    {
        Recipes = new ObservableCollection<IRecipe>();
 
        IRecipe r1 = new FakeRecipe();
        r1.UniqueId = "1000";
        r1.Title = "Chinese Salad";
        r1.ShortTitle = "Chinese Salad";
        r1.PrepTime = 30;
        r1.SetImage("images/Chinese/Chinese Salad.jpg");
        r1.Ingredients = new ObservableCollection<string>() { "1 head Napa or bok choy cabbage", "1/4 cup sugar", "1/4 teaspoon salt", "3 tablespoons white vinegar" };
 
        IRecipe r2 = new FakeRecipe();
        r2.UniqueId = "1001";
        r2.Title = "Grilled Salmon with Chinese Barbeque Sauce";
        r2.ShortTitle = "Grilled Salmon";
        r2.PrepTime = 15;
        r2.SetImage("images/Chinese/Grilled Salmon with Chinese Barbeque Sauce.jpg");
        r2.Ingredients = new ObservableCollection<string>() { "1 tablespoon canola oil", "1 tablespoon minced garlic", "1 tablespoon minced ginger", "1 tablespoon minced green onion" };
 
        IRecipe r3 = new FakeRecipe();
        r3.UniqueId = "1002";
        r3.Title = "Fried Rice with Chinese Sausage";
        r3.ShortTitle = "Fried Rice & Sausage";
        r3.PrepTime = 10;
        r3.SetImage("images/Chinese/Fried Rice with Chinese Sausage.jpg");
        r3.Ingredients = new ObservableCollection<string>() { "1/4 cup canola oil, divided", "1/4 cup low-sodium soy sauce", "4 cups cooked white rice", "1 small bunch scallions, sliced, divided" };
 
        Recipes.Add(r1);
        Recipes.Add(r2);
        Recipes.Add(r3);
    }
 
}

Note that I’ve also created a FakeRecipe and a FakeRecipeGroup in my test project that implement the interfaces. See the code for these. In the FakeRecipeDataService, I’m now going to implement the methods needed for my test as follows.

public class FakeRecipeDataService: IRecipeDataService
{
    public async Task<System.Collections.ObjectModel.ObservableCollection<Contracts.Model.IRecipeGroup>> GetAllRecipeGroups()
    {
        return new FakeRecipeRepository().RecipeGroups;
    }
 
    public async Task<System.Collections.ObjectModel.ObservableCollection<Contracts.Model.IRecipe>> SearchRecipes(string searchValue)
    {
        return new System.Collections.ObjectModel.ObservableCollection<Contracts.Model.IRecipe>();
    }
 
    public async Task<Contracts.Model.IRecipe> FindRecipeById(string id)
    {
        return new FakeRecipe();
    }
}

All the mocks have now been created, we can now write the test. This test is now using the FakeRecipeDataService since that was registered in the container and therefore injected in the RecipeGroupDetailViewModel.

[TestMethod]
public void CheckSelectedRecipeGroupNotEmptyAfterInitialize()
{
    var vm = new RecipeGroupDetailViewModel();
 
    vm.Initialize("Chinese");
 
    Assert.IsNotNull(vm.SelectedRecipeGroup);
}

Et voila, a working unit test. Can this be improved? Of course, you could look at using a mocking framework as well to bring down the amount of code needed to write for the mocks.

Summary

That’s it for this article series. We’ve covered most Windows 8 specific features from an MVVM perspective and we’ve shown how we can then write unit tests to test our code.

I hope these articles will help you in writing great Windows 8 apps that are built on a strong MVVM foundation!

About the author

Gill Cleeren is Microsoft Regional Director, Silverlight MVP, Pluralsight trainer and Telerik MVP. He lives in Belgium where he works as .NET architect at Ordina. Gill has given many sessions, webcasts and trainings on new as well as existing technologies, such as Silverlight, ASP.NET and WPF at conferences including TechEd, TechDays, DevDays, NDC Oslo, SQL Server Saturday Switserland, Silverlight Roadshow in Sweden, Telerik RoadShow UK… Gill has written 2 books: “Silverlight 4 Data and Services Cookbook” and Silverlight 5 Data and Services Cookbook and is author of many articles for magazines and websites. You can find his blog at www.snowball.be. Twitter: @gillcleeren


Subscribe

Comments

  • cong

    Re: Advanced MVVM concepts using Contoso Cookbook: An analysis of a complete MVVM application: Part 3


    posted by cong on Apr 01, 2013 06:11
    Please show me how to state management with navigation

Add Comment

Login to comment:
  *      *       

From this series