The Windows Phone 7 Application Lifecycle

In the April issue you saw how easily you can create a Windows Phone 7 application using Visual Studio 2010 and Expression Blend 4 ("Intro to Windows Phone 7," Language Lab). However, there's more to building a Windows Phone 7 application than first meets the eye. In this column, I'll take a closer look at the whole lifecycle of an application and show you some of the tricks you'll need to build a great user experience.

Before I jump into the lifecycle model, let's do a quick recap of the navigation model for a Windows Phone 7 application. When your application is launched the initial page is displayed. If you use the application template that comes with the Windows Phone 7 Developer Tools, this will be called MainPage.xaml. From this page, which is the first -- or home -- page of the application, if the user presses the hardware Back button the application will terminate, returning the user to the Start screen. To navigate to another page within your application you call the Navigate method on the NavigationService instance on the current page:

From the second page, if the user presses the Back button, instead of exiting the application -- which is what happens when they're on the first page of the application -- they're returned to the previous page within the application. Similarly, if they launch a third page, pressing the Back button on this page will return them to the second page, then to the first page, and then if they press the Back button again it will terminate the application.

You'll often hear Windows Phone 7 developers referring to the back stack. They're typically either referring to the list of applications that a user has progressively opened, or the pages that the user has opened within an application. In fact, the back stack is a combination of both and is designed in such a way as to give the user a consistent experience when they press the Back button.

Take the scenario where the user starts with Page1_1 of Application1 and navigates to Page1_2, presses the Start key to display the Start screen, then launches Application2 (which starts on Page2_1) and navigates to Page2_2. When the user repeatedly presses the back button they will see Page2_1, followed by the Start screen, followed by Page1_2, followed by Page1_1, followed by the Start screen. As you can see, the back stack exists both within the applications, keeping track of which pages the user has traversed, and between applications.

Now, it might appear that the user has opened multiple applications, which is correct. However, they're not all active at the same time. In fact, only one application is executing while the other applications are suspended in the background. So what happens when an application goes into the background? This is not an easy problem to solve, as it's not as simple as just taking a memory dump of the running application and restoring it when the user clicks the Back button. Doing this could lead to all manner of timing and functional breaks. Instead, the model that Microsoft has adopted allows state to be persisted prior to the application being completely unloaded from memory. When the user returns to the application, it's restarted and the state information is restored.

Windows Phone 7 Application LifecycleFigure 1 illustrates the various states of a Windows Phone 7 application, along with events that make up the lifecycle model.

[Click on image for larger view.]

Figure 1.The various states of a Windows Phone 7 application and the events that make up the lifecycle model.

An application begins in the Not Running state. This is where the application appears in either -- or both -- the Start screen and the applications list. But the application at this time is not in the back stack, as it hasn't been previously run. When the user launches the application, the Launching event is raised on the Application object. By default an event handler for this, and other Application events, can be found in the App.xaml.cs file. The application is then in the Running state. There are some cases where part of the screen can be obscured -- for example, by the low-battery warning. In these cases an Obscured event is raised so that the application can pause anything that requires user interaction. Conversely, the Unobscured event is raised when the screen is unobscured, allowing the application to resume.

From the Running state, if the user taps the Start button, as if they were about to launch another application, a Deactivated event is raised on the Application object and the app moves to the Eligible for Termination state. In this state the app is effectively suspended but is still memory resident. At this point if the user changes their mind and clicks the Back button, the application will resume immediately, raising the Activated event on the Application object.

An alternative would be for the user to tap the Start button, followed by selecting another application to open. In this scenario the application transitions to the Eligible for Termination state (when the user taps the Start button) and is then terminated. Note that there's no way for you to intercept this transition, to either cancel the operation or persist state. This is by design and ensures that all state is persisted by the end of the Deactivated event, and that all state is resumed after the Activated event.

When an application is terminated this way it actually moves into a Tombstoned state. You can imagine an RIP tombstone for your application on which is engraved the current state, including the page back stack, of the application when it was terminated. If the user decides to return to the application by pressing the Back button, the application will be resurrected. This process includes launching the application, raising the Activated event on the Application object and navigating directly to the page that was at the top of the back stack.

Note that, regardless of which path is taken back to the Running state, the Activated event is raised. However, there are two scenarios that are not covered by this diagram. The first is when the application is in either the Eligible for Termination or the Tombstoned states, and the user taps on the application again from either the Start screen or the applications list. In this case the application will be removed from the back stack, any state information will be discarded and the application returned to the Not Running state. From there the application will launch as usual, raising the launching event.

