Windows 8.1 and Windows 8.1 Phone Convergence. Part 3: App Lifecycle and MVVM

12 June 2014

In this post we continue to examine the converged lifecycle of WinRT 8.1-based Windows 8.1 and Windows Phone 8.1 apps. In particular, I discuss app lifecycle in relation to a simple MVVM-based approach, which provides automatic load/save of view model state.

For useful background reading, please see Part Two of this series of posts. It provides an overview of:

  1. The basic events raised during the converged Windows 8.1/Windows Phone 8.1 app lifecycle
  2. Lifecycle events and associated state management when using the SuspensionManager and NavigationHelper classes provided by Microsoft

MVVM and Auto-Load/Save of ViewModel State

Note that, for those wanting an introduction to the concepts involved with an MVVM-based approach, you can check-out my previous posts on the subject: Windows 8 Store Apps - Embracing the MVVM Pattern, Part One and Part Two.

Together, SuspensionManager and NavigationHelper are extremely useful. However, we've not yet seen how their use can be applied to a simple MVVM-based architecture. Also, their use is still a manually-coded process. That is, it's the job of the developer to make sure that properties are saved and restored. While this is not a problem with simple "demo" apps, real-world apps that have large numbers of properties can be more of a problem in this respect. It's easy to forget to save or load the state for a particular property, leading to hard to find bugs.

Previously, in an attempt to automate the loading and saving of state in WP8.0 (Silverlight)-based apps I developed my own simple MVVM-based toolkit. The toolkit included the ability for ViewModel properties to be auto-loaded and saved. Now that we have a converged WinRT 8.1 API, I decided to re-implement the MVVM "auto-state" approach (I'll be making a WinRT 8.1 version of the toolkit available as a NuGet package soon).

We'll first take a brief look at how to implement a very simple MVVM approach to building WinRT 8.1 apps. Then we can add the ability for ViewModel objects to auto-save/load their state. If you want to view a demo project that includes all the MVVM and auto-state code, it can be downloaded from here (you'll need Visual Studio 3013 Update 2 or higher to build it).

A Simple MVVM Approach

It's strange, given that it's generally accepted that MVVM is the preferred way to approach the development of non-trivial apps, that Microsoft don't provide a simple MVVM framework and associated Visual Studio app templates. Microsoft's Mike Taulty has some insightful things to say on the topic. However, there are some excellent open-source frameworks such as MVVM Light available.

I decided to re-implement my own (very lightweight) MVVM toolkit for WinRT 8.1 simply because it's useful and works for me! If you find any of my code useful, feel free to reuse or adapt it for your own needs.

So, assuming we're going to continue using SuspensionManager and NavigationHelper (and they do provide a number of useful features such as saving/restoring the current page, navigation history, etc.), what are the very minimum set of MVVM bits missing after you've created a Universal App? As I see it, they are as follows:

  • A simple IoC Container
  • A ViewModelLocator class
  • Plumbing in App.xaml to instantiate a global ViewModelLocator object
  • Creation of ViewModel interfaces and classes for each each View type
  • Plumbing in each View (page) to set the appropriate ViewModel as the DataContext
  • Plumbing in each View to use the NavigationHelper.LoadState/SaveState handlers to load/save state

Let's briefly look at the above points individually in more detail.

Simple IoC Container

In the WP8.0 version of my MVVM toolkit I used Ninject. However, currently (June, 2014) there doesn't seem to be a version compatible with WinRT 8.1 Universal Apps. So, I decided to switch to using Microsoft's own Unity dependency injection container, which has support for constructor, property, and method call injection. From what I can see, version "3.5.1405-prerelease" is the only version that currently supports Universal Apps. It can be downloaded as a NuGet package, but when searching for packages from Visual Studio, make sure to include pre-release packages.

Using Unity is pretty straightforward. I wrap Unity in a class named IocContainer and create all the required type mappings when the app's launched:

protected async override void OnLaunched(LaunchActivatedEventArgs e)
{
    // Initialize the IoC container type bindings
    InitializeIocBindings();

    :
}

private void InitializeIocBindings()
{
    /// Register ViewModel interfaces and types (as singletons)

    IocContainer.Container

        .RegisterType(
            typeof(IView1Model), 
            typeof(View1Model), 
            null, 
            new ContainerControlledLifetimeManager()) 

        .RegisterType(
            typeof(IView2Model), 
            typeof(View2Model), 
            null, 
            new ContainerControlledLifetimeManager());
}
using System;
using Microsoft.Practices.Unity;

