Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

My MVVM Tombstone Pattern

DZone's Guide to

My MVVM Tombstone Pattern

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

I am developing an application for Windows Phone 7 and during tombstoning I had to store some data of the view model. Tombstoning is done in the code behind of the view in the “OnNavigatedFrom” and “OnNavigatedTo” methods. This forced me to tightly couple my view model to (the code behind of) my view. As I don’t want this tightly coupling, I came with the following pattern:

Properties in the view model that must be tomstoned are decorated with the “Tombstone” attribute.

[Tombstone]
public SomeSerializableType SomeProperty { get; set; }

All my view then has to contain is the following code.

public MyPage()
{
    // ...
    isNewPage = true;
}

Please note: we need the “isNewPage” variable to differentiate between restoring a tombstoned view or revisiting the view in the current application instance.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    if (isNewPage)
    {
        TombstoneHelper.RestoreState(this);

        isNewPage = false;
    }
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);

    TombstoneHelper.SaveState(this);
}

TombstoneHelper uses reflection to find all properties that must be tombstoned. It can be used with any view model and it allows my view models and views to be loosely coupled. The only drawback is that it can only be used for properties with public getters and setters, because reflection on WP7 does not allow developers to access private properties or variables.

/// <summary>
/// Attribute to indicate the ViewModel Property should be tombstoned.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class TombstoneAttribute : Attribute
{
}
public static class TombstoneHelper
{
    public static void SaveState(PhoneApplicationPage page)
    {
        foreach (PropertyInfo tombstoneProperty in FindTombstoneProperties(page.DataContext))
        {
            page.State["ViewModel." + tombstoneProperty.Name] = tombstoneProperty.GetValue(page.DataContext, null);
        }
    }

    public static void RestoreState(PhoneApplicationPage page)
    {
        foreach (PropertyInfo tombstoneProperty in FindTombstoneProperties(page.DataContext))
        {
            string key = "ViewModel." + tombstoneProperty.Name;

            if (page.State.ContainsKey(key))
            {
                tombstoneProperty.SetValue(page.DataContext, page.State[key], null);
            }
        }
    }

    // ...

    private static IEnumerable<PropertyInfo> FindTombstoneProperties(object o)
    {
        IList<PropertyInfo> tombstoneProperties = (from p in o.GetType().GetProperties()
                                                        where p.GetCustomAttributes(typeof(TombstoneAttribute), false).Length > 0
                                                        select p).ToList();

        foreach (PropertyInfo tombstoneProperty in tombstoneProperties)
        {
            if (!tombstoneProperty.CanRead || !tombstoneProperty.CanWrite)
            {
                throw new TombstoneException("The getter and the setter of a property that needs to be tomstoned must be declared public.");
            }
        }

        return tombstoneProperties;
    }
}

I also use TomstoneUtility to help tombstoning my controls. I will write more about this in a future blog article.

Source:  http://pieterderycke.wordpress.com/2011/08/25/my-mvvm-tombstone-pattern/



Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}