Migrating Page Navigation Apps from Xamarin Forms

Getting navigation architecture right is crucial for creating apps that work smoothly. This is especially true for frameworks like Xamarin.Forms, WPF, or UWP, which handle how users move around the app.

In Xamarin.Forms, everything revolves around Pages – the screens or views of the app. Moving between these pages is a big part of how users interact with the app. There are different ways to structure this movement, like Master Detail, Tabbed, and Modal patterns, giving you flexibility in designing how your app looks and feels.

However, the road to a smooth app experience is not always straightforward. There are common issues like messing up the navigation stack, memory leaks, and dealing with async operations that can cause headaches. In this blog, we’re going to dive into the practical side of migrating page navigation in Xamarin.Forms apps. We’ll look at common issues you might face and, more importantly, how to solve them. Our goal is to help you ensure your app remains user-friendly while keeping up with the latest in .NET development.

Xamarin Forms offers multiple navigation architectures for apps. In this first blog in a series on navigation, we will look at page navigation and how to migrate your Xamarin Forms page navigation apps to Uno Platform. 

Feel free to follow along with the provided code sample or refer to it as we move through the article. 

Navigation in Xamarin Forms

To support page navigation the application has a single NavigationPage and this provides the blank canvas in which to navigate to specific pages. The NavigationPage maintains a stack allowing navigation forwards to new pages by specifying a new instance of page type to navigate to. You can also programmatically navigate backwards in this stack or right to the beginning to return to your initial page. The methods are named with the terminology of a stack, so you call PopAsync to remove a page from the stack and return to the previous page, or PushAsync with an instance of the new page to be added to the stack and navigated to. There isn’t a mechanism to pass a parameter to the new page but since you must construct it yourself you can set properties on the new page before passing it to PushAsync. 

It is also possible to programmatically remove pages from the stack, for example when you have a page to perform a one-off operation like authentication that you don’t want to remain in the history. From the user’s perspective, they can request to navigate back using the shell back button (e.g. on Android) or presented in the header of the NavigationPage on other platforms e.g. iOS. Either way the intention is to present navigation in a way which is consistent with the device OS. Each page has a Navigation property which provides methods to programmatically navigate forwards or backwards in the parent NavigationPage – you don’t have to programmatically crawl through the tree to locate the hosting NavigationPage. When you want to navigate from a ViewModel you need a reference to the INavigation interface from the main NavigationPage. In this case you can register this with your IoC container, such as the DependencyService in Xamarin Forms, or a third-party library. 

The WinUI and Uno Platform Approach

There are similarities between the above approach and that used by Uno. That isn’t surprising as WinUI is based on UWP which itself continues many of the concepts introduced with WPF. 

Instead of a NavigationPage there is the Frame control which provides the equivalent navigation options and integration with hardware or shell back buttons. Individual pages are derived from the Page type and the Frame supports navigating between pages using the appropriate animations and maintaining a back-stack. Unlike Xamarin Forms, the stack supports forward navigation and maintains the forward navigation stack even when navigating backwards through it. If you navigate backwards and then navigate forwards to a new page this removes the previous forward stack. There isn’t any shell integration for moving forwards as this is not a navigation feature which appears on mobile devices. In practice, you are unlikely to need it and if migrating from Xamarin Forms you will not have to worry about it. 

To navigate from a Page there is a property called Frame which provides a reference to the containing frame. We use this instead of the Navigation property on Xamarin Forms. With a reference to the Frame you can call GoBack or Navigate to move backwards or forwards respectively.  

Hardware Buttons