namespace WP81DemoLifeCycleMvvm.Common
{
    /// <summary>
    /// Inversion of Control container. Used to facilitate loose coupling between services 
    /// and dependent objects
    /// </summary>
    public static class IocContainer
    {
        /// <summary>The Unity Ioc object</summary>
        public static readonly IUnityContainer Container = new UnityContainer();

        /// <summary>
        /// Gets a concrete implementation of the specified interface. 
        /// The actual type returned depends on the binding which has been established 
        /// between the interface and a concrete type.
        /// </summary>
        /// <typeparam name="T">The interface for the required type</typeparam>
        /// <returns>Returns a concrete implementation of the specified interface</returns>
        public static T Get<T>() where T : class
        {
            return Get<T>(null);
        }

        /// <summary>
        /// Gets a concrete implementation of the specified interface. The actual type 
        /// returned depends on the binding  which has been established between the 
        /// interface and a concrete type. You may optionally supply constructor parameters 
        /// that will be injected into the resolved type. For example, if the target type 
        /// constructor is defined as ctor(string name, int index) then pass 
        /// ("name", "my name val", "index", 99) to this method.
        /// </summary>
        /// <typeparam name="T">The interface for the required type</typeparam>
        /// <param name="parameters">
        /// Array of parameter names and values to be passed to the type's constructor
        /// </param>
        /// <returns>Returns a concrete implementation of the specified interface</returns>
        public static T Get<T>(params object[] parameters) where T : class
        {
            ResolverOverride[] ctorParams = null;

            try
            {
                if(parameters != null)
                {
                    ctorParams = new ResolverOverride[parameters.Length/2];
                    for(var i = 0; i < parameters.Length; i += 2)
                        ctorParams[i] = new ParameterOverride(
                            parameters[i] as string, parameters[i + 1]);
                }

                var resolvedObject = ctorParams == null ? 
                    Container.Resolve<T>() : Container.Resolve<T>(ctorParams);

                if(resolvedObject != null) return resolvedObject;

                throw new Exception();
            }
            catch(Exception ex)
            {
                Logger.Log(ex, "Fatal exception during IoC resolution of type " + typeof(T).Name);
                return default(T);
            }
        }
    }
}
ViewModelLocator class

In this example, the ViewModelLocator class simply holds properties that use the IoC Container to retrieve the appropriate ViewModel instance:

using WP81DemoLifeCycleMvvm.Common;

namespace WP81DemoLifeCycleMvvm.ViewModel
{
    public class ViewModelLocator
    {
        public IView1Model View1Model { get { return IocContainer.Get<IView1Model>(); }}
        public IView2Model View2Model { get { return IocContainer.Get<IView2Model>(); }}
    }
}
Instantiate a global ViewModelLocator

The ViewModelLocator is instantiated in App.xaml:

<Application
    x:Class="WP81DemoLifeCycleMvvm.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vm="using:WP81DemoLifeCycleMvvm.ViewModel"
    xmlns:local="using:WP81DemoLifeCycleMvvm">

    <Application.Resources>
        <vm:ViewModelLocator x:Key="ViewModelLocator" />
    </Application.Resources>
</Application>

Using this technique, the ViewModelLocator is assigned a resource key and becomes available in the XAML for each View, allowing us to set the ViewModel as the DataContext:

<Page
    x:Class="WP81DemoLifeCycleMvvm.View.View1"
    :>
    
    <Page.DataContext>
        <Binding Source="{StaticResource ViewModelLocator}" Path="View1Model" />    
    </Page.DataContext>
Create IViewModel and ViewModel for each each View type

The code shown below provides a very simple definition for a ViewModel. Note that, to keep the example simple, I've not created a Model. So, I have the "model properties" defined directly in the ViewModel. In a real-world MVVM app, the ViewModel surfaces Model data, and raises associated change events, to the View (allowing the View to data bind).

public interface IView1Model
{
    event PropertyChangedEventHandler PropertyChanged;
    
    string MyText { get; set; }

    void LoadState();
    void SaveState();
}

public class View1Model : IView1Model, INotifyPropertyChanged 
{
    public string MyText
    {
        get { return _myText; }
        set
        {
            _myText = value; 
            OnPropertyChanged();
        }
    }

    private string _myText = string.Empty;

    public override void SaveState()
    {
        // Save state using NavigationHelper    
    }

    public override void LoadState()
    {       
        // Load state using NavigationHelper    
    }
}
In each View set the ViewModel as the DataContext

We've seen how to set the View's DataContext to the ViewModel in XAML. Here's how we can access the ViewModel in the View's code-behind:

