Solving Circular Navigation in Windows Phone Silverlight Applications

The Nonlinear Navigation Service is the first recipe we are releasing. Windows Phone recipes are a group of open source (MSPL) projects aimed at helping the Windows Phone community with their applications. Recipes are usually common patterns for topics that require some attention. The idea is to provide a sample with some reusable code that can be reused later and modified to fit other similar scenarios. Developers are more than welcome to use them as is or to change them as they see fit.

Back to our topic – the main reason we are introducing the Nonlinear Navigation Service is to address a common pattern in Windows Phone Silverlight applications, where a given application has a circular path (also known as a loop) in its page navigation. Loops in an application navigation back-stack can be defined as “duplication” of specific pages in the application back-stack. The Windows Phone Silverlight application model uses pages as the building block of Silverlight applications. Most applications are built from multiple pages (of type WindowsPhonePage) hosted in a frame (of type WindowsPhoneFrame), and as a developer you have the ability to navigate to a new page and the user has the ability to navigate back and forth between the application’s pages. For each application, the phone maintains a page back-stack that reflects the history of pages visited by the user. With the current version of Windows Phone, you can’t manipulate the back-stack; that is, you don’t have any APIs to remove pages from the application’s back-stack.

Since developers can call the built-in NavigationServices.Navigate method to pass a page URI in order to show the desired URI, loops can become a real problem. For example if you have a wizard with a few pages, you can end up with a navigation back-stack that looks like the following figure, where you navigate from the main page to the second page to the third page, and then back again to the main page, because that is where you want to go.

From the UX perspective, the end user experiences a lack of consistency. When the user presses Navigate to Main Page on the third page, the application takes the user back to the main page. But now, what should happen when the user presses Back from the revisited main page? Normally from the main page you expect the application to exit, right? But if you have a loop, you will start to traverse back through the loop. This can result in user confusion, where the user expects a particular navigation behavior from pressing Back, while in fact, due to the navigation loop, the Back button behaves differently.

image

From a technical point of view, loops are duplications of page instances in your application’s back-stack. We want to eliminate these duplicates in the back-stack in order to avoid any navigation loops. You can read a lot more about Windows Phone navigation and circular navigation in the Non Linear Navigation documentation

Note: The UX guidelines for Windows Phone advise that you avoid creating a “home” functionality in your application since this is the number one cause of navigation loops. But even then, you might end up with navigation loops that you need to handle.

There are number of solutions. You can try avoiding navigation loops, perhaps by using a smart UI, popup, and context menus. However, sometimes you simply have no choice and you must include some navigation loops. Since you can’t manipulate the application’s page back-stack, you are forced to perform back navigation to clear the application’s page back-stack (read more on how Windows Phone navigation works).

Up until now, the only solution to this problem was to perform a recursive back navigation. The recursive back navigation automatically navigates back through the pages found in the application’s page back-stack, until the beginning of the navigation loop. For this to work, you needed to do the following:

  1. Keep track of your navigation history in order to identify potential loops.
  2. Once you identify a potential loop, start a recursive back by raising a “recursive back flag” and calling the NavigationServices.GoBack() method.
  3. For each page in your application, in the OnNavigatedTo method, check whether the “recursive back flag” is set; if so navigate back once more.

Luckily, we’ve solved that problem for you. Once it is initialized, the NonLinearNavigationService monitors the navigation in your application. When it detects a navigation loop (navigation to a page with a URI that already exists in the application’s page back-stack), it automatically performs the recursive back navigation for you. That is right–you need not do any additional work and this service will navigate back for you through the application’s page back-stack. Simply initialize the service in your application constructor using the following code snippet:

// init the non-linear nav service, make sure you call it last in the app CTOR
NonLinearNavigationService.Instance.Init(RootFrame);

From this point on, just keep on using the Navigate() method and the service will take care of everything else. You can “navigate” to any page, and the NLNS will clear the back-stack for you

There are few limitations that you need to be aware of when using the NonLinearNavigationService (NLNS):

  • NLNS will not allow you to create any navigation loops whatsoever; even if you want to create a loop, there is no way to signal to the NonLinearNavigationService that you want it–if loops are part of your application scenario, do not use the NonLinearNavigationService
  • If you are using an application bar in your application, you will have to hide it manually in the OnNavigatedFrom method to avoid flickering during the recursive back; while the NonLinearNavigationService sets the RootFrame to transparent during the recursive back navigation, the app bar requires special attention
  • A Back button bug manifests when canceling navigation requests; to work around this, you need to use the following code snippet in all of your pages if you are manually canceling the navigation event (usually in the OnNavigatedFrom method):
protected override void OnBackKeyPress
                      (System.ComponentModel.CancelEventArgs e)
{
    base.OnBackKeyPress(e);
 
    if (NavigationService.CanGoBack)
    {
        e.Cancel = true;
        NavigationService.GoBack();
    }
}

I strongly recommend that you to read the documentation that comes with the NonLinearNavigationService to better understand the problem and its solution. The document also includes some background information about how this recipe was created.

Download the Non-Linear Navigation Service