Dela via


Part 2: Manage app lifecycle and state (Windows Store apps using C#/VB and XAML)

[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]

In Windows 8, you can launch a bunch of apps and switch between them without having to worry about slowing down the system or running the battery down. That's because the system automatically suspends (and sometimes terminates) apps that are running in the background for you. A well-designed app can be suspended, terminated, and relaunched by the system and seem as though it were running the entire time.

Important  This tutorial is intended for use with Microsoft Visual Studio 2013 and Windows 8.1. Parts of it will not work correctly with Microsoft Visual Studio 2012 and Windows 8.

 

In this tutorial, you learn how to:

  • Save state using different types of storage
  • Restore your app's state the next time the app is launched

Before you start...

About the app's lifecycle

Before you go back to the code, let's talk a bit about the app's lifecycle. Activation marks the beginning of the app's lifecycle. At any given point, an app is not running, running, or suspended.

An app can be suspended when the user switches away from it or when Windows enters a low power state. While your app is suspended, it continues to reside in memory so that users can quickly and reliably switch between suspended apps, resuming them. When your app is suspended and then resumed, you don't have to write any extra code to make it look as though it had been running the entire time.

But Windows can also terminate a suspended app at any time to free up memory for other apps or to save power. When your app is terminated, it stops running and is unloaded from memory.

When the user closes an app by pressing Alt+F4 or using the close gesture, the app is suspended for 10 seconds and then terminated.

Windows notifies your app when it suspends it, but doesn't provide additional notification when it terminates the app. That means your app must handle the suspended event and use it to save its state and release its exclusive resources and file handles immediately.

To create a good user experience, you want your app to look like it never stopped running. The app needs to retain any data the user entered, settings they changed, and so on. That means you need to save your app's state when it's suspended, in case Windows terminates it, so that you can restore its state later.

There are two types of data for you to manage in your app: app data and session data.

In the next steps, you learn how to update the app to save these types of data. What state do you need to save? Right now, the only thing the user can change is their name entry. The user can also click the Say "Hello" button to generate a personalized greeting.

Step 1: Using SuspensionManager

In Part 1: Create a "Hello, world" app, you replaced the default MainPage file with one based on the Basic Page template. When you used the Basic Page template, Microsoft Visual Studio added several files to the project in the Common folder. One of these files contains the SuspensionManager class. SuspensionManager is a helper class that simplifies lifecycle management for the app. It does several things for you. It saves and restores the navigation state of the Frame that hosts the app pages. In a single-page app, saving the navigation state doesn't seem very important, but it becomes very important when you add multiple pages to an app. It also gives each page the opportunity to save and restore its state. SuspensionManager serializes the page state data and writes it to an XML file in your app's local storage.

To use the SuspensionManager class in the app, you first need to register the main app Frame. When you are done, SuspensionManager knows about each page in the app, and it can save and restore the navigation state. You register the Frame immediately after it's created in the OnLaunched method in App.xaml.cs/vb.

To use SuspensionManager

  1. Double-click App.xaml.cs/vb in Solution Explorer to open it.

  2. In the OnLaunched method, call the SuspensionManager.RegisterFrame method to register the root Frame.

    HelloWorld.Common.SuspensionManager.RegisterFrame(rootFrame, "appFrame");
    
    HelloWorld.Common.SuspensionManager.RegisterFrame(rootFrame, "appFrame")
    

Add this code after the new Frame is created, as shown here.

            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate to the first page
                rootFrame = new Frame();
                HelloWorld.Common.SuspensionManager.RegisterFrame(rootFrame, "appFrame");
            ...
        If rootFrame Is Nothing Then
            ' Create a Frame to act as the navigation context and navigate to the first page
            rootFrame = New Frame()
            HelloWorld.Common.SuspensionManager.RegisterFrame(rootFrame, "appFrame")
        ...

Step 2: Save the app's state

Generally, there are two types of data for you to manage in your app: app data and session data. App data is persistent across sessions and must always be accessible to the user. In your app, the Text of the nameInputTextBox is app data. Always save important app data incrementally throughout the life of your app. Because your app has only up to five seconds to run code in the suspending event handler, you need to ensure that your important app data is saved to persistent storage by the time it's suspended.

Windows provides a Windows.Storage.ApplicationData object to help you manage your app data. This object has a RoamingSettings property that returns an ApplicationDataContainer. You can use this ApplicationDataContainer to store app data that persists across sessions. Let's store the user's name in the roaming ApplicationDataContainer as the user types it in.

Note  This tutorial shows how to use RoamingSettings. The roaming settings app data container makes it easy to store data in a way that is accessible to the user across multiple machines. Basically, the data is uploaded to the cloud in the background for you. You can also use the local settings app data container (LocalSettings), but you should only use it when you want to store machine-specific info.

 

Save persistent app data as often as it makes sense in your application. Here, you handle the TextBox.TextChanged event and save the user name as the user enters it.

To save app data

  1. Double-click MainPage.xaml in Solution Explorer to open it.

  2. In Extensible Application Markup Language (XAML) or design view, select the nameInputTextBox that you added to MainPage.xaml.

  3. In the Properties panel, click the Events button ().

  4. Find the TextChanged event in the event list. In the text box for the event, type "NameInput_TextChanged" as the name of the function that handles the TextChanged event.

  5. Press Enter. The event handler method is created and opened in the code editor so you can add code that's executed when the event occurs.

  6. Add this code to the event handler in the code behind page. In the event handler, you save the nameInput text in roamingSettings.

                Windows.Storage.ApplicationDataContainer roamingSettings = 
                    Windows.Storage.ApplicationData.Current.RoamingSettings;
                roamingSettings.Values["userName"] = nameInput.Text;
    
            Dim roamingSettings = Windows.Storage.ApplicationData.Current.RoamingSettings
            roamingSettings.Values("userName") = nameInput.Text
    
  7. Press F5 to build and run the app. Your name is saved as you enter it into the text box.

Session data is temporary data that is relevant to the user’s current session in your app. A session ends when the user closes the app using the close gesture or Alt + F4, reboots the computer, or logs off the computer. In your app, the Text of the greetingOutputTextBlock is session data. You restore it only if Windows suspends and terminates the app. You need to save the navigation state of the app Frame, so the app can be restored to the same page it was on, and so the SuspensionManager knows which page to restore the state of. You also need to save the state of the page itself. This is where you save the greetingOutput text. You use the SuspensionManager class to save session state in the Application.Suspending event handler.

(For more info about the other ways you can save state, see Managing app data and Working with state efficiently.)

The App.xaml.cs/vb file contains a handler for the Application.Suspending event. This event handler gets called when Windows is about to suspend your app. This is your opportunity to save the state of the app in case it gets terminated. You use the SuspensionManager class to simplify saving session state. It saves the navigation state of the app, and gives you the opportunity to save the session state of the active page.

To save the session state

  1. Double-click App.xaml.cs/vb in Solution Explorer to open it.

  2. In App.xaml.cs/vb, add the async keyword to the OnSuspending method signature.

    For more info about asynchronous programming, see Quickstart: Calling asynchronous APIs in C# or Visual Basic.

  3. In the OnSuspending method, call the SuspensionManager.SaveAsync method.

                await HelloWorld.Common.SuspensionManager.SaveAsync();
    
            Await HelloWorld.Common.SuspensionManager.SaveAsync()
    

    Calling SaveAsync saves the navigation state of the Frame and then gives your Page the opportunity to save its content.

    Here's the full OnSuspending method with the updates made.

            /// <summary>
            /// Invoked when application execution is being suspended.  Application state is saved
            /// without knowing whether the application will be terminated or resumed with the contents
            /// of memory still intact.
            /// </summary>
            /// <param name="sender">The source of the suspend request.</param>
            /// <param name="e">Details about the suspend request.</param>
    
            private async void OnSuspending(object sender, SuspendingEventArgs e)
            {
                var deferral = e.SuspendingOperation.GetDeferral();
                //TODO: Save application state and stop any background activity
                await HelloWorld.Common.SuspensionManager.SaveAsync();
                deferral.Complete();
            }
    
        ''' <summary>
        ''' Invoked when application execution is being suspended.  Application state is saved
        ''' without knowing whether the application will be terminated or resumed with the contents
        ''' of memory still intact.
        ''' </summary>
        ''' <param name="sender">The source of the suspend request.</param>
        ''' <param name="e">Details about the suspend request.</param>
    
        Private Async Sub OnSuspending(sender As Object, e As SuspendingEventArgs) Handles Me.Suspending
            Dim deferral = e.SuspendingOperation.GetDeferral()
            ' TODO: Save application state and stop any background activity
            Await HelloWorld.Common.SuspensionManager.SaveAsync()
            deferral.Complete()
        End Sub
    
  4. Double-click MainPage.xaml.cs/vb in Solution Explorer to open it.

  5. In MainPage.xaml.cs/vb, add this code to the navigationHelper_SaveState method to save the page state.

                e.PageState["greetingOutputText"] = greetingOutput.Text;
    
                e.PageState("greetingOutputText") = greetingOutput.Text
    

    The SuspensionManager class serializes and saves the PageState dictionary to an XML file. Data saved in PageState is saved only for this session. You save the greetingOutput text here.

    Here's the full code of the navigationHelper_SaveState method.

    private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
    {
        e.PageState["greetingOutputText"] = greetingOutput.Text;
    }
    
    Private Sub NavigationHelper_SaveState(sender As Object, e As Common.SaveStateEventArgs)
        e.PageState("greetingOutputText") = greetingOutput.Text
    End Sub
    
  6. Click Build > Build solution to make sure the app builds with no errors.

That's all you need to do to save your app's state before your app is terminated. Now you need to learn how to restore your app's state the next time the user launches the app.

Step 3: Restore the app's state

Earlier, you saw that the App.xaml.cs/vb file contains code that handles your app's activation. There are many different ways to activate an app. Here we look at the Launch activation and the OnLaunched method.

An app is launched whenever it wasn't running and then the user activates it. When an app is launched, Windows displays a splash screen for the app.

Let's take a look at the code in App.xaml.cs/vb that handles app activation. The code defines an override of the OnLaunched method. The code in this method is executed only if the activation is a Launch activation. (You can override other methods to handle other kinds of activation, but we don't do it here. For more info, see Application lifecycle.)

First, the code declares a Frame and tries to assign the current window contents to it.

Frame rootFrame = Window.Current.Content as Frame;
Dim rootFrame As Frame = TryCast(Window.Current.Content, Frame)

If the window already contains a Frame, that indicates that the app is already initialized, so initializing the Frame is skipped.

If the app isn't initialized, the code creates a Frame to host the app pages. You register the Frame with the SuspensionManager. This is the code you added in Step 1.

Next, the code checks the previous execution state to see how the app was last shut down. When the previous execution state is Terminated it means that the last time the app ran, Windows successfully suspended the app and then terminated it. In that case, you need to restore the app's state.

            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate to the first page
                rootFrame = new Frame();
                HelloWorld.Common.SuspensionManager.RegisterFrame(rootFrame, "appFrame");

                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    //TODO: Load state from previously suspended application
                }

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }
        ' Do not repeat app initialization when the Window already has content,
        ' just ensure that the window is active

        If rootFrame Is Nothing Then
            ' Create a Frame to act as the navigation context and navigate to the first page
            rootFrame = New Frame()
            HelloWorld.Common.SuspensionManager.RegisterFrame(rootFrame, "appFrame")

            If e.PreviousExecutionState = ApplicationExecutionState.Terminated Then
                ' TODO: Load state from previously suspended application
            End If
            ' Place the frame in the current Window
            Window.Current.Content = rootFrame
        End If

Next, the code checks if the Frame contains any content. If the app is already running, or the navigation state was restored, the Frame already has content. Otherwise, the Frame navigates to the first page in the app. In this case, it navigates to MainPage.

            if (rootFrame.Content == null)
            {
                // When the navigation stack isn't restored navigate to the first page,
                // configuring the new page by passing required information as a navigation
                // parameter
                if (!rootFrame.Navigate(typeof(MainPage), e.Arguments))
                {
                    throw new Exception("Failed to create initial page");
                }
            }
        If rootFrame.Content Is Nothing Then
            ' When the navigation stack isn't restored navigate to the first page,
            ' configuring the new page by passing required information as a navigation
            ' parameter
            If Not rootFrame.Navigate(GetType(MainPage), e.Arguments) Then
                Throw New Exception("Failed to create initial page")
            End If
        End If

Finally, the code activates the window.

            // Ensure the current window is active
            Window.Current.Activate();
        ' Ensure the current window is active
        Window.Current.Activate()

Now that you know what happens when the app is launched, let's look at how to restore the app state.

To restore the app's state

  1. In App.xaml.cs/vb, add the async keyword to the OnLaunched method signature.

    protected async override void OnLaunched(LaunchActivatedEventArgs e)
    
    Protected Overrides Async  Sub _
        OnLaunched(e As Windows.ApplicationModel.Activation.LaunchActivatedEventArgs))
    
  2. If the app was terminated, call the SuspensionManager.RestoreAsync method.

    Calling RestoreAsync restores the navigation state of the Frame and then gives the Page the opportunity to restore its content.

                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    //TODO: Load state from previously suspended application
                    await HelloWorld.Common.SuspensionManager.RestoreAsync();
                }
    
            If e.PreviousExecutionState = ApplicationExecutionState.Terminated Then
                ' TODO: Load state from previously suspended application
                Await HelloWorld.Common.SuspensionManager.RestoreAsync()
            End If
    
  3. In MainPage.xaml.cs/vb, add code to the navigationHelper_LoadState method to restore the page state.

    1. First, check to see if the PageState dictionary exists and has a key named greetingOutputText. If the key exists, use it to restore the greetingOutput text.

                  // Restore values stored in session state.
                  if (e.PageState != null && e.PageState.ContainsKey("greetingOutputText"))
                  {
                      greetingOutput.Text = e.PageState["greetingOutputText"].ToString();
                  }
      
              ' Restore values stored in session state.
              If e.PageState IsNot Nothing AndAlso e.PageState.ContainsKey("greetingOutputText") Then
                  greetingOutput.Text = e.PageState("greetingOutputText").ToString()
              End If
      
    2. Next, load the user name. Because you want the user name data to persist over multiple sessions, you store it in the RoamingSettings app data container. Let's add some code to see whether the user name exists and, if it does, display it.

                  // Restore values stored in app data.
                  Windows.Storage.ApplicationDataContainer roamingSettings = 
                      Windows.Storage.ApplicationData.Current.RoamingSettings;
                  if (roamingSettings.Values.ContainsKey("userName"))
                  {
                      nameInput.Text = roamingSettings.Values["userName"].ToString();
                  }
      
              ' Restore values stored in app data.
              Dim roamingSettings =
                  Windows.Storage.ApplicationData.Current.RoamingSettings
      
              If roamingSettings.Values.ContainsKey("userName") Then
                  nameInput.Text = roamingSettings.Values("userName").ToString()
              End If
      

    Here's the complete code for the navigationHelper_LoadState method.

            /// <summary>
            /// Populates the page with content passed during navigation.  Any saved state is also
            /// provided when recreating a page from a prior session.
            /// </summary>
            /// <param name="navigationParameter">The parameter value passed to
            /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
            /// </param>
            /// <param name="pageState">A dictionary of state preserved by this page during an earlier
            /// session.  This will be null the first time a page is visited.</param>
            private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
            {
                // Restore values stored in session state.
                if (e.PageState != null && e.PageState.ContainsKey("greetingOutputText"))
                {
                    greetingOutput.Text = e.PageState["greetingOutputText"].ToString();
                }
    
    
                // Restore values stored in app data.
                Windows.Storage.ApplicationDataContainer roamingSettings =
                    Windows.Storage.ApplicationData.Current.RoamingSettings;
                if (roamingSettings.Values.ContainsKey("userName"))
                {
                    nameInput.Text = roamingSettings.Values["userName"].ToString();
                }
            }
    
        ''' <summary>
        ''' Populates the page with content passed during navigation.  Any saved state is also
        ''' provided when recreating a page from a prior session.
        ''' </summary>
        ''' <param name="sender">
        ''' The source of the event; typically <see cref="NavigationHelper"/>
        ''' </param>
        ''' <param name="e">Event data that provides both the navigation parameter passed to
        ''' <see cref="Frame.Navigate"/> when this page was initially requested and
        ''' a dictionary of state preserved by this page during an earlier
        ''' session.  The state will be null the first time a page is visited.</param>
        Private Sub NavigationHelper_LoadState(sender As Object, e As Common.LoadStateEventArgs)
            ' Restore values stored in session state.
            If e.PageState IsNot Nothing AndAlso e.PageState.ContainsKey("greetingOutputText") Then
                greetingOutput.Text = e.PageState("greetingOutputText").ToString()
            End If
    
            ' Restore values stored in app data.
            Dim roamingSettings =
                Windows.Storage.ApplicationData.Current.RoamingSettings
    
            If roamingSettings.Values.ContainsKey("userName") Then
                nameInput.Text = roamingSettings.Values("userName").ToString()
            End If
        End Sub
    

Now you can build and run the app, and see how the session state is saved and restored. So far, you've tested your app by running it in debug mode, and stopped it by selecting Debug > Stop Debugging in Visual Studio. But doing this causes the app to perform a normal shutdown, and the Suspending event doesn't occur. Fortunately, Visual Studio lets you simulate suspending, terminating, and restoring an app.

To simulate suspending, terminating, and restoring an app in Visual Studio

  1. Press F5 to run the app in debug mode.

  2. Enter your name in the input box and click "Say "Hello"". The greeting is displayed.

  3. Press Alt+Tab to return to Visual Studio.

  4. Open the drop down menu next to the Suspend button on the Debug Location toolbar.

    The Debug Location toolbar appears by default while the debugger is running. If you don't see it, click View > Toolbars > Debug Location to show it.

  5. Select Suspend and shutdown.

    Visual Studio simulates suspending and terminating your app, so the Suspending event occurs and your state management code is executed.

  6. Press F5 to run the app again. The app is restored to its previous state.

  7. Change the name in the text box, and click "Say "Hello"".

  8. Press Alt+Tab to return to Visual Studio.

  9. Close the app by selecting Debug > Stop Debugging.

  10. Press F5 to run the app again.

    Now, the user name is restored because it was saved as you typed it in. The greeting is not restored because the Suspending event didn't occur, so the session state wasn't saved.

Summary

Congratulations, you're done with the second tutorial! You learned how to save app data and session state in a Windows Store app.

See the code

Did you get stuck, or do you want to check your work? If so, see Part 2 complete code.

Next steps

In the next part of this tutorial series, you learn how to use the design tools in Visual Studio to create a more complex UI. Go to Part 3: Navigation, layout, and views.