When the WP7 beta dev tools were released, developers were pleased to find several new events hanging off of Shell.PhoneApplicationService for managing a phone application’s life cycle: Launching, Closing, Activated and Deactivated. The first two events are obviously thrown when an application is first launched and when it is closed down (either by hitting the back button past the first page in the app or, interestingly, by throwing an unhandled exception).
The Activated and Deactivated events are thrown when the application is interrupted mid-run – typically by launching a Windows Phone task or by hitting the Windows button on the phone. This is a scenario known as tombstoning.
If you are not yet familiar with the concept of tombstoning or simply need a refresher, please read Yochay Kiriaty’s post on the subject here.
These new events give Silverlight for WP7 developers a way to manage application state outside of the navigation events on each page. Two design considerations must be taken into account, however, in order to take advantage of this centralized way of persisting state information. 1. All data that needs to be persisted between application runs or during tombstoning must be accessible from the event handlers for the four lifecycle events. 2. If application data persistence is handled outside of each page, another mechanism is required for persisting page state information as well as model data between page instantiations.
2. Anchor ViewModel
The Pledge
An anchored boat is able to stay in one place while currents wash away unmoored items floating on the ocean. Anything tethered to the boat, or tethered to something tethered to the boat, is likewise stationary. This is the main concept behind an Anchor ViewModel phone architecture.
While the developer has little control over the lifespan of a PhoneApplicationPage, he can nevertheless control the life of the state of the page if he is using an M-V-VM pattern. There are several artifacts in the application that ViewModels can be tethered to and which survive the constant newing up and dereferencing of Views in a Silverlight app for the phone. The Application itself can serve as an anchor. It has a RootVisual, the PhoneApplicationFrame, which hosts (typically) a MainPage.xaml PhoneApplicationPage that doesn’t go out of scope until the application is terminated. ViewModels can be tethered to any of these objects in order to give them a lifespan beyond the lifespan of their respective Views.
The Windows Phone Databound Application Template that is provided with Visual Studio for Windows Phone demonstrates this anchoring technique. In this sample template, the MainViewModel object hangs off of the App class. When the MainPage view is instantiated, the App’s MainViewModel instance is assigned to the MainPage view’s DataContext. At this point, the lifecycle of MainViewModel is tethered to the lifecycle of App rather than to the lifecycle of MainPage.
The DetailsPage view, in turn, retrieves its DataContext from a ViewModel hanging off of MainViewModel which hangs off of App. The lifecycle of the ItemViewModel is tethered to the lifecycle of MainViewModel which in turn is anchored to the App class.
The Turn
This is more evident if we change the default application based on the Windows Phone Databound Application Template a little. First, MainViewModel can be modified to include a SelectedItem property:
private ItemViewModel _selectedItem; public ItemViewModel SelectedItem { get { return _selectedItem; } set { _selectedItem = value; NotifyPropertyChanged("SelectedItem"); } }
Then the ListBox in MainPage.xaml should have it’s SelectedItem attribute bound to this property:
<ListBox x:Name="MainListBox"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
ItemsSource="{Binding Items}"
We simplify the MainListBox’s SelectionChanged event handler like so:
// Handle selection changed on ListBox private void MainListBox_SelectionChanged(object sender , SelectionChangedEventArgs e) { // If selected index is -1 (no selection) do nothing if (MainListBox.SelectedIndex == -1) return; // Navigate to the new page NavigationService.Navigate(new Uri("/DetailsPage.xaml" , UriKind.Relative)); }
Finally, in our details page, we would set the DataContext in the constructor rather than in the OnNavigatedTo method:
public DetailsPage() { InitializeComponent(); if (DataContext == null) DataContext = App.ViewModel.SelectedItem; }
The point to remember here is that when the DetailsPage view goes out of scope through a back page navigation, the VM for DetailsPage continues to exist in memory and remains accessible because it is tethered to MainViewModel.
More importantly, if we change a property on the ItemViewModel we are using for the DataContext of DetailsPage, these changes would be persisted back to MainViewModel after we navigate away from DetailsPage, and would remain persisted for as long as the current App is held in memory.
The Prestige
The payoff to this pattern is that we can now handle tombstoning by persisting all of our VMs centrally in a few lines of code:
string _appToken = "all_vms"; private void Application_Activated(object sender , ActivatedEventArgs e) { var store = PhoneApplicationService.Current.State; if (store.ContainsKey(_appToken)) viewModel = store[_appToken] as MainViewModel; } private void Application_Deactivated(object sender , DeactivatedEventArgs e) { var store = PhoneApplicationService.Current.State; if (store.ContainsKey(_appToken)) store[_appToken] = viewModel; else store.Add(_appToken, viewModel); }
Because all view models are anchored to MainViewModel, saving our top level view model will also automatically serialize and later deserialize all the of VMs hanging off of MainViewModel as well as the state of their properties.
We have effectively made the entire default Databound Phone Application sample “tombworthy”. If you now tombstone the application while navigated to the DetailsPage (for instance by pressing the Windows button at bottom center), it will be restored when you return to the application.
In an application with 5, 10 or more VMs, this will be very handy.
The next post in this series will cover the Satellite ViewModels pattern, which uses many of the same principles but allows greater control over how and what state gets persisted.