A few weeks back I wrote a blog post about how the recent announcement of PhoneGap support for Windows Phone 7 (WP7) which makes it possible to develop HTML5-based applications. In my previous blog post I showed the development of a simple HTML5 / JavaScript application which PhoneGap wraps up within a Silverlight application 'shell' allowing it to be deployed to your phone and potentially submitted to the Marketplace.
However, in order to pass the various Marketplace requirements and gain certification, your application must correctly handle the application lifecycle. With the recent Mango release, the lifecycle has become a little more complicated (although better! in that it adds multi-tasking / fast-app switching). I have also covered the lifecycle in a previous blog post and demonstrated how you can handle the various lifecycle events within an MVVM application.
The most tricky part of the application lifecycle that as a developer you need to handle is the tombstoned state, where your application is terminated (i.e. stopped and removed from memory). It is your responsibility to save enough state in order that when your tombstoned application is restarted, it looks to the user as if your application never stopped running, i.e. you restore your application UI to its original state.
The Mango application lifecycle is illustrated below:
The PhoneGap events API includes pause and resume events, which can be used to detect when the application transitions to and from the dormant state, however, for WP7 these events do not give us enough information. When resuming, we need to know whether it has resumed from a dormant or a tombstoned state. Considering that the tombstoned state is peculiar to WP7 (Android, and iPhone simply have a suspend / resume model), I don't think it makes sense for the PhoneGap APIs to change in order to accommodate this. In this blog post I will show how the WP7 PhoneGap application host can be modified in order to support tombstoning.
But before we get there, I want to digress a while and look at using the MVVM pattern with JavaScript ...
Using the MVVM pattern in JavaScript
Handling tombstoning is much easier if you have a good separation between your view and your logic, with the MVVM pattern being a sensible choice for achieving this. When your application is tombstoned (and your application terminated), then re-activated, it is your responsibility to recreate the original state. Your view-model, is a model-of-a-view, so technically should provide all the information required to fulfil this requirement. See my previous blog post for details.
There are numerous JavaScript UI frameworks available (MVC, MVP, MVVM), however, because I feel tombstoning lends itself particularly well to the MVVM pattern, I decided to give KnockoutJS a try. When reading about this framework you will find references to WPF and Silverlight, it is clear that it has been heavily inspired by the Microsoft XAML frameworks.
The application I have built to demonstrate tombstoning is a very simple, single page twitter search application.
The Knockout view model is a JavaScript object where the properties are defined as 'observables'. These are JavaScript functions which provide change notification, much like CLR properties with INotifyPropergtyChanged
within Silverlight / WPF.
The View-Model
The view model for my twitter search application is shown below:
It comprises a few simple properties and a search function. Note, the items
property is an observableArray
, this is analogous to the WPF / Silverlight ObservableCollection
, which raises events when its contents are modified, allowing the UI to update automatically. The search
function queries the twitter APIs to find matching tweets, updating the observable items
array with the results.
The TwitterSearchViewModel
items
collection is populated with TweetViewModel
instances:
Note, here the properties are not observables, again much like WPF / Silverlight you can bind to a property that does not notify of changes if this is not required.
Also, the Knockout documentation typically defines view-models as literal objects. I prefer to use constructor functions, allowing the creation of multiple instances of the same view model.
The View
With Knockout the view is defined in HTML, you can create it directly, or via a template. I have created the following templates:
The data-bind
attribute is used by Knockout to set up the various bindings, connecting your view model properties and observables to the UI.It also defines functions to invoke when DOM events are raised, in much the same way as commands do in WPF / Silverlight.
The application code
Instantiating the view-model and the view is as simple as the following:
Originally I wanted to have the HTML for each view within a separate file, loading them via jQuery as described in this blog post. However, I just couldn't get this to work within the embedded WP7 browser.
The tweetView
tempate is used via the template
/ foreach
Knockout binding, mimicking the Silverlight / WPF ItemsControl.ItemTemplate
concept.
The application, after a bit of styling, looks like this:
Tombstoning
Now that the application has a decent structure, we can tackle the application-lifecycle. We need a way to store the view model state when the application transitions into a dormant state. Fortunately Knockout makes this very easy by supplying a toJSON
function, which can create a JSON representation of your view-model graph (minus the observables). I have added a getState
function to the TwitterSearchViewModel
as follows:
Now we need a way to invoke this function when the application pauses. PhoneGap provides a pause
lifecycle event, however, we need to store the output of this function in the WP7 PhoneApplicationService.Current.State
dictionary. Because this is a very much WP7 specific feature, I decided to do this outside of the PhoneGap lifecycle events.
My handler for the Deactivated
event simply invokes the above method, storing the state in the application state dictionary:
Note, to do this I have had to modify the current PhoneGap WP7 library to provide access to the underlying WebBrowser control.
The application now stores it state when it becomes dormant, the next step is to use this state when an application is activated from a tombstoned state. Within the Activated
handler we can read this state as follows:
Note, we check IsApplicationInstancePreserved
, if this is true, we do not need to use the state that was saved during deactivation, this allows for fast-application switching.
Unfortunately as this point our UI has not been created and our JavaScript application code is not running, which is why the tombstoned state is simply stored in a public property of our application. To pick this state up, we add a new step to our JavaScript view model creation code:
When the PhoneGap view is created, we add a handler to the ScriptNotify
event, allowing us to handle this getState
notification:
This checks for the presence of tombstone state, and if found, invokes setState back on our JavaScript view model:
Note, re-creating our view-model form JSON data is a little more involved than the opposite. I have also cheated a little here, rather than re-creating each TweetViewModel
I am using the JSON representation, because this view model has no public functions (i.e. commands).
Conclusions
With the above code the PhoneGap application now successfully handles all of the WP7 lifecycle states and transitions. There are a couple of things to note if you try to run this code yourself:
- You can force tombstoning via the Debug properties, "Tombstone upon deactiviation while debugging".
- I have fund that the WebBrowser on the emulator does not tombstone correctly, when your application resumes the WebBrowser control fails to execute any JavaScript! Fortunately on a real device it works just fine.
Now that I can tombstone a PhoneGap application, I feel that it is one step closer to be a viable solution for application development. The final thing that I still haven't quite worked out yet is navigation and back-button support. Fortunately Knockout has a lot to offer in this area as well, but more on that later ...
You can download the full sourcecode (including PhoneGap library mods) here: PhoneGapExample.zip
Regards, Colin E.