Windows/Phone 8.1–Frame, Page, NavigationHelper, SuspensionManager

I’m more than prepared to admit that I find the way in which navigation and state management are done in a Windows/Phone XAML application to be “a bit confusing” and so I thought I’d write down some thoughts here both in the interests of reminding myself in the future and maybe helping out somebody else who’s looking at the same thing.

I have to keep reminding myself because I wouldn’t necessarily write an app using these mechanisms myself but they do ship in the box with Visual Studio so, frequently, I find myself talking to other people about them or looking at code that uses them.

The first thing to say is that when you’re looking at an app that navigates with Windows/Phone 8.1 it’s a bit hard to know where to start because you have to choose between;

image

and the blank application template doesn’t include classes like NavigationHelper, SuspensionManager whereas the other 3 templates do. However, those other 3 templates come with a lot of pieces that might be more than you really want and so you have to do work to take out all the sample data and that kind of thing.

It’s the same for the Windows Phone project templates;

image

What I do is to create a blank application. For this post, I am starting with a “Blank App” for Windows and a “Blank App” for Phone and I’ve added them both into a single solution.

What I then generally do is delete the MainPage.xaml file that comes in from the project template and replace it with a new page created via the “Add New Item…” dialog box and I choose the “Basic Page” template as below;

image

This then adds all the missing pieces that are needed to support that basic page;

image

giving new items in the Common folder of my project;

image

Now…the only pieces of this I care about from the point of view of this post are the NavigationHelper and the SuspensionManager objects but I now have 2 new MainPage.xaml files in both of my Windows/Phone projects and these 4 supporting code files have been added too;

image

SuspensionManager Meet Frame

In one of these Windows/Phone apps you have a UI which is sitting in a Frame UI control and that Frame knows how to Navigate between Pages. Unlike Windows Phone 8.0, it’s possible in Windows/Phone 8.1 to have more than one Frame in an application with each one having a separate navigation history. That said, I’ve almost never seen anyone do that and so (for me) it adds some complexity that’s rarely used.

In the templates that we’ve used to create our app, the Frame will be created in the override of Application.OnLaunched with code that takes a look at the Window.Content property and creates the main UI if it’s not found in that Windows’ Content.

For me, this is kind of “ok” but it doesn’t indicate that there are scenarios where we might need to create our application’s UI outside of the OnLaunched override and so, generally, I’d prefer to factor this code out of this function but for this post I’ll leave it there to avoid adding more confusion.

The code looks exactly like this as of Visual Studio 2013 Update 2;

        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
#if DEBUG
            if (System.Diagnostics.Debugger.IsAttached)
            {
                this.DebugSettings.EnableFrameRateCounter = true;
            }
#endif

            Frame rootFrame = Window.Current.Content as Frame;

            // 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();

                // TODO: change this value to a cache size that is appropriate for your application
                rootFrame.CacheSize = 1;

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

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }

            if (rootFrame.Content == null)
            {
                // Removes the turnstile navigation for startup.
                if (rootFrame.ContentTransitions != null)
                {
                    this.transitions = new TransitionCollection();
                    foreach (var c in rootFrame.ContentTransitions)
                    {
                        this.transitions.Add(c);
                    }
                }

                rootFrame.ContentTransitions = null;
                rootFrame.Navigated += this.RootFrame_FirstNavigated;

                // 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");
                }
            }

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

If you’re particularly sharp-eyed then you might notice that the code above is taken from my Windows Phone project and the way in which you can spot that is that the Windows Phone project template adds an additional call to set the Frame.CacheSize property to a value of 1. It also adds transition animations. There would be less code in the Windows code but the difference is fairly small.

Now, the Frame is a content control (i.e. has a Content property) and it can do a few navigation things (i.e. a Navigate method, some navigation events, properties like CanNavigate and it manages a BackStack for you and so on) but it also has methods via which it can be asked to get/set its navigation history as a string;

  • GetNavigationState() and SetNavigationState()

I don’t think the specifics of the string used by this method are specified anywhere but if the Frame has navigated Page1->Page2->Page3 then the string will include that information and, because a navigation call can take an optional single object parameter that parameter also needs to be represented in this string which puts a requirement on those parameters to be able to be serialized into a string.

