Tombstoning made easy in Windows Phone MVVM

Tombstoning your application data in Windows Phone can be tricky when you are using the MVVM pattern. In order to save your data you have to do it between the NavigatedTo and NavigatedFrom events of the page. In your ViewModel you don’t have access to these events. So in order to do that you need to bubble these events somehow up to your ViewModel, thus making your ViewModel dependent on your pages, which is not such a good idea.

Apart from that, you also need to manually save all the data in your ViewModel and restore it when appropriate.

Therefore I have created a helper library that does all the plumbing for you.

I will explain the usage first and then take explain how it works.

Setup

First of all, the TombstoneHelper needs to be aware of the different events that occur. So we need to setup a few things in order to get going:

  1. Hook up the application_activated event.
    To do this add the following line in the Application_Activated event handler in app.xaml:
    TombstoneHelper.Application_Activated(sender, e);
  2. Hook up the page events.
    The helper needs to be aware of the page navigation events.
    To make sure this gets done for all the pages, the easiest way is to create a base page and override the OnNavigatedTo and OnNavigatedFrom methods (otherwise you will have to do this in every page):
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);
        TombstoneHelper.page_OnNavigatedTo(this, e);
    }
    
    protected override void OnNavigatedFrom(NavigationEventArgs e)
    {
        base.OnNavigatedFrom(e);
        TombstoneHelper.page_OnNavigatedFrom(this, e);
    }

Usage

With these two items in place, we can now handle tombstoning unobtrusively in our ViewModels through the use of attributes:

[Tombstone()]
public SomeClass MyObject {
   get { return _myobject; }
    set {
      _myobject = value;
      RaisePropertyChanged("MyObject");
   }
}

How does it work?

TombstoneHelper

The first thing we do is call the Application_Activated-method in app.xaml.

private static bool _hasbeentombstoned = false;
public static void Application_Activated(object sender, ActivatedEventArgs e)
{
    if (!e.IsApplicationInstancePreserved)
    {
        _hasbeentombstoned = true;
    }
}

This simply keeps track of whether the application has been tombstoned at a certain point. This is necessary in order to avoid unnecessary restoring before the application has been tombstoned. It also makes sure that nothing is restored when the application was dormant.

After that there are two other methods:

public static void page_OnNavigatedFrom(PhoneApplicationPage sender, NavigationEventArgs e)
{
    if (!(e.NavigationMode == NavigationMode.Back))
    {
        if (sender != null)
        {
            ApplicationState.Save(sender);
        }
    }
}

This checks whether we’re not navigating backwards (in which case we don’t need to save any state) and then uses the ApplicationState-class to save the state.

private static List<PhoneApplicationPage> restoredpages = new List<PhoneApplicationPage>();
public static void page_OnNavigatedTo(PhoneApplicationPage sender, NavigationEventArgs e)
{
    if (sender != null && _hasbeentombstoned && !restoredpages.Contains(sender))
    {
        restoredpages.Add(sender);
        ApplicationState.Restore(sender);
    }                      
}

This will restore the state if the application has been tombstoned before and if the page has not been restored before.

ApplicationState

This is a helper class that saves and restores the state of the pages. To do this it uses reflection to read and write to the properties marked with the Tombstone-attribute.

private static IEnumerable<PropertyInfo> GetTombstoneProperties(object ViewModel)
{
    if (ViewModel != null) {
        IEnumerable<PropertyInfo> tsProps = from p in ViewModel.GetType().GetProperties() 
                                        where p.GetCustomAttributes(typeof(TombstoneAttribute), false).Length > 0
                                        select p;

        foreach (PropertyInfo tsProp in tsProps) {
            if (!tsProp.CanRead || !tsProp.CanWrite) {
                throw new TombstoneException(string.Format("Cannot restore value of property {0}. Make sure the getter and setter are public", tsProp.Name));
            }
        }
        return tsProps;
    } else {
        return new List<PropertyInfo>();
    }
}

This receives a ViewModel of type Object (in reality this is the page’s DataContext, usually your ViewModel).
It selects all the properties that have the tombstone attribute applied and checks whether it’s getters and setters are public. If not it will throw a tombstone-exception informing you of the property that cannot be tombstoned.

static internal void Save(PhoneApplicationPage page)
{
    foreach (PropertyInfo tombstoneProperty in GetTombstoneProperties(page.DataContext))
    {
        string key = GetKey(tombstoneProperty);
        JsonSerializerSettings settings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects };
        object value = tombstoneProperty.GetValue(page.DataContext, null);
        page.State[key] = JsonConvert.SerializeObject(value, Formatting.None, settings);
    }
}

The save-method gets all the tombstone properties, available in the page’s Datacontext.
It reads the value of each of these properties through reflection and then serializes and saves them to the page’s state dictionary.

static internal void Restore(PhoneApplicationPage page)
{
    foreach (PropertyInfo tombstoneProperty in GetTombstoneProperties(page.DataContext))
    {
        if (tombstoneProperty.GetValue(page.DataContext, null) == null)
        {
            string key = GetKey(tombstoneProperty);
            if (page.State.ContainsKey(key))
            {
                tombstoneProperty.SetValue(page.DataContext, JsonConvert.DeserializeObject((string)page.State[key], tombstoneProperty.PropertyType), null);
            }
        }
    }
}

The restore method does the inverse of the save method. It iterates over all the properties with the tombstone-attribute and applies the deserialized value from the page’s state dictionary through reflection.
It does an extra check to see whether the property currently holds a null. If so, it will not overwrite it.

So that’s it, with these few items in place, you can just apply an attribute to your ViewModel properties and it will work without any manual intervention.

Note that for the sake of brevity I have not included all the code in this post.
You can download the complete code at Codeplex.

Or, if you prefer you can install it via nuget:

Install-Package WindowsPhone.MVVM.Tombstone