Sunday, November 21, 2010

Chrysalis – Tombstoning support (Part II - Implementation Details)

In my last post I introduced the tombstoning features of the Chrysalis Windows Phone 7 application framework. To summarise, this allowed developers using the Model-View-ViewModel presentation pattern to easily ensure that their ViewModels could maintain their state during tombstoning. In this post I will provide an overview of how Chrysalis implements this feature. As usual, all the source code is available on the Chrysalis CodePlex site under the “Source Code” tab.

The Chrysalis Service

One of the first steps when using the Chrysalis framework is to register the ChrysalisService in the App.xaml file’s ApplicationLifetimeObjects section (see my last post for details). This takes advantage of Silverlight’s application extension services (see MSDN) which allow you to register services that run at start-up of the application. The advantage of this approach is that once registered, the rest of the application can take advantage of the features of ChrysalisService without having to use any custom base classes of pages, View Models, etc.

The bulk of the initialization of the ChrysalisService happens within the StartService method, which Siliverlight calls during application start-up.

   1: void IApplicationService.StartService(ApplicationServiceContext context)
   2: {
   3:     // Set this as the current ServiceManager via the static property
   4:  
   5:     ChrysalisService.Current = this;
   6:  
   7:     // Attach to the PhoneApplicationService lifetime events
   8:  
   9:     PhoneApplicationService phoneApplicationService = PhoneApplicationService.Current;
  10:  
  11:     phoneApplicationService.Activated += new EventHandler<ActivatedEventArgs>(PhoneApplicationService_Activated);
  12:     phoneApplicationService.Deactivated += new EventHandler<DeactivatedEventArgs>(PhoneApplicationService_Deactivated);
  13:     phoneApplicationService.Closing += new EventHandler<ClosingEventArgs>(PhoneApplicationService_Closing);
  14:  
  15:     // Attach to the page navigation events
  16:     // NB: We set off a DispatcherTimer since we need to wait for the RootVisual property to be set before we can access this
  17:  
  18:     ExecuteViaDispatcherTimer(() =>
  19:         {
  20:             if (Application.Current.RootVisual != null)
  21:                 PhoneApplicationFrame_RootVisualLoaded((PhoneApplicationFrame)Application.Current.RootVisual);
  22:         });
  23: }


Firstly we store a static reference to our service so that we can find it at any other point within the application execution. After this we get the current PhoneApplicationService (added by default to all Silverlight Windows Phone 7 applications). Since Silverlight will initialize all application services in the order we have specified in the App.xaml file, this will already have been initialized. We then attach to the Activated, Deactivated and Closing events. Note that we do not need to attach to the Launching event since we can identify this if our service has been initialized without the Activated event.


The next step is to attach to navigation events to and from pages within the application. Normally you would do this by using the NavigationService class accessible from the current page, but since at this point of application execution no page is present (and hence the application’s RootVisual property is null) we have no way to access this. The ChrysalisService class overcomes this problem by queuing a request using a DispatcherTimer with a delay of 0ms. Although this sounds like it should execute immediately, the contract for the DispatcherTimer merely states that it should call the callback at least the specified time after starting. In fact this is queued up when the dispatcher next becomes free, conveniently after the RootVisual has been initialized.



   1: private void PhoneApplicationFrame_RootVisualLoaded(PhoneApplicationFrame frame)
   2: {
   3:     // Attach to the navigation events
   4:  
   5:     frame.Navigating += new NavigatingCancelEventHandler(NavigationService_Navigating);
   6:     frame.Navigated += new NavigatedEventHandler(NavigationService_Navigated);
   7:  
   8:     // Set the current page
   9:  
  10:     CurrentPage = frame.Content as PhoneApplicationPage;
  11:  
  12:     ...
  13: }

We can then use the RootVisual (which we know for Windows Phone 7 Silverlight applications will be a PhoneApplicationFrame) to attach to the Navigating and Navigated events. We also store a reference to the current page for future use, which is also updated on any successful navigation events.


Notifying the ViewModel of application events


Now the ChrysalisService is able to identify all the application events of interest, we need a way to pass this to the current ViewModel. This is done via the two interfaces, IPhoneLifetimeAware and IPhoneNavigationAware.



   1: public interface IPhoneLifetimeAware
   2: {
   3:     void Activated();
   4:     void Closed();
   5:     void Deactivated();
   6:     void Launched();
   7: }



   1: public interface IPhoneNavigationAware
   2: {
   3:     void NavigatingFrom(NavigatingCancelEventArgs e);
   4:     void NavigatedTo(NavigationEventArgs e);
   5: }

Since we are tracking the current page at any point of time, we can identify the ViewModel associated with it by using the DataContext of that page. If the returned object implements either of the above interfaces then we can call the relevant methods in response to application events.


Storing application page state


We have not yet addressed the main use of this feature, for storing state relevant to the ViewModel during tombstoning. This is done via a third interface that the ViewModel can implement (as is done by the provided ViewModelBase class), IHasSessionState.



   1: public interface IHasSessionState
   2: {
   3:     void Initialize(object state);
   4:     object SaveState();
   5: }

 

The SaveState() method is called by ChrysalisService whenever the application is Deactivated or when navigation occurs away from the respective page. From this method the ViewModel can return some state that will be automatically persisted during tombstoning.

Similarly, the Initialize(…) method is called by ChrysalisService whenever the application is Activated or when navigation occurs to the respective page. The ViewModel can then reinstate any state required to recreate the page prior to tombstoning.


Summary


I have now described how the Chrysalis application framework allows developers to easily manage any state relating to individual ViewModels (and therefore to individual pages of an application). In my next post I will describe how Chrysalis allows applications to store application state that may span several pages, and how this can easily be persisted between tombstoning and application restarts as is required.