Clearly, if we have a Frame that’s navigated Page 1 ( param 1 ) –> Page 2 ( param 2 ) –> Page 3 ( param 3 ) and we then were to;

  1. Ask the Frame for its navigation state via GetNavigationState()
  2. Destroy the Frame
  3. Recreate the Frame
  4. Attempt to put the Frame back to where it was by calling SetNavigationState()

then unless the entire information needed by the pages can be reconstructed purely from the navigation parameters we will have lost information. This becomes important when an app is terminated by the OS and has to restore itself at a later point.

If the SuspensionManager is to be able to assist in this process then it needs to be aware of the Frame (or potentially Frames) and so our code has to change to make that introduction. The other project templates (Grid, Hub, etc) already contain this code and I’ll add it to my OnLaunched method as below and in both my Windows/Phone projects;

      Frame rootFrame = Window.Current.Content as Frame;

      // 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();

        // MT: Register the Frame with the SuspensionManager.
        SuspensionManager.RegisterFrame(rootFrame, "rootFrameKey");

Here’s a nice picture that I drew of this meeting between the Frame and the SuspensionManager;

image

What does the SuspensionManager actually do with this Frame? It effectively adds it onto a list such that it can get back to it at a later point in time.

SuspensionManager Restores Frame

Having introduced the Frame to the SuspensionManager we can ask that SuspensionManager to attempt to restore the Frame back to a state which it was in at the point where the operating system chose to terminate the application without the user’s knowledge as part of the regular suspend/resume/terminate application lifecycle management that it performs.

We’d only want to do this in that specific scenario where the app has been terminated and the template Application.OnLaunched override already has a check in there for this condition;

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

and we can wire that up with our SuspensionManager by making an async method and making the call;

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

This call ends up being async because the SuspensionManager stores its state to disk so it’s going to be an async call on WinRT to get that state back. By making this call, one of the things we’re asking the SuspensionManager to do is to attempt to load from disk any Frame navigation history that it has stored and to put it back onto the right Frame via the SetNavigationState method on the Frame.

image

Note that your application may want to make a choice about whether to restore state or not depending on how recently that state was saved – e.g. if the app was terminated a long time ago it may not make sense to attempt to restore the user back to their previous state as they may well not remember it and the behaviour could appear random.

With the code as it stands though, nothing in my two applications would cause the SuspensionManager to ever save anything to disk and so I need to make sure that happens.

SuspensionManager Saves State During Suspension

Whenever the app is suspended, the SuspensionManager needs to save state such that it can get it back for the case where the app needs to restore itself after a termination.

image

This is easy enough from a code perspective – I can edit the Application.OnSuspending handler that the templates give me to ask the SuspensionManager to save its state to disk.

    private async void OnSuspending(object sender, SuspendingEventArgs e)
    {
      var deferral = e.SuspendingOperation.GetDeferral();

      await SuspensionManager.SaveAsync();

      deferral.Complete();
    }

and as the diagram above indicates, the SuspensionManager is managing a global set of state which is accessed via SuspensionManager.SessionState. Within that state, there’s effectively a dictionary of state for each Frame and within that state there’s effectively a dictionary of state for each Page which gets created by the NavigationHelper class but we haven’t got to that just yet.

Adding Some Pages

To see where I’ve got to so far, I thought I’d add some pages to my application and so I modified MainPage.xaml and then added a SecondPage.xaml and ThirdPage.xaml as below.

What I didn’t do was just copy the code/XAML for the pages between the 2 projects. In each case, I used the “Add New Item…” dialog.

I did that because I was convinced that the templated item for a “Basic Page” was different between Windows/Phone 8.1 and, specifically, I thought that the page template for Windows Phone 8.1 set the Page.NavigationCacheMode to a value of Required but it looks like that has changed between the release candidate of Visual Studio 2013 Update 2 and the final version. You will still find places in the templates (e.g. in the Hub project template) where pages are setting that value and I’ll mention it a little later.

I tried to make these pages as small as they could possibly be to avoid taking up space in the post with XAML/code and I’ve collapsed them here in the post as they’re very simple. Here’s the MainPage;