Out of the box the Frame control is not hooked up to the hardware back button like the NavigationPage in Xamarin Forms. There are some platform differences here and a bit of conditional compilation can be used to apply the correct behaviour to each platform. This is encapsulated in the ConfigureNavigation method which is called from our App.cs after creating and activating the MainWindow. The method applies the correct behaviour across Windows, WebAssembly and Android. 

				
					        private void ConfigureNavigation() 
        { 
            var frame = (Frame)Microsoft.UI.Xaml.Window.Current.Content; 
            var manager = Windows.UI.Core.SystemNavigationManager.GetForCurrentView(); 


#if WINDOWS_UWP || __WASM__ 
    // Toggle the visibility of back button based on if the frame can navigate back. 
    // Setting it to visible has the follow effect on the platform: 
    // - uwp: add a `<-` back button on the title bar 
    // - wasm: add a dummy entry in the browser back stack 
    frame.Navigated += (s, e) => manager.AppViewBackButtonVisibility = frame.CanGoBack 
        ? Windows.UI.Core.AppViewBackButtonVisibility.Visible 
        : Windows.UI.Core.AppViewBackButtonVisibility.Collapsed; 
#endif 

#if WINDOWS_UWP || __ANDROID__ || __WASM__ 
    // On some platforms, the back navigation request needs to be hooked up to the back navigation of the Frame. 
    // These requests can come from: 
    // - uwp: title bar back button 
    // - droid: CommandBar back button, os back button/gesture 
    // - wasm: browser back button 
    manager.BackRequested += (s, e) => 
    { 
        if (frame.CanGoBack) 
        { 
            frame.GoBack(); 
 
            e.Handled = true; 
        } 
    }; 
#endif 
} 
				
			

Navigation Bar

By default, the Frame control doesn’t give you any on-screen controls unlike the Xamarin Forms NavigationPage. Here you can add your own controls to navigate back if you want to completely customize the look. However, if you want to maintain the native platform look you can use the Uno.Toolkit.WinUI library which includes the NavigationBar control which you can add to each page. 

To use the control you need to add the Uno.Toolkit.WinUI package via NuGet, then import the default styles in your AppResources.xaml file. Then you can add the namespace in each page where you want to use the control, then add the NavigationBar to the top of the page.  

				
					xmlns:utu="using:Uno.Toolkit.UI" 

<utu:NavigationBar Grid.Row="0" MainCommandMode="Back" Content="Cakes" Foreground="White" Background="#3d9165"> 
				
			

Since Uno uses the native controls by default on mobile the NavigationBar you place in XAML is only used to specify the properties and the control is rendered outside of the page content docked to the top of the screen. If you used the page’s ToolbarItems in Xamarin Forms to add custom buttons to the navigation bar, you can add these commands to the Uno NavigationBar. The example above doesn’t specify any commands but ensures that by default the system back button will be presented on the left, so long as there is a page in the back stack. 

Page Specific Navigation

Unlike Xamarin Forms, when you navigate forwards you pass in the type of the required page, and it is created for you. In order to setup the page for specific data, e.g. a details page for a particular product, you can pass a parameter (an object of any type) as the optional second argument to Navigate. 

				
					_navigationService.Navigate(typeof(CakeDetails), 3); 
				
			

In the receiving page you can override the OnNavigatedTo method and handle this value. 

				
					protected override void OnNavigatedTo(NavigationEventArgs e) 
{ 
    System.Diagnostics.Debug.WriteLine(e.Parameter); 

    base.OnNavigatedTo(e); 
} 
				
			

Depending on the make up of your application this could be a ViewModel, or some other identifier or parameter used to load specific data for the page. 

Transition Animations in Xamarin Forms and WinUI

As well as switching between pages, each transition is normally accompanied by an animation to provide a smoother looking experience. In Xamarin Forms the NavigationPage allows you to turn off the animation with an optional Boolean parameter on both the Pop and Push operations. 

WinUI has multiple transitions which provide additional cues as to the nature of the navigation. Some of these are specific to other controls rather than the Frame – for example providing horizontal navigation with tabs. However, for page navigation with the Frame control the default is called EntranceTransitionInfo and it subtly slides the page up into position and fades it in on Windows. On other platforms the animations are consistent with the native animations – for example, on iOS pages slide in from the right. You can disable the animation by specifying a SuppressNavigationTransitionInfo in the call to Frame.Navigate.  

WinUI also supports the Drill In transition which is used to indicate a move from a collection to a details page. In the sample app we use this to differentiate the animation between the cake list page and the cake details page. To use this, we pass a DrillInNavigationTransitionInfo to the Navigate method. The animation gives the impression that you are moving into the data by performing a subtle zoom. When you navigate backwards it appears to zoom out to the wider collection. This can be used when navigating from pages with lists or grids. Again, whether this is used will depend on the target platform. 

