WEBINAR:
On-Demand
Desktop-as-a-Service Designed for Any Cloud ? Nutanix Frame
Building an example
To show how to manage the application lifecycle and application settings we'll build an example that backs-up and restores both page state and application state. This Silverlight application will have one single page that displays three counters:
- total starts of the applications (persistent application state)
- total re-activations of the application from tombstoning after the last fresh start-up (transient application state)
- total hits of a button on the main page (transient page state)
This is how the XAML code for the content panel should look like:
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel Grid.Row="0">
<TextBlock Text="Total application runs"
HorizontalAlignment="Center" TextAlignment="Center"
Style="{StaticResource PhoneTextTitle2Style}"/>
<TextBlock x:Name="totalAppRuns"
Text="0"
HorizontalAlignment="Center"
Style="{StaticResource PhoneTextTitle1Style}" />
<TextBlock Text="Total application activations since last start"
TextWrapping="Wrap"
HorizontalAlignment="Center" TextAlignment="Center"
Style="{StaticResource PhoneTextTitle2Style}"/>
<TextBlock x:Name="totalAppActivations"
Text="0"
HorizontalAlignment="Center"
Style="{StaticResource PhoneTextTitle1Style}" />
<TextBlock Text="Total button hits since the last start"
TextWrapping="Wrap"
HorizontalAlignment="Center" TextAlignment="Center"
Style="{StaticResource PhoneTextTitle2Style}"/>
<TextBlock x:Name="totalButtonHits"
Text="0"
HorizontalAlignment="Center"
Style="{StaticResource PhoneTextTitle1Style}" />
<Button x:Name="btnHit"
Content="Hit me"
Click="btnHit_Click"/>
</StackPanel>
</Grid>
And this is how the page looks in the designer:
[wp7tombstoning_3.png]
Preserving page state
When pressing the button labeled "Hit me" an internal page counter is incremented and the value is displayed on the page. However, when the application is tombstoned we want to save that counter and restore it back when the application is reactivated and the page is navigated to again. For that we will override the OnNavigatedFrom handler to store the hit count in the PhoneApplicationService.Current.State dictionary, and the OnNavigatedTo handler to retrieve the hit count from the transient dictionary and display it in the page. The code for the main page looks like this:
public partial class MainPage : PhoneApplicationPage
{
int totalHits = 0;
public MainPage()
{
InitializeComponent();
}
private void btnHit_Click(object sender, RoutedEventArgs e)
{
totalHits++;
totalButtonHits.Text = totalHits.ToString();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
object obj;
if (PhoneApplicationService.Current.State.TryGetValue("totalHits", out obj))
{
totalHits = (int)obj;
totalButtonHits.Text = totalHits.ToString();
}
base.OnNavigatedTo(e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
PhoneApplicationService.Current.State["totalHits"] = totalHits;
base.OnNavigatedFrom(e);
}
}
Preserving application state
If you look in the App.xaml page you'll notice the following event handlers for the events of the PhoneApplicationService class.
<Application.ApplicationLifetimeObjects>
<!--Required object that handles lifetime events for the application-->
<shell:PhoneApplicationService
Launching="Application_Launching" Closing="Application_Closing"
Activated="Application_Activated" Deactivated="Application_Deactivated"/>
</Application.ApplicationLifetimeObjects>
They are implemented as empty methods in the App class, with comments indicating when the handlers are called.
// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
}
// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
}
// Code to execute when the application is deactivated (sent to background)
// This code will not execute when the application is closing
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
}
// Code to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
}
Because you can't be sure that a tombstoned application will actually be re-activated, when the application is deactivating you should store both transient and persistent data and reload it appropriately when activating. You should adhere to the following pattern for saving and loading application data:
- Application_Launching: load only the persistent data (required between different runs)
- Application_Closing: save only the persistent data
- Application_Activated: load both persistent data and transient data
- Application_Deactivated: save both persistent data and transient data
Let's consider that we store the count of total runs and total activations since the last run in two public (read only) properties of the App class (which makes them available to all the pages of the application).
public int TotalRuns { get; private set; }
public int TotalActivations { get; private set; }
We'll have two methods for saving and two for loading application data, one pair for the persistent data and one for the transient data. The persistent data is the total number of runs of the application, and the transient data is the total number of activations since the last run. These four methods will look like this (they should be self-explanatory by now):
private void SaveTransientData()
{
PhoneApplicationService.Current.State["totalActivations"] = TotalActivations;
}
private void LoadTransientData()
{
object obj;
if (PhoneApplicationService.Current.State.TryGetValue("totalActivations", out obj))
{
TotalActivations = (int)obj;
}
}
private void SavePersistantData()
{
if (IsolatedStorageSettings.ApplicationSettings.Contains("totalRuns"))
{
IsolatedStorageSettings.ApplicationSettings["totalRuns"] = TotalRuns;
}
else
{
IsolatedStorageSettings.ApplicationSettings.Add("totalRuns", TotalRuns);
}
// make sure data is saved immediatelly
IsolatedStorageSettings.ApplicationSettings.Save();
}
private void LoadPersistantData()
{
if (IsolatedStorageSettings.ApplicationSettings.Contains("totalRuns"))
{
TotalRuns = (int)IsolatedStorageSettings.ApplicationSettings["totalRuns"];
}
}
Following the pattern described earlier will call them from the proper event handlers:
// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
LoadPersistantData();
TotalRuns++;
}
// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
LoadPersistantData();
LoadTransientData();
TotalActivations++;
}
// Code to execute when the application is deactivated (sent to background)
// This code will not execute when the application is closing
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
SavePersistantData();
SaveTransientData();
}
// Code to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
SavePersistantData();
}
Notice that the total runs counter is only incremented in the handler for the Launching event after loading the persisting data, and the total activations counter is only incremented in the handler for the Activated event after loading the transient data.
In order to display these counters in the main page, we'll need the following addition to the OnNavigatedTo method of the main page (the only page of the application):
// application settings
App app = Application.Current as App;
totalAppActivations.Text = app.TotalActivations.ToString();
totalAppRuns.Text = app.TotalRuns.ToString();
Examples
The first set of images below shows:
- The counters after starting the application the first time: runs=1, activations=0, button hits=0
- The counters after pressing the button three times: runs=1, activations=0, button hits=3
- The counters after pressing the Start button to tombstone the application and then the Back button to return to it and activated it: runs=1, activations=1, button hits=3
[wp7tombstoning_4.png]
The second set of images below shows:
- The counters after pressing the button two more times: runs=1, activations=1, button hits=5
- The counters after pressing the Start button to tombstone the application and then the Back button to return to it and activated it: runs=1, activations=2, button hits=3
- The counters after pressing the Back button to close the application and then launching the application again: runs=2, activations=0, button hits=0
[wp7tombstoning_5.png]
Conclusion
WP7 applications can be in different states, they can be deactivated and reactivated by the system as user navigates to and away the application or launchers/choosers activate. The application data (whether at page level or application level) should be backed-up and restored. There are different dictionaries the framework offers for this purpose but developers can employ other means too. This article discussed the application lifecycle, the tombstoning and data persistence.