<Page x:Name="pageRoot"
      x:Class="BlankWindowsApp.SecondPage"
      DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:BlankWindowsApp"
      xmlns:common="using:BlankWindowsApp.Common"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

  <Grid Background="Green">
    <Grid.ChildrenTransitions>
      <TransitionCollection>
        <EntranceThemeTransition />
      </TransitionCollection>
    </Grid.ChildrenTransitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="140" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <!-- Back button and page title -->
    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="120" />
        <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>
      <Button x:Name="backButton"
              Margin="39,59,39,0"
              Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
              Style="{StaticResource NavigationBackButtonNormalStyle}"
              VerticalAlignment="Top"
              AutomationProperties.Name="Back"
              AutomationProperties.AutomationId="BackButton"
              AutomationProperties.ItemType="Navigation Button" />
      <TextBlock x:Name="pageTitle"
                 Style="{StaticResource HeaderTextBlockStyle}"
                 Grid.Column="1"
                 IsHitTestVisible="false"
                 TextWrapping="NoWrap"
                 VerticalAlignment="Bottom"
                 Margin="0,0,30,40" />
    </Grid>
    <StackPanel Grid.Row="1"
                Margin="120,0,0,0">
      <TextBox x:Name="txtSecondPageOne"
               Text="Not Set" />
      <Button Content="Navigate Thid Page"
              Click="OnNavigateThirdPage" />
    </StackPanel>
  </Grid>
</Page>

and there’s a bunch of stuff in the code-behind file but the only bit that was contributed by me is the button handler to navigate to the second page;

    private void OnNavigateSecondPage(object sender, RoutedEventArgs e)
    {
      this.Frame.Navigate(typeof(SecondPage), this.txtMainPageOne.Text);
    }

and that SecondPage looks like this;

<Page x:Name="pageRoot"
      x:Class="BlankWindowsApp.SecondPage"
      DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:BlankWindowsApp"
      xmlns:common="using:BlankWindowsApp.Common"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

  <Grid Background="Green">
    <Grid.ChildrenTransitions>
      <TransitionCollection>
        <EntranceThemeTransition />
      </TransitionCollection>
    </Grid.ChildrenTransitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="140" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <!-- Back button and page title -->
    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="120" />
        <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>
      <Button x:Name="backButton"
              Margin="39,59,39,0"
              Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
              Style="{StaticResource NavigationBackButtonNormalStyle}"
              VerticalAlignment="Top"
              AutomationProperties.Name="Back"
              AutomationProperties.AutomationId="BackButton"
              AutomationProperties.ItemType="Navigation Button" />
      <TextBlock x:Name="pageTitle"
                 Style="{StaticResource HeaderTextBlockStyle}"
                 Grid.Column="1"
                 IsHitTestVisible="false"
                 TextWrapping="NoWrap"
                 VerticalAlignment="Bottom"
                 Margin="0,0,30,40" />
    </Grid>
    <StackPanel Grid.Row="1"
                Margin="120,0,0,0">
      <TextBox x:Name="txtSecondPageOne"
               Text="Not Set" />
      <Button Content="Navigate Thid Page"
              Click="OnNavigateThirdPage" />
    </StackPanel>
  </Grid>
</Page>

and, again, I’ve only added a tiny bit of code to that page;

    private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
    {
      this.pageTitle.Text = (string)e.NavigationParameter;
    }

    private void OnNavigateThirdPage(object sender, RoutedEventArgs e)
    {
      this.Frame.Navigate(typeof(ThirdPage), this.txtSecondPageOne.Text);
    }

now this navigationHelper_LoadState function feels like a strange place to put the code which picks up the navigation parameter but I think it’s the right place to do it. I’ll come back to NavigationHelper in a moment but, again, there’s a button handler which navigates to the third page in the app which is just;

<Page x:Name="pageRoot"
      x:Class="BlankWindowsApp.ThirdPage"
      DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:BlankWindowsApp"
      xmlns:common="using:BlankWindowsApp.Common"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">


  <Grid Background="Orange">
    <Grid.ChildrenTransitions>
      <TransitionCollection>
        <EntranceThemeTransition />
      </TransitionCollection>
    </Grid.ChildrenTransitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="140" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <!-- Back button and page title -->
    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="120" />
        <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>
      <Button x:Name="backButton"
              Margin="39,59,39,0"
              Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
              Style="{StaticResource NavigationBackButtonNormalStyle}"
              VerticalAlignment="Top"
              AutomationProperties.Name="Back"
              AutomationProperties.AutomationId="BackButton"
              AutomationProperties.ItemType="Navigation Button" />
      <TextBlock x:Name="pageTitle"
                 Style="{StaticResource HeaderTextBlockStyle}"
                 Grid.Column="1"
                 IsHitTestVisible="false"
                 TextWrapping="NoWrap"
                 VerticalAlignment="Bottom"
                 Margin="0,0,30,40" />
    </Grid>
  </Grid>