The second scenario relates to the length of the inter-application back stack. Only five applications will ever exist in the back stack. When the user opens the sixth application, the first application will be removed from the back stack and sent back to the Not Running state.

Transient StateTombstoning, while good in theory, has some challenges associated with it. When an application is tombstoned, the only information about the state of the application that's persisted by default is the back stack -- in other words, what page the user is currently on and which pages they traversed to get to that page. Data that the user may have typed, entered or changed on any of those pages won't be persisted.

Take the example of a page with a number of TextBox controls and a ListBox full of items. When the user taps the Start button, opens another application and then at some time returns to the original application, they'll expect that the contents of the TextBox controls will be the same, and the scroll position and selected item of the ListBox will be preserved. This is what is known as transient state and needs to be persisted by you when the application is deactivated, and restored when the application is activated. Other information, such as a document that a user has saved, is referred to as persistent data or state, because it's information that should survive multiple application sessions.

From the application lifecycle diagram in Figure 1, you know that there are four events on the Application object that correspond to the state transitions within the application:

Of these, the ones that are important for handling tombstoning are the Deactivated and Activated events. When an application is sent into the background (when the user taps the Start button), the last opportunity for you to persist any transient state is in the Deactivated event. For example, the code in Listing 1 would persist a property called ActiveSessionDuration on the Application object.

In this case the ActiveSessionDuration maintains how long the user has actually been using the application, rather than the length of time from when the application was launched to when it was closed. Of course, you'll only see the output if the application is closed via the Back button. You won't see it if the application is tombstoned and never activated again.

Page StateThe Deactivated and Activated events on the Application object are useful for persisting application-wide transient data. However, it's difficult to use these events to persist information pertaining to pages that are in the back stack. Luckily there's a much better way to handle tombstoning on a page-by-page basis.

Each page within your application will inherit from the PhoneApplicationPage class. This class has a property, State, which is a dictionary of objects with keys that are strings. At any time during the lifetime of the page you can read and write to this dictionary. However, in most cases, you'll want to write to this dictionary in the OnNavigatedFrom method and read from this dictionary in the OnNavigatedTo method.

When the user navigates into a page, regardless of whether it's from a previous page in the application, returning to the page from elsewhere in the application or returning to the page after closing a different application, the OnNavigatedTo method is invoked on the page. Conversely, when the user navigates away from a page, regardless of whether they're navigating to another page in the application, going back to a previous page within the application or navigating to another application, the OnNavigatedFrom method is invoked.

Let's look at these methods a little closer. Figure 2 provides a basic illustration to help you understand the sequence of navigation events. The left side represents the user navigating to this page from a previous page within the application (1) and pressing the back key to return to the previous page within the application (4). The right side represents the user navigating to either another application or another page within the application (2) and then returning to the application from the other application, or other page within the application (3).

[Click on image for larger view.]

Figure 2.Page state and the OnNavigatedFrom and OnNavigatedTo methods.

In this case, the page in question has a single TextBox called MessageText. When the user navigates away from the page, in the OnNavigatedFrom method, the current Text property of MessageText is added to the State dictionary using the key "Message":

Let's see how this would play out. When the user navigates to the page for the first time (1) the page is created and the State dictionary is initialized. In this case, when the OnNavigatedTo method is invoked, there's nothing in the State dictionary so the Text property is not set. This is expected, because the user would expect to see the default value of the Text.

If the user navigates away from the page to either another application or to another page in the application (2), the OnNavigatedFrom method is invoked and the current Text property is persisted. When the user later returns (3), the OnNavigatedTo method is invoked. This time the State dictionary has a value for the "Message" key, so the Text property of the MessageText is set. This returns the state of the TextBox to how the user left it -- again, expected behavior.

Finally, when the user then presses the Back button to return to the previous page, the OnNavigatedFrom method is again invoked and the current Text value is persisted in the State dictionary. However, this time, because the page is then closed, it and its corresponding State dictionary are subsequently disposed of.

This model treats each page independently, allowing you to easily persist and restore transient state as part of the navigation events. There are still some edge cases where you'll need to use the Deactivated and Activated events, but in most cases using the page State dictionary eliminates any tombstoning issues you may come across. This will lead to a consistent user experience across your whole application.