Windows Phone : Handling the lifecycle at two levels

 

The sudden closure of the your software may trigger a series of issues, if you don’t handle these situations correctly. To make the user experience almost similar to what you would have if there would be a real multi threading it is important to handle the a couple of lifecycle event at two levels, the application level and the page level.

The application level includes the state of the entire application including everything that is not directly related to the visual aspect of the page. As an example, if you have a service running in a background thread, this may be have application-wide state that you need to save before tombstoning. For this purpose the runtime exposes four events that are raised during the phases of the tombstoning, during the startup and closing of the software.

// 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)
{
}


Into the App.xaml.cs file you can find the four handlers already hooked up to the events. If you go deep into the code and the XAML markup of this file it becomes evident the source of the events is the PhoneApplicationService instance that is present inside the LifetimeObjects section. We already encountered this service in the previous article when we had to save the state of the page during the navigation.

In these events you can save the state using the PhoneApplicationService.Current.State dictionary being aware that this dictionary is retaind in memory when the application is tombstoned and not when it is definitively closed. The correct way of handling state is the following:

1) in the deactivating event you have to save, into the transient “State” dictionary, the informations you know it can be lost if the application does not restart again. If you have some sensitive data you want to save also if the user completely restarts the application the best is to use a persistent storage like the IsolatedStorage.

2) when in the activating event you have to read back the informations and recreate the exact state you have saved. It is a good rule of thumb, that at the reprise the user will find exactly what he leaved during tombstoning.

3) in the closing event you have to manage to save the required information to a persistent storage. The deactivating and closing events are mutually exclusive, so you have to write an entirely separate process for the state management.

4) during the Launching event you must restore only the data you have saved in the closing event. This event means that the user has restarted the application from scratch so he expects to find it in its initial state. This event is also mutually exclusive with the activating event.

So if you imagine to have an application that pull data from a wcf service periodically we can have some stored data in an “Items” collection that we want to save across restarts and a “Current” property that only need to be saved during tombstoning; here is a sample code for the App.xaml.cs.

// 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)
{
    MyApplicationState.Instance.Items = this.LoadFromIsolatedStorage();
    MyApplicationState.Instance.Current = null;
}

// 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)
{
    MyApplicationState.Instance.Items = this.LoadFromIsolatedStorage();

    if (PhoneApplicationService.Current.State.ContainsKey("CurrentItem"))
    {
        MyApplicationState.Instance.Current = PhoneApplicationService.Current.State["CurrentItem"] as ItemData;
        PhoneApplicationService.Current.State.Remove("CurrentItem");
    }
    else
        MyApplicationState.Instance.Current = null;
}

// 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)
{
    this.SaveToIsolatedStorage(MyApplicationState.Instance.Items);
    PhoneApplicationService.Current.State["CurrentItem"] = MyApplicationState.Instance.Current;
}

// 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)
{
    this.SaveToIsolatedStorage(MyApplicationState.Instance.Items);
}

private List LoadFromIsolatedStorage()
{
    using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
    {
        if (file.FileExists("app.state"))
        {
            using (IsolatedStorageFileStream stream = file.OpenFile("app.state", System.IO.FileMode.Open))
                return this.DeserializeXml(stream);
        }

        return new List();
    }
}

private List DeserializeXml(IsolatedStorageFileStream stream)
{
    XmlSerializer serializer = new XmlSerializer(typeof(List));
    return serializer.Deserialize(stream) as List;
}

private void SaveToIsolatedStorage(List state)
{
    using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
    {
        using (IsolatedStorageFileStream stream = file.OpenFile("app.state", System.IO.FileMode.Create))
            this.SerializeXml(stream, state);
    }
}

private void SerializeXml(IsolatedStorageFileStream stream, List state)
{
    XmlSerializer serializer = new XmlSerializer(typeof(List));
    serializer.Serialize(stream, state);
}

Handling the state at the application level does not suffice. There are lot of details you should manage, during tombstoning, also at the page level. As an example you can imagine a form where the user have to input some data, the level of a game or the point where the user scrolled an image. All these detail do not refer directly to the application. They makes sense only when the tombstoning happens while the page is open. For these cases it is available a state bag, exposed by a property of the page. Unfortunately the activating and deactivating events cannot be handled at the page level. The problem comes from the fact that, during the reprise after tombstoning,  the activating event is raised long time before the page instance is recreated. So if you attach the activating event of the PhoneApplicationService, this is never raised because the code that effectively attache the event is executed after the event is raised.

To handle the tombstoning at the page level you have to use the navigation events to understand what is happening. Particularly the OnNavigatedTo event, that is raised as part of the tombstoning process uses an empty parameter in this case. Infact, the NavigationEventArgs.Content property, that usually indicated the target of the navigatione, is empty when the navigation is pointed to exit from the application scope. And this happen only during the tombstoning. Here is how to persist the state of a textbox this way:

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    if (this.State.ContainsKey("Message"))
    {
        this.message.Text = this.State["Message"] as string;
        this.State.Remove("Message");
    }

    base.OnNavigatedTo(e);
}

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    if (e.Content == null) // TOMBSTONING
    {
        State.Add("Message", message.Text);
    }

    base.OnNavigatedFrom(e);
}

The purpose of this code is to persist the content of the message textbox and read it again after the reprise.


Leave a Reply

Your email address will not be published. Required fields are marked *

Comment moderation is enabled. Your comment may take some time to appear.