The Windows Phone platform introduces several problems for the software architect which, if not completely absent in standard Silverlight applications, are nevertheless more acute. The most important of these revolve around state: the state of data during the running of an application and the state of the data between runs.

Because Windows Phone design is structured around the phone navigation frame, a Silverlight out-of-browser application on the phone behaves more like a web application than a stateful application. The lifespan of a typical PhoneApplicationPage is uncertain.

While the first page in a Phone application’s backstack (usually MainPage.xaml) is tied to the lifespan of the application itself, all other pages are only accessible while currently displayed or existing in the backstack. By existing on the backstack, I here mean accessible 1. by pressing the back button and 2. by calling NavigationService.GoBack() .

Once a page is no longer visible because a user has navigated back, it is no longer accessible whether it has been garbage collected or not. In this sense, typical concepts of garbage collection are not really relevant.

Once you have navigated backwards, the same instance of the page cannot be retrieved. For instance, in the scenario where a user begins at MainPage.xaml and then navigates forward to DetailsPage.xaml, then navigates back to MainPage again, an additional navigation forward to DetailsPage.xaml will create a new instance of Details. The original instance is lost and is no longer accessible.

To make matters more difficult, if the the application launches a task based on user input, the Details page is once again lost. When the user clicks back to return to the application from a launcher or completes a chooser task, a new instance of DetailsPage will be created. When the user clicks back from DetailsPage, a new instance of MainPage will be instantiated. This termination of the current application in order to run task is known as “tombstoning”.

A final problem regarding state occurs if the phone application makes calls to web services. Because web services are much more expensive and time sensitive than on typical web applications, it is highly desirable to cache data that has been retrieved from a service.

We’ll categorize these problems as problems of 1. page data state, 2. application data state and 3. model data state, respectively.

In this post I will outline three architectural patterns for solving these three problems of state in a Windows Phone application. The patterns are View-ViewModel Pairing, Satellite ViewModels and Anchor ViewModels.

In the following posts, I will go in depth into the implementation each approach as well as provide helper classes and recommended coding patterns. Finally, I will show how they can be incorporated into existing frameworks and techniques such as MVVM Light, Caliburn Micro and IOC.

I. View-ViewModel Pairing (Buddy System)

The View-ViewModel Pairing pattern is based on a persistence pattern recommended during the CTP stage of the Phone Development Tools. In this pattern, each page or view (MainPage, DetailsPage, etc.) is responsible for the persistence of its data each time the page is added to or leaves the backstack.

In the CTP, this was often illustrated by saving the data for a particular control on the view (current.State is used here, but isolated storage would work just as well):

string token = "myToken" ; protected override void OnNavigatedTo(NavigationEventArgs e) { var store = PhoneApplicationService.Current.State; if (store.ContainsKey(token)) { string txt = store[token] as string ; if (txt != null ) this .textBox1.Text = txt; } } protected override void OnNavigatedFrom(NavigationEventArgs e) { var store = PhoneApplicationService.Current.State; if (store.ContainsKey(token)) store[token] = textBox1.Text; else store.Add(token, textBox1.Text); }

This becomes cumbersome, however, if more than a few properties require persistence. A better approach is possible if the phone application uses M-V-VM in its design (or really any UI Design Pattern that takes advantage of the DataContext property). In this case, we can simply persist the entire VM rather than iterate through each control. The above code can be simplified like this:

public Page1() { InitializeComponent(); this .Loaded += new RoutedEventHandler(Page1_Loaded); } void Page1_Loaded( object sender, RoutedEventArgs e) { if ( this .DataContext == null ) this .DataContext = new Page1ViewModel(); } string token = "myToken" ; protected override void OnNavigatedTo(NavigationEventArgs e) { var store = PhoneApplicationService.Current.State; if (store.ContainsKey(token)) this .DataContext = store[token] as string ; } protected override void OnNavigatedFrom(NavigationEventArgs e) { var store = PhoneApplicationService.Current.State; if (store.ContainsKey(token)) store[token] = this .DataContext; else store.Add(token, this .DataContext); }

This pattern collapses the problems of page data persistence and application data persistence by saving both off at the same time.

On the first navigation to this page, the Loaded event handler news up the datacontext. On subsequent visits to the page, the DataContext is set in the OnNavigatedTo event handler. Because Loaded fires after OnNavigatedTo, the DataContext is not set there. Each visit to this page will always retrieve the same ViewModel data from state, whether the View is itself new or not.

This solution also works in cases where the application has been tombstoned.

If IsolatedStorage is used instead of PhoneApplicationService.Current.State, the ViewModel will even survive between runs of the application.

Finally, because any data retrieved from a web service is typically fed into an ObservableCollection on the ViewModel, the serialization/deserialization process will also typically take care of caching relevant model data.

There is one significant shortcomings to using the Buddy System. Because there is no way (at least in the beta) to find out in the OnNavigatedTo event handler if we are returning from a tombstoned state or simply returning from a subsequent page through the back button, we may be re-hydrating the ViewModel unnecessarily.

(To be continued …)