public sealed partial class View1 : Page
{
    public IView1Model ViewModel { get; set; }
    :
    
    public View1()
    {
        this.InitializeComponent();

        // Get a reference to our view model, which has been set in XAML
        ViewModel = this.DataContext as IView1Model;
        if(ViewModel == null) throw new NullReferenceException();
        :
In each View load/save state

This is done in a very similar manner to that described earlier:

public void NavigationHelperLoadState(object sender, LoadStateEventArgs e)
{
    if(e.PageState != null && e.PageState.ContainsKey("MyText"))
        ViewModel.MyText = e.PageState["MyText"] as string;
}

public void NavigationHelperSaveState(object sender, SaveStateEventArgs e)
{
    e.PageState["MyText"] = ViewModel.MyText;
}

ViewModel Auto-State

The above all works well, but we still have the problem of having to manually code the load/save of ViewModel state. To solve this problem I:

  1. Introduce a common base class named ModelBase for Model and ViewModel classes. This base class provides load/save methods so that models and view models can automatically serialize/deserialize their own state to/from a persistent store
  2. Define a custom attribute class named [AutoState], and use it to decorate each Model or ViewModel property that needs to be auto-loaded/saved
  3. In the View, I add calls to ViewModel.LoadState() in the NavigationHelper.LoadState handler, and ViewModel.SaveState() in the NavigationHelper.SaveState handler.

    Each Model and ViewModel hands-off the load/save request to methods in ModelBase, which use reflection to examine its own properties, looking for the [AutoState] attribute
  4. The state storage mechanism is abstracted, via the IStateHelper interface allowing different storage providers to be plugged-in as required. For example, the LocalStoreStateHelper class implements IStateHelper and stores state data in the ApplicationData.Current.LocalFolder folder

In summary, whenever I want any Model or ViewModel property state to be automatically save/restored, I simply do this:

[AutoState]
public ObservableCollection<string> MyCollection
{
    get { return _myCollection; }
    set { _myCollection = value; OnPropertyChanged(); }
}

ModelBase and LocalStoreStateHelper

You can download a sample project containing the code for ModelBase and LocalStoreStateHelper from here.

Example usage

The following example shows how to use the [AutoState] attribute to decorate ViewModel properties for automatic load/save of state. Notice how ModelBase is able to:

  • Supply default values specified declaratively in the ViewModel using the form [AutoState(DefaultValue = object)]
  • Specify how to handle the auto-load of null values using the form [AutoState(RestoreNullValues = true|false)]
  • Automatically serialize collections of objects like ObservableCollection<string>, which SuspensionManager is unable to handle
  • Support serialization/deserialization of complex custom types that implement the ISerialize interface
public class View1Model : ModelBase, IView1Model, INotifyPropertyChanged 
{
    [AutoState(DefaultValue = "View1", RestoreNullValues = false)]
    public string MyText
    {
        get { return _myText; }
        set { _myText = value; OnPropertyChanged(); }
    }

    [AutoState]
    public ObservableCollection<string> MyCollection
    {
        get { return _myCollection; }
        set { _myCollection = value; OnPropertyChanged(); }
    }

    [AutoState]
    public List<int> MyInts
    {
        get { return _myInts; }
        set { _myInts = value; OnPropertyChanged();}
    }

    [AutoState]
    public ObservableCollection<MyCustomType> MyCustomCollection
    {
        get { return _myCustomCollection; }
        set { _myCustomCollection = value; OnPropertyChanged();}
    }

    [AutoState]
    public List<bool> MyBools
    {
        get { return _myBools; }
        set { _myBools = value; OnPropertyChanged();}
    }

    private string _myText = string.Empty;
    private ObservableCollection<string> _myCollection = new ObservableCollection<string>();
    private List<int> _myInts = new List<int>();
    private ObservableCollection<MyCustomType> _myCustomCollection = 
        new ObservableCollection<MyCustomType>();
    private List<bool> _myBools = new List<bool>();

    public override void SaveState()
    {
        SaveAutoState();  // Save all properties marked with the [AutoState] attribute
    }

    public override void LoadState()
    {           
        LoadAutoState();  // Restore all [AutoState] properties
    }
}

The load/save state code in the View then becomes simply:

public void NavigationHelperLoadState(object sender, LoadStateEventArgs e)
{
    ViewModel.LoadState();  // Load ViewModel state
}

public void NavigationHelperSaveState(object sender, SaveStateEventArgs e)
{
    ViewModel.SaveState();  // Save ViewModel state
}

References