Using SuspensionManager for Saving Application State
One of the important aspects of Windows Store application development is the application lifecycle. While at first it might seem a minor detail that can be taken care of late in the application development process, it can affect the application architecture quite profoundly. Therefore it's a good idea to address it soon enough to avoid unplanned refactoring when the application is almost complete.
Since the basics of Windows Store application lifecycle are well documented, I'm not going to repeat them here. Rather, I'm going to focus on a single aspect of it: the transition between the running and the suspended state. During this transition the Application.Suspending
event is called to allow the application to save its state to local storage which can be used to resume the application in case it gets terminated.
If you base your application on correct templates for Windows Store applications which are included with Visual Studio 2012 this will already be mostly taken care of for you. Both Grid App and Split App templates include a SuspensionManager
class in their Common
folder and also call it at all the appropriate spots in the application. This class can save the application state to the local storage into a file named _sessionState.xml
and later reload it. It includes the complete frame navigation history consisting of the loaded pages and the parameters they were loaded with. As long as you only need this parameter to completely restore the state of the page, you don't have to do anything extra.
There's a function in Visual Studio that makes it easy to test that, it's just hidden in its default configuration. To access it you first need to show the Debug Location toolbar. There is a dropdown menu on it which is enabled only when the application is running. It contains three items: Suspend, Resume, and Suspend and shutdown. The last one is your friend when you want to test how your application resumes after it has been terminated: just run the application, navigate to the page you want to test and invoke the Suspend and shutdown action. The next time you run the application, it should resume with the same state.
Unfortunately you can easily break suspending and resuming in your application if you pass a custom class as the page parameter. Try it out! For example you can replace the UniqueId
that's being passed between the GroupedItemsPage
and GroupDetailPage
in the Grid App template with a complete instance of SampleDataGroup
. If you now Suspend and shutdown your application, the SuspensionManager.SaveAsync
method will throw a non-descriptive SuspensionManagerException
:
SuspensionManager failed
Investigating the call stack reveals that the failing method was actually Frame.GetNavigationState
which has thrown an easier to comprehend COMException
(It sounds like an oxymoron, doesn't it?):
WinRT information: GetNavigationState doesn't support serialization of a parameter type which was passed to Frame.Navigate.
What can we do about it? As it turns out, not much. According to this Connect issue, it's a limitation of the platform that the Frame
object can't serialize complex objects. The workaround is to only pass around the unique identifiers as it is demonstrated in both above mentioned Visual Studio templates. If you don't mind reloading your data every time a new page is loaded, that's not a problem. Otherwise you need to cache your data in memory.
Both templates contain only sample data and cache it in a static member. Unfortunately static members don't lend themselves well to the MVVM pattern and testing therefore it's a better idea to inject the application state into the view models. I like the following approach using the view model locator pattern:
public class ViewModelLocator
{
private static ApplicationState applicationState;
static ViewModelLocator()
{
applicationState = new ApplicationState();
}
public ViewModel ViewModel
{
get
{
return new ViewModel(applicationState);
}
}
}
The code can be improved further by using an IoC container which will ensure a single instance of the application state even without having a static reference to it in the view model locator as long as you scope it properly. But that's already a subject for another post.