2. Managing Application State
Let's try the following
experiment: open the Tasks project if it's not already open within
Visual Studio 2010 Express for Windows Phone, and press F5 to run it. In
the text box field that comes up, type "Hello, world" (you can press
the Page Up key to allow you to type from the keyboard in the emulator).
Next, click the Launch Browser button, and then press the Back button
to return to your application.
Notice how the text box is blank
(the "Hello, world" text is gone) once you come back to the main
application screen. Now imagine if your application were a real-world
application capturing many different pieces of data and it provided a WebBrowserTask
to allow quick lookup of data on the Internet. It would certainly not
be acceptable to the end user to have to retype all the information once
the WebBrowserTask completes.
Hence, you must devise a mechanism to preserve such data when the
application is being tombstoned. Enterstate management.
If you have done any sort of
web development, the concept of state management will already be very
familiar to you. And if you haven't been exposed to state management
before, it's a fairly easy concept to grasp. Per Microsoft's
documentation, when an application like the Tasks application of the
previous example is tombstoned, it should save state information in case
it is reactivated. In the sections that follow, we will show you how to
save and retrieve state information within your application.
2.1. Managing State at the PhoneApplicationPage Level
The concept of state
management at the page level applies not only to those times when an
application is about to be tombstoned and the page needs to persist its
state for possible future retrieval. Many times, individual pages within
a Windows Phone 7 application must save their session data to allow
navigation to other pages within that same application; and if the user
comes back to the original page, it should be smart enough to retrieve
data previously entered by the user.
To accomplish session
persistence for both tombstoning and page navigation scenarios, each
page relies on the following three methods within the PhoneApplicationPage class:
OnNavigatedFrom(), which is called when the page is no longer an active page in a frame
OnNavigatedTo(), which is called when the page becomes the active page in a frame
OnBackKeyPress(), which is called when the hardware Back key is pressed
In the following code walkthrough, you will use each one of these methods, as well as the State
dictionary object, to persist data from the text box in the Tasks
application that you have built in this article. Follow these steps to
accomplish this task.
You will not be making any changes to the user interface of the Tasks application—it will stay very basic, as shown in Figure 10-2.
You will, however, add code to the Tasks application to persist the
information that has been entered inside the single text box in that
application.
Launch Visual Studio 2010 Express, and open the previously created Tasks project, if it's not already open.
Open MainPage.xaml.cs (one way is to right-click MainPage.xaml and select View Code). You will add code to save text entered in the text box on MainPage.xaml into the session objects if the page becomes no longer active in a frame—i.e., in that page's OnNavigatedFrom event. Add the following code to the MainPage.xaml.cs file:
protected override void
OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
Debug.WriteLine("Navigated From MainPage");
if (State.ContainsKey("TextboxText"))
State.Remove("TextboxText");
State.Add("TextboxText", textBox1.Text);
base.OnNavigatedFrom(e);
}
Notice the use of the State dictionary object—it is indeed very similar to the Session variable of ASP.NET web-based applications, and in the foregoing method we add the value from the text box into the State dictionary object.
Next, you will add code to retrieve values from the State dictionary object when the user navigates to the current page; you will do that inside the OnNavigatedTo method:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs
e)
{
Debug.WriteLine("Navigated To MainPage");
if (State.ContainsKey("TextboxText"))
{
string strTextboxText = State["TextboxText"] as string;
if (null != strTextboxText)
textBox1.Text = strTextboxText;
}
base.OnNavigatedTo(e);
}
The code you have written thus
far is sufficient to save the text from the text box for the duration of
the application session; even if an application is tombstoned, and the
user later returns to the currentx page, the text will be properly
preserved. Let's test this out really quickly: press F5 to run this
application, enter "Hello, world" in the text box, and press the Launch
Browser button. Once the browser comes up, press the Back button—you
should see "Hello, world" still displayed in the text box, where we did not handle any
state information at all.
While saving information for
the duration of the session is extremely important, there are many
occasions when you would like to save information permanently, so that
even if you turn your phone off (or the battery dies), you will still
have access to that information. Let's expand our walkthrough to
accommodate saving text into the isolated storage
on the Windows Phone 7, so that text is available for use as long as
the application's isolated storage is intact (and as long as we don't
remove this information from the isolated storage, of course). Follow
these steps to accomplish this task.
Add the following two using directives to the top of the MainPage.xaml.cs file:
using System.IO.IsolatedStorage;
using System.IO;
You
will add code to save text into the isolated storage area of your
application if the user presses the Back button. If you would like to
get more familiar with isolated storage. Add the following method to the MainPage.xaml.cs file:
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
base.OnBackKeyPress(e);
MessageBoxResult res = MessageBox.Show("Do you want to save your work before
leaving?", "You are exiting the application", MessageBoxButton.OKCancel);
if (res == MessageBoxResult.OK)
{
Debug.WriteLine("Ok");
SaveString(textBox1.Text, "TextboxText.dat");
}
else
{
Debug.WriteLine("Cancel");
}
}
Notice how the message box
is used to ask the user whether to save information to the file inside
isolated storage; if the user chooses Yes, the SaveString method is called, passing the value to save and the file to save it to.
Finally, you need to code the SaveString
method that performs all the heavy lifting within the isolated storage.
This method accepts the name of the file as one of the parameters, and
then it creates a file with that name within the isolated storage. After
the file is created, the method saves the data string passed to it
inside that file. While persisting string values inside the file is
perfectly acceptable for the small application that you are building in
this walkthrough, you might consider a different data structure for
bigger production applications with lots of data to persist. Serializing
data to XML would be a better alternative for such applications, as
would be saving data inside a dictionary or key-value collection
objects. Here is the full listing of that method; make sure it is also
present in your code.
private void SaveString(string strTextToSave, string fileName)
{
using (IsolatedStorageFile isf =
IsolatedStorageFile.GetUserStoreForApplication())
{
//If user choose to save, create a new file
using (IsolatedStorageFileStream fs = isf.CreateFile(fileName))
{
using (StreamWriter write = new StreamWriter(fs))
{
write.WriteLine(strTextToSave);
}
}
}
}
You are now ready to run your
application. Press F5 to launch it, type "Hello, world" in the text box
shown, and press the Back button. Remember, pressing the Back button
past the first page of an application results in termination of that
application. Click "Yes" on the message box prompting you to save your
work before leaving. Next, re-launch your application. You should see
"Hello, world" displayed in the text box—but you don't. What happened?
If you guessed that we still have to retrieve the values previously
stored inside the isolated storage, you are correct. We will retrieve
those values in the next section, together with looking at the best
practices for retrieving this information.
2.2. Retrieving Application Initial State
Microsoft guidelines state that within the Application_Launching
event there should not be any isolated storage access or web service
calls, so that the application comes up and is available for use as
quickly as possible. Instead, Microsoft recommends asynchronously
loading values from the isolated storage of an application once the
application is fully loaded. This set of restrictions forces us as
developers to code the initialization routines using the following two
guidelines:
Invoke data initialization and retrieval on a separate thread so as to maximize the responsiveness of an application.
Perform application initialization inside the OnNavigatedTo method of the PhoneApplicationPage class.
In the next walkthrough, you will add the necessary methods to properly load application data from the isolated storage.
You will continue modifying the Tasks project that you have worked with throughout this article. At the top of MainPage.xaml.cs, add the following using directive:
using System.Threading;
Open MainPage.xaml.cs and go to the OnNavigatedTo
method within that code. You will make adjustments to that method to
load data asynchronously (on a separate thread) from the isolated
storage, if there is no data in the State dictionary. Make the OnNavigatedTo method look like the following:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs
e)
{
Debug.WriteLine("Navigated To MainPage");
if (State.ContainsKey("TextboxText"))
{
string strTextboxText = State["TextboxText"] as string;
if (null != strTextboxText)
textBox1.Text = strTextboxText;
}
else
{
LoadAppStateDataAsync();
}
base.OnNavigatedTo(e);
}
The GetDataAsync
method is responsible for invoking a method that accesses isolated
storage data on a separate thread. The full method is shown here:
public void LoadAppStateDataAsync
{
Thread t = new Thread(new ThreadStart(LoadAppStateData));
t.Start();
}
Finally, the GetData method accesses isolated storage data looking for a particular file (hard-coded to be "TextboxText.dat" at the moment) and the settings within that file:
public void LoadAppStateData()
{
string strData = String.Empty;
//Try to load previously saved data from IsolatedStorage
using (IsolatedStorageFile isf =
IsolatedStorageFile.GetUserStoreForApplication())
{
//Check if file exits
if (isf.FileExists("TextboxText.dat"))
{
using (IsolatedStorageFileStream fs = isf.OpenFile("TextboxText.dat",
System.IO.FileMode.Open))
{
using (StreamReader reader = new StreamReader(fs))
{
strData = reader.ReadToEnd();
}
}
}
}
Dispatcher.BeginInvoke(() => { textBox1.Text = strData; });
}
Your
application is now complete, and it should handle both transient and
persistent states. To test it, press F5 and enter "Hello, world" in the
text box presented. Next, press the Back button and answer "Yes" to save
work before leaving. The application is now terminated; if you press F5
again to re-launch the application, the screen should come with "Hello,
world" already populated within it.
3. Best Practices for Managing the Application Life Cycle on the Windows Phone 7 OS
Microsoft provides an important
set of guidelines to follow to ensure a consistent and positive user
experience on a Windows Phone 7 platform. Some of the highlights of
those best practices are as follows.
Ensuring
that when the user launches a new instance of an application, it is
clear that it's a new instance (in other words, our last example of
automatically retrieving settings from the isolated storage may not be
ideal). At the same time, if an application is being reactivated, the
user should feel that the reactivated application has returned in its
previous state.
Since
the user may never return to the application once it becomestombstoned,
any data that needs to be saved to a persistent data store should be
saved with either the Closing or Deactivated event fires.
Invoking a launcher or a chooser will always deactivate an application and may
cause it to become tombstoned. An application may not be tombstoned if
it launches an experience that feels like it's a part of the original
application. Microsoft currently lists the following launchers and
choosers as not automatically tombstoning the calling application (it
clarifies, however, that an OS may still choose to tombstone the calling application if the resources are needed elsewhere):
You can take a look at the full
list of best practices for managing the application life cycle at http://msdn.microsoft.com/en-us/library/ff817009(v=VS.92).aspx.