</Page>

and for that page I only added enough code to set up the page title from the navigation parameter of the previous page;


    private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
    {
      this.pageTitle.Text = (string)e.NavigationParameter;
    }

I’ll not paste all of the Windows Phone XAML into the blog post – it’s the same XAML/code but with the “Back” buttons removed as the phone doesn’t need a soft back-button displayed by an app like this.

It’s hard to make a mess when you’re only writing so little code but this code definitely doesn’t work. If I navigate through the application typing in text to be passed to each subsequent page as a parameter then I see;

image

Clearly, on navigating back here from page 3 to page 2 the state that was present in the TextBox has been lost and while the screenshots come from Windows 8.1, it’s the same story on the Phone.

Page Meet NavigationHelper Meet SuspensionManager

Before fixing this, what about this NavigationHelper class? Every “Basic Page” that you add to your project via the “Add New Item…” dialog is going to create a NavigationHelper object instance and you’ll see a bunch of boiler-plate code in each page from the template. That code does a few things;

  1. Creates an instance of NavigationHelper for the Page and makes it available via the NavigationHelper property.
    1. The creation passes the Page so that the helper can handle the Page.Loaded event and so that it can also get hold of the Frame.
  2. Overrides Page.OnNavigatedTo and OnNavigatedFrom and calls NavigationHelper.OnNavigatedTo/NavigationHelper.OnNavigatedFrom.
  3. Surfaces the instance of NavigationHelper as a property named NavigationHelper and data-binds to that property from the Back button that it creates in the XAML.

Because it has a reference to the Frame, the NavigationHelper can offer;

  1. GoForward/GoBack methods.
  2. GoForwardCommand/GoBackCommand properties serving up ICommand implementations which can then be bound to from the UI and the XAML.
  3. A handler for the phone’s hardware Back button such that the button will cause the Frame to check its backstack. If the Frame can navigate backwards then it will do so and it will tell the OS that it has handled the Back event. Otherwise, the OS will perform its default behaviour and will navigate out of the app (this is the default behaviour on Windows Phone 8.1).

The other aspect of the NavigationHelper is the work it does in its OnNavigatedTo/OnNavigatedFrom methods. What’s done here is effectively to fire LoadState/SaveState events in response to the Page being navigated to/from ( which the template sets the Page up to handle ).

I find this pattern a little odd. We navigate to the Page. The Page handles OnNavigatedTo and calls NavigationHandler.OnNavigatedTo which in turn fires a LoadState event back into the Page. It feels a bit circular to me but that’s how it works.

The SaveState event is a simpler one. The NavigationHelper makes a new state dictionary, passes it into the SaveState handler for the Page to save its state and then simply adds it to the state dictionary that the SuspensionManager is keeping for the Frame using a key which is based on the depth of the navigation stack (given that only one page can be at  a particular depth in the navigation stack).

So, as a page is being “left” it has an opportunity to write out any state that it might need to reconstruct itself in the future.

The LoadState event is a bit more complex but, fundamentally, a handler is passed a dictionary of state to restore itself from along with the navigation parameter which could be null. That dictionary is obtained from the SuspensionManager’s state for the Frame and if the dictionary is not null then it should be used to restore state. The NavigationHelper attempts to make sure that it passes null or the appropriate dictionary to match the navigation scenario.

So, in order to deal with the problem of my code not working right now I’m going to need to change my LoadState/SaveState handlers in the 2 pages I have which build up transient state which is my MainPage and my SecondPage.

Before I go there though, I want to take a detour.

Page Caching

I can fix the current problem that I’ve got by simply adding one line of code to each of my MainPage and SecondPage constructors;

      this.NavigationCacheMode = NavigationCacheMode.Enabled;

Easy.

If I now run the Windows 8.1 application it works “correctly” or, at least, seems to.

If I run the Windows Phone 8.1 application it doesn’t quite work as well…I see it losing state as per the screenshots below where I navigate forwards and then backwards;

image

By switching on page caching and having a Frame.CacheSize set to 1 on the Phone, I’m hitting a problem because I’m relying on the Pages all staying in memory which they don’t.

One thing I could try and do is to change my use of NavigationCacheMode.Enabled to NavigationCacheMode.Required or I could manipulate the value of Frame.CacheSize to try and make sure that I cache all my pages.