By default, the frame performs the reverse transition defined for that page navigation when navigating backwards. This keeps the experience consistent for the user. However, you can override this by passing an optional NavigationTransitionInfo to the GoBack method. 

The way that these transitions are interpreted will vary depending on the platform. The goal is to create something which feels natural on each platform. 

Don’t Cross the Threads

As was the case with Xamarin Forms you must call all navigation methods on the UI thread. The Navigate method is passed a type and will create the required page instantiating all of the UI controls before handling the animations to transition to the new page. Any code which creates UI elements or interacts with the UI tree must be performed on the UI thread. In Xamarin this was done with the Dispatcher property of the Page: 

				
					Dispatcher.BeginInvokeOnMainThread(() => 
{ 
   // code here executes on ui thread 
}); 
				
			

For Uno Platform, the Frame has a DispatcherQueue property which is responsible for queuing work on the UI thread using the TryEnqueue method. For example, for our navigation service which can be called from a ViewModel we can wrap the GoBack calls like so: 

				
					public void GoBack() 
{ 
    if (_frame.DispatcherQueue.HasThreadAccess) 
        _frame.GoBack();
    else 
        _frame.DispatcherQueue.TryEnqueue(_frame.GoBack); 
} 
				
			

Here we check if we are already running on the UI thread by checking the HasThreadAccess property. If so, we can simply execute the code, otherwise we call TryEnqueue to queue it up for the UI thread. TryEnqueue doesn’t wait for the code to finish executing it is handed over and control passes on and out of this method. 

The sample application includes a NavigationService which implements this approach so that you can call it from any thread, and it will take care of this for you. 

Modal Navigation

Xamarin Forms exposed a Modal navigation stack for pages which appear above the main navigation stack for one-off operations which block the normal flow of the app. You can use this for log-in screens or consenting to terms and conditions and other activities which are important. In Uno there isn’t a direct equivalent so there are two approaches.  

The first is to use one of the available modal controls like the MessageDialog or ContentDialog. The other approach is to use the normal navigation stack, but you can override the navigation behaviour. For one-off operations you can remove the page from the back stack so that it doesn’t appear again when navigating backwards. You can also override the system back button so you can block it if you need the user to perform an action before navigating away from the current page. 

In Summary

Uno Platform’s Frame/Page navigation follows a similar pattern to Xamarin Forms. You can, therefore, map the same functionality when migrating from Xamarin Forms to Uno Platform. There is some scope for additional features in Uno Platform, such as transition types and more control over manipulating the back stack. As with any cross-platform development, there will be some differences to provide a familiar experience on each device type. 

It’s worth noting that Uno.Extensions Navigation offers an enticing option that can enhance your overall navigation experience for those seeking a comprehensive navigation solution beyond the basics, including abstractions for ViewModel and XAML-based navigation.

Quick References

 

Xamarin Forms 

Uno / WinUI 

Navigate back to previous page 

await Navigation.PopAsync(); 

Frame.GoBack(); 

Navigate forward to a new page 

await Navigation.PushAsync(new NewPage()); 

Frame.Navigate(typeof(NewPage)); 

Navigate forward with parameter 

await Navigation.PushAsync(new NewPage(parameter)); 

Frame.Navigate(typeof(NewPage), parameter); 

Navigate without animation 

await Navigation.PopAsync(false); 

await Navigation.PushAsync(new NewPage(), false); 

Frame.GoBack(new SuppressNavigationTransitionInfo()); 

Frame.Navigate(typeof(NewPage), null, new SuppressNavigationTransitionInfo()); 

Remove from backstack 

Navigation.RemovePage(thePage); 

Frame.BackStack.RemoveAt(index); 

Next Steps

To upgrade to the latest release of Uno Platform, please update your packages to 5.0 via your Visual Studio NuGet package manager! If you are new to Uno Platform, following our official getting started, guide is the best way to get started. (5 min to complete)

Uno Platform

Uno Platform

Follow us on Twitter

Tags: