Windows Phone 8 Auto-Save and Restore State

13 July 2013

Summary:
This post shows a useful pattern for auto-saving and restoring object state to/from either persistent or transient state.

The "One-Diagram" Overview of WP8 Lifecycle

I'm currently in the process of putting the finishing touches to a Windows Phone 8 app that has a number of key objects, each of which has complex state that needs to be managed as part of the app's lifecycle. I decided early on in the development cycle that I wanted a simple (automatic, as far as possible) way of saving and restoring the properties of the various objects to and from either persistent (isolated) storage or the transient (PhoneApplicationService.Current.State or PhoneApplicationPage.State) state dictionaries.

Unlike desktop apps, WP8 apps are "activated" and "deactivated" (or "tombstoned") by the OS. This means that the developer has to manage the state of the app's objects. In this respect, WP8 apps are very similar to Windows 8 store and ASP.NET web apps.

The following image represents my attempt to summarize the lifecycle of a WP8 app in a single diagram:

The lifecycle of WP8 apps is well-documented on MSDN, so I don't propose to repeat that information here. Rather, I want to share the patterns and code I developed for semi-automatically managing object state.

Overview of [AutoState]

The pattern I developed can be summarized very simply. Essentially, properties that need to be auto-saved/restored from a persistent or transient state store are marked with a custom [AutoState] attribute. A common base class for model classes provides Save/Restore state methods which use reflection to enumerate their properties looking for those decorated with [AutoState]. Property values are then saved/restored using the appropriate state helper class:

  • Key objects (including the app's View Model, if using the MVVM pattern) that need to persist state derive from a common base class (ModelBase), which provides various overloaded SaveState and RestoreState methods
  • IStateHelper describes an interface for a generalized state helper class. The ModelBase codes against this interface, and two classes (PersistentStateHelper and TransientStateHelper) implement the IStateHelper interface
  • An IoC container is used to bind the IStateHelper interface to a concrete implementation (I used Ninject). This isn't really necessary, you could simply hard-code a reference to the required state helper implementation in the ModelBase
  • Each View in the app overrides the page's OnNaviagatedTo and OnNavigatedFrom methods, and calls the view model's SaveState and RestoreState methods (the view model can then call the Save/Restore methods on any of its key objects that derive from ModelBase)
  • A custom attribute class is defined that allows auto-save/restore properties to be marked with [AutoState]. These properties are automatically saved and restored to/from state as necessary

The following code shows an example from the view model:

namespace WPLifecycleDemo.Model
{
    public class MainPageViewModel : ModelBase
    {
        private string _appName = "Lifecyle Demo";
        private string _pageName = "Demo";
        private string _myProp;
        
        [AutoState(defaultValue:"No Date Yet")]
        public string MyProp
        {
            get { return _myProp; }
            set { _myProp = value; OnPropertyChanged();}
        }

        [AutoState]
        public string AppName
        {
            get { return _appName; }
            set { _appName = value; OnPropertyChanged();}
        }
        
        [AutoState]
        public string PageName
        {
            get { return _pageName; }
            set { _pageName = value; OnPropertyChanged();}
        }
        
        public override void SaveState()
        {
            SaveAutoState();  // Save all properties marked with the [AutoState] attribute
        }

        public override void RestoreState()
        {
            // Restore all properties marked with the [AutoState] attribute

            // In a real-world app you'd decide if state needed to be restored
            // by using the value of IsApplicationInstancePreserved, which is
            // passed in the ActivatedEventArgs param to the Application_Activated
            // event handler in your App class

            RestoreAutoState();  
        }

        public MainPageViewModel()
        {
            MyProp = DateTime.Now.ToShortTimeString();
        }
    }
}

The custom attribute class is defined as follows:

namespace WPLifecycleDemo.Common
{
    [AttributeUsage(AttributeTargets.Property)]
    public class AutoState : Attribute
    {
        public object DefaultValue { get; set; }
        
        public AutoState() 
        { 
            DefaultValue = null;
        }
        
        public AutoState(object defaultValue)
        {
            DefaultValue = defaultValue;
        }
    }
}

The relevant Save/Restore methods from the ModelBase are defined as follows. Notice how we use reflection to enumerate properties, looking for those marked [AutoState]:

namespace WPLifecycleDemo.Model
{
    /// <summary>Base class for core services and the view model</summary>
    public abstract class ModelBase : INotifyPropertyChanged
    {
        private readonly string _stateSavedId;
        private readonly IStateHelper _stateHelper;
        
        protected ModelBase()
        {
            // This will use the name of the class that inherits from ModelBase to be the
            // key we use to define if state has been saved for this object. So individual
            // state items will have keys like "LocationService.TrackLocation"
            _stateSavedId = this.GetType().Name + ".";

            // Get the concrete state helper via the IoCContainer
            _stateHelper = IocContainer.Get<IStateHelper>();
        }
        
        public virtual bool IsStateAvailable()
        {
            return _stateHelper.ContainsKey(_stateSavedId);
        }
        
        public virtual void SetStateItem(string key, object value)
        {
            key = _stateSavedId + key;
            _stateHelper[_stateSavedId] = true;
            _stateHelper[key] = value;
        }
        
        public virtual T GetStateItem<T>(string key)
        {
            key = _stateSavedId + key;
            return _stateHelper.ContainsKey(key) ? (T)_stateHelper[key] : default(T);
        }
        
        public virtual T GetStateItem<T>(string key, T defaultValue)
        {
            key = _stateSavedId + key;
            return _stateHelper.ContainsKey(key) ? (T)_stateHelper[key] : defaultValue;
        }
        
        public virtual void SaveAutoState(bool saveNullValues = false)
        {
            try
            {
                var typeInfo = this.GetType().GetTypeInfo();

                // Get all our properties
                var properties = typeInfo.GetProperties();

                // Enumerate all properties looking those marked [AutoState]
                foreach (var pi in properties)
                {
                    if (pi == null) continue;

                    var ca = pi.GetCustomAttribute<AutoState>();
                    if (ca == null) continue;  // Skip it - property not marked [AutoState]

                    var val = pi.GetValue(this);
                    if (val != null)
                    {
                        if (val.ToString().StartsWith("System.Collections")) continue;
                        if (val.ToString().EndsWith("RelayCommand")) continue;
                    }
                    else if (!saveNullValues && ca.DefaultValue == null) continue;

                    SetStateItem(pi.Name, val ?? ca.DefaultValue);  
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex, "Error saving state for " + _stateSavedId, new StackFrame(0, true));
            }
        }
        
        public virtual void RestoreAutoState(bool restoreNullValues = false)
        {
            try
            {
                if (!IsStateAvailable()) return;

                var typeInfo = this.GetType().GetTypeInfo();
                var properties = typeInfo.GetProperties();

                foreach (var pi in properties)
                {
                    if (pi == null) continue;

                    var ca = pi.GetCustomAttribute<AutoState>();
                    if (ca == null) continue;  // Property was not marked [AutoState]

                    var val = pi.GetValue(this);
                    if (val != null)
                    {
                        if (val.ToString().StartsWith("System.Collections")) continue;
                        if (val.ToString().EndsWith("RelayCommand")) continue;
                    }
                    else if (!restoreNullValues && ca.DefaultValue == null) continue;

                    pi.SetValue(this, GetStateItem(pi.Name, ca.DefaultValue));
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex, "Error restoring state for " + _stateSavedId, new StackFrame(0, true));
            }
        }
    }
}

Finally, here's a snippet from the PersistentStateHelper class, showing a couple of the Get/Set methods:

public class PersistentStateHelper : IStateHelper
{
    public object this[string key]
    {
        get { return IsolatedStorageSettings.ApplicationSettings[key]; }
        set { IsolatedStorageSettings.ApplicationSettings[key] = value; }
    }

    public T Get<T>(string key)
    {
        // If the setting doesn't exist, create it with a default value
        if (!IsolatedStorageSettings.ApplicationSettings.Contains(key))
            IsolatedStorageSettings.ApplicationSettings[key] = default(T);
        return (T)IsolatedStorageSettings.ApplicationSettings[key];
    }

    public void Set(string key, object value)
    {
        IsolatedStorageSettings.ApplicationSettings[key] = value;
    }

Conclusion

In my project I found the ability to simply mark key object properties with the [AutoState] attribute really useful. It freed me from having to remember to manually code (or worse, cut & paste code) for the saving and restoring of properties. Using this pattern you can simply forget about the mechanics of saving/restoring values, knowing that they'll be taken care of automatically.

Although I've shown how to use [AutoState] for WP8 apps, it could also easily be adpated for use with Windows 8 store apps.