It’s worth repeating that the template code that Visual Studio gave me for Windows 8.1 isn’t setting Frame.CacheSize at all whereas the Windows Phone 8.1 is setting it to a value of 1. 

So, I change the CacheSize to 2 in my Windows Phone App and everything works fine but I’m living pretty dangerously because;

  1. If the device doesn’t have the memory for the cache, I guess I’m going to fail.
  2. If the application was suspended/terminated/re-run I’m going to fail.

For 2, if I;

  1. Run the app.
  2. Enter text on page 1.
  3. Navigate to page 2.
  4. Enter text on page 2.
  5. Navigate to page 3.
  6. Suspend/Terminate the app using the debugger.
  7. Run the app again.

Then what happens is that I’ll land back on page 3 and I can correctly navigate back to page 2 and then page 1 but neither of those pages will display the right text in their textboxes because all that has been saved/restored by the SuspensionManager is going to be the navigation history, not the page state.

That’s not to say that page caching doesn’t have its place – it does Smile but relying on it to solve this problem isn’t going to work and so I stopped changing the default value of Page.NavigationCacheMode from its Disabled default and went about things a different way…

Handling NavigationHelper.LoadState/SaveState

Doing the “right thing” in response to the NavigationHelper.Load/SaveState events is going to solve both the problem around a page losing its state as it is navigated to/from and also in the case where the application is suspended/terminated/re-run.

For my “one text box” application this can be as simple as changing my MainPage.xaml.cs and my SecondPage.xaml.cs;

The MainPage.xaml.cs file adds this;

    private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
    {
      if (e.PageState != null)
      {
        this.txtMainPageOne.Text = e.PageState["txtContents"] as string;
      }
    }
    private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
    {
      e.PageState["txtContents"] = this.txtMainPageOne.Text;
    }

and the SecondPage.xaml.cs file adds this;

    private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
    {
      this.pageTitle.Text = (string)e.NavigationParameter;

      if (e.PageState != null)
      {
        this.txtSecondPageOne.Text = e.PageState["txtContents"] as string;
      }
    }
    private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
    {
      e.PageState["txtContents"] = this.txtSecondPageOne.Text;
    }

and that all works as would be expected both from the point of view of navigating backwards and forwards and also from the point of view of suspend/terminate/re-run.

There is one point I’d like to add though around “forward” navigation.

Forward Navigation

There’s still something here that perhaps produces a slightly unexpected result. If I run the app as below;

image

then you can see that the actions are;

  1. Enter state “abc” on main page.
  2. Navigate to second page with parameter “abc”.
  3. Enter state “def” on second page.
  4. Navigate back.
  5. State is correctly displayed “abc” on second main page.
  6. Navigate to second page with parameter “abc”.
  7. State on second page is “Not Set” rather than the “def” from step 3 above.

Now, this is more than likely obvious but I wanted to reflect that if I add another button to the Main Page as below;

      <Button Content="Navigate Forward"
              Click="OnNavigateForward" />

and simply ask the frame to navigate forward from the handler;

    private void OnNavigateForward(object sender, RoutedEventArgs e)
    {
      if (this.Frame.CanGoForward)
      {
        this.Frame.GoForward();
      }
    }

Then this works quite differently from a navigation perspective and you can see via the screenshots below;

image

and so, clearly, this highlights the difference between;

  1. Navigating to a page with a parameter via Navigate(Type, Parameter) which is being treated as a “new” naveigation to a page and the page is not being asked to load its state from any previous state dictionaries.
  2. Navigation to a page via going “Forwards” where the page is being asked to load its state from previous state dictionaries.

One Last Thing

One other thing that puzzled me the first time I dug into this set of classes and the way that they work is this scenario.

  1. User runs app.
  2. User enters state onto a page.
  3. User switches to another app.

Now…at some point the OS is going to suspend that app (on the Phone it happens very quickly, on Windows it takes around 10 seconds).

After then, the OS may terminate the app.

But…the NavigationHelper hooks into the Page.OnNavigatedFrom handler. That handler does not get called as the user moves away from the app at step 3 above and so any state that had been built up at stage 2 would be lost?

That’s not the case though. If you put a breakpoint into a relevant Page.OnNavigatedFrom method and then suspend your application using the debugger you’ll see a call-stack;

image

as part of calling Frame.GetNavigationState() there’s a call made to OnNavigatedFrom and so the state is grabbed from the Page if it has any.