Xamarin.Forms Shell navigation
Xamarin.Forms Shell includes a URI-based navigation experience that uses routes to navigate to any page in the application, without having to follow a set navigation hierarchy. In addition, it also provides the ability to navigate backwards without having to visit all of the pages on the navigation stack.
The Shell
class defines the following navigation-related properties:
BackButtonBehavior
, of typeBackButtonBehavior
, an attached property that defines the behavior of the back button.CurrentItem
, of typeShellItem
, the currently selected item.CurrentPage
, of typePage
, the currently presented page.CurrentState
, of typeShellNavigationState
, the current navigation state of theShell
.Current
, of typeShell
, a type-casted alias forApplication.Current.MainPage
.
The BackButtonBehavior
, CurrentItem
, and CurrentState
properties are backed by BindableProperty
objects, which means that these properties can be targets of data bindings.
Navigation is performed by invoking the GoToAsync
method, from the Shell
class. When navigation is about to be performed, the Navigating
event is fired, and the Navigated
event is fired when navigation completes.
Note
Navigation can still be performed between pages in a Shell application by using the Navigation property. For more information, see Hierarchical Navigation.
Routes
Navigation is performed in a Shell application by specifying a URI to navigate to. Navigation URIs can have three components:
- A route, which defines the path to content that exists as part of the Shell visual hierarchy.
- A page. Pages that don't exist in the Shell visual hierarchy can be pushed onto the navigation stack from anywhere within a Shell application. For example, a details page won't be defined in the Shell visual hierarchy, but can be pushed onto the navigation stack as required.
- One or more query parameters. Query parameters are parameters that can be passed to the destination page while navigating.
When a navigation URI includes all three components, the structure is: //route/page?queryParameters
Register routes
Routes can be defined on FlyoutItem
, TabBar
, Tab
, and ShellContent
objects, through their Route
properties:
<Shell ...>
<FlyoutItem ...
Route="animals">
<Tab ...
Route="domestic">
<ShellContent ...
Route="cats" />
<ShellContent ...
Route="dogs" />
</Tab>
<ShellContent ...
Route="monkeys" />
<ShellContent ...
Route="elephants" />
<ShellContent ...
Route="bears" />
</FlyoutItem>
<ShellContent ...
Route="about" />
...
</Shell>
Note
All items in the Shell hierarchy have a route associated with them. If you don't set a route, one is generated at runtime. However, generated routes are not guaranteed to be consistent across different application sessions.
The above example creates the following route hierarchy, which can be used in programmatic navigation:
animals
domestic
cats
dogs
monkeys
elephants
bears
about
To navigate to the ShellContent
object for the dogs
route, the absolute route URI is //animals/domestic/dogs
. Similarly, to navigate to the ShellContent
object for the about
route, the absolute route URI is //about
.
Warning
An ArgumentException
will be thrown on application startup if a duplicate route is detected. This exception will also be thrown if two or more routes at the same level in the hierarchy share a route name.
Register detail page routes
In the Shell
subclass constructor, or any other location that runs before a route is invoked, additional routes can be explicitly registered for any detail pages that aren't represented in the Shell visual hierarchy. This is accomplished with the Routing.RegisterRoute
method:
Routing.RegisterRoute("monkeydetails", typeof(MonkeyDetailPage));
Routing.RegisterRoute("beardetails", typeof(BearDetailPage));
Routing.RegisterRoute("catdetails", typeof(CatDetailPage));
Routing.RegisterRoute("dogdetails", typeof(DogDetailPage));
Routing.RegisterRoute("elephantdetails", typeof(ElephantDetailPage));
This example registers detail pages, that aren't defined in the Shell
subclass, as routes. These detail pages can then be navigated to using URI-based navigation, from anywhere within the application. The routes for such pages are known as global routes.
Warning
An ArgumentException
will be thrown if the Routing.RegisterRoute
method attempts to register the same route to two or more different types.
Alternatively, pages can be registered at different route hierarchies if required:
Routing.RegisterRoute("monkeys/details", typeof(MonkeyDetailPage));
Routing.RegisterRoute("bears/details", typeof(BearDetailPage));
Routing.RegisterRoute("cats/details", typeof(CatDetailPage));
Routing.RegisterRoute("dogs/details", typeof(DogDetailPage));
Routing.RegisterRoute("elephants/details", typeof(ElephantDetailPage));
This example enables contextual page navigation, where navigating to the details
route from the page for the monkeys
route displays the MonkeyDetailPage
. Similarly, navigating to the details
route from the page for the elephants
route displays the ElephantDetailPage
. For more information, see Contextual navigation.
Note
Pages whose routes have been registered with the Routing.RegisterRoute
method can be deregistered with the Routing.UnRegisterRoute
method, if required.
Perform navigation
To perform navigation, a reference to the Shell
subclass must first be obtained. This reference can be obtained by casting the App.Current.MainPage
property to a Shell
object, or through the Shell.Current
property. Navigation can then be performed by calling the GoToAsync
method on the Shell
object. This method navigates to a ShellNavigationState
and returns a Task
that will complete once the navigation animation has completed. The ShellNavigationState
object is constructed by the GoToAsync
method, from a string
, or a Uri
, and it has its Location
property set to the string
or Uri
argument.
Important
When a route from the Shell visual hierarchy is navigated to, a navigation stack isn't created. However, when a page that's not in the Shell visual hierarchy is navigated to, a navigation stack is created.
The current navigation state of the Shell
object can be retrieved through the Shell.Current.CurrentState
property, which includes the URI of the displayed route in the Location
property.
Absolute routes
Navigation can be performed by specifying a valid absolute URI as an argument to the GoToAsync
method:
await Shell.Current.GoToAsync("//animals/monkeys");
This example navigates to the page for the monkeys
route, with the route being defined on a ShellContent
object. The ShellContent
object that represents the monkeys
route is a child of a FlyoutItem
object, whose route is animals
.
Relative routes
Navigation can also be performed by specifying a valid relative URI as an argument to the GoToAsync
method. The routing system will attempt to match the URI to a ShellContent
object. Therefore, if all the routes in an application are unique, navigation can be performed by only specifying the unique route name as a relative URI.
The following relative route formats are supported:
Format | Description |
---|---|
route | The route hierarchy will be searched for the specified route, upwards from the current position. The matching page will be pushed to the navigation stack. |
/route | The route hierarchy will be searched from the specified route, downwards from the current position. The matching page will be pushed to the navigation stack. |
//route | The route hierarchy will be searched for the specified route, upwards from the current position. The matching page will replace the navigation stack. |
///route | The route hierarchy will be searched for the specified route, downwards from the current position. The matching page will replace the navigation stack. |
The following example navigates to the page for the monkeydetails
route:
await Shell.Current.GoToAsync("monkeydetails");
In this example, the monkeyDetails
route is searched for up the hierarchy until the matching page is found. When the page is found, it's pushed to the navigation stack.
Contextual navigation
Relative routes enable contextual navigation. For example, consider the following route hierarchy:
monkeys
details
bears
details
When the registered page for the monkeys
route is displayed, navigating to the details
route will display the registered page for the monkeys/details
route. Similarly, when the registered page for the bears
route is displayed, navigating to the details
route will display the registered page for the bears/details
route. For information on how to register the routes in this example, see Register page routes.
Backwards navigation
Backwards navigation can be performed by specifying ".." as the argument to the GoToAsync
method:
await Shell.Current.GoToAsync("..");
Backwards navigation with ".." can also be combined with a route:
await Shell.Current.GoToAsync("../route");
In this example, backwards navigation is performed, and then navigation to the specified route.
Important
Navigating backwards and into a specified route is only possible if the backwards navigation places you at the current location in the route hierarchy to navigate to the specified route.
Similarly, it's possible to navigate backwards multiple times, and then navigate to a specified route:
await Shell.Current.GoToAsync("../../route");
In this example, backwards navigation is performed twice, and then navigation to the specified route.
In addition, data can be passed through query properties when navigating backwards:
await Shell.Current.GoToAsync($"..?parameterToPassBack={parameterValueToPassBack}");
In this example, backwards navigation is performed, and the query parameter value is passed to the query parameter on the previous page.
Note
Query parameters can be appended to any backwards navigation request.
For more information about passing data when navigating, see Pass data.
Invalid routes
The following route formats are invalid:
Format | Explanation |
---|---|
//page or ///page | Global routes currently can't be the only page on the navigation stack. Therefore, absolute routing to global routes is unsupported. |
Use of these route formats results in an Exception
being thrown.
Warning
Attempting to navigate to a non-existent route results in an ArgumentException
exception being thrown.
Debugging navigation
Some of the Shell classes are decorated with the DebuggerDisplayAttribute
, which specifies how a class or field is displayed by the debugger. This can help to debug navigation requests by displaying data related to the navigation request. For example, the following screenshot shows the CurrentItem
and CurrentState
properties of the Shell.Current
object:
In this example, the CurrentItem
property, of type FlyoutItem
, displays the title and route of the FlyoutItem
object. Similarly, the CurrentState
property, of type ShellNavigationState
, displays the URI of the displayed route within the Shell application.
Navigation stack
The Tab
class defines a Stack
property, of type IReadOnlyList<Page>
, which represents the current navigation stack within the Tab
. The class also provides the following overridable navigation methods:
GetNavigationStack
, returnsIReadOnlyList<Page>
, the current navigation stack.OnInsertPageBefore
, that's called whenINavigation.InsertPageBefore
is called.OnPopAsync
, returnsTask<Page>
, and is called whenINavigation.PopAsync
is called.OnPopToRootAsync
, returnsTask
, and is called whenINavigation.OnPopToRootAsync
is called.OnPushAsync
, returnsTask
, and is called whenINavigation.PushAsync
is called.OnRemovePage
, that's called whenINavigation.RemovePage
is called.
The following example shows how to override the OnRemovePage
method:
public class MyTab : Tab
{
protected override void OnRemovePage(Page page)
{
base.OnRemovePage(page);
// Custom logic
}
}
In this example, MyTab
objects should be consumed in your Shell visual hierarchy instead of Tab
objects.
Navigation events
The Shell
class defines the Navigating
event, which is fired when navigation is about to be performed, either due to programmatic navigation or user interaction. The ShellNavigatingEventArgs
object that accompanies the Navigating
event provides the following properties:
Property | Type | Description |
---|---|---|
Current |
ShellNavigationState |
The URI of the current page. |
Source |
ShellNavigationSource |
The type of navigation that occurred. |
Target |
ShellNavigationState |
The URI representing where the navigation is destined. |
CanCancel |
bool |
A value indicating if it's possible to cancel the navigation. |
Cancelled |
bool |
A value indicating if the navigation was canceled. |
In addition, the ShellNavigatingEventArgs
class provides a Cancel
method that can be used to cancel navigation, and a GetDeferral
method that returns a ShellNavigatingDeferral
token that can be used to complete navigation. For more information about navigation deferral, see Navigation deferral.
The Shell
class also defines the Navigated
event, which is fired when navigation has completed. The ShellNavigatedEventArgs
object that accompanies the Navigated
event provides the following properties:
Property | Type | Description |
---|---|---|
Current |
ShellNavigationState |
The URI of the current page. |
Previous |
ShellNavigationState |
The URI of the previous page. |
Source |
ShellNavigationSource |
The type of navigation that occurred. |
Important
The OnNavigating
method is called when the Navigating
event fires. Similarly, the OnNavigated
method is called when the Navigated
event fires. Both methods can be overridden in your Shell
subclass to intercept navigation requests.
The ShellNavigatedEventArgs
and ShellNavigatingEventArgs
classes both have Source
properties, of type ShellNavigationSource
. This enumeration provides the following values:
Unknown
Push
Pop
PopToRoot
Insert
Remove
ShellItemChanged
ShellSectionChanged
ShellContentChanged
Therefore, navigation can be intercepted in an OnNavigating
override and actions can be performed based on the navigation source. For example, the following code shows how to cancel backwards navigation if the data on the page is unsaved:
protected override void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
// Cancel any back navigation.
if (args.Source == ShellNavigationSource.Pop)
{
args.Cancel();
}
// }
Navigation deferral
Shell navigation can be intercepted and completed or canceled based on user choice. This can be achieved by overriding the OnNavigating
method in your Shell
subclass, and by calling the GetDeferral
method on the ShellNavigatingEventArgs
object. This method returns a ShellNavigatingDeferral
token that has a Complete
method, which can be used to complete the navigation request:
public MyShell : Shell
{
// ...
protected override async void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
ShellNavigatingDeferral token = args.GetDeferral();
var result = await DisplayActionSheet("Navigate?", "Cancel", "Yes", "No");
if (result != "Yes")
{
args.Cancel();
}
token.Complete();
}
}
In this example, an action sheet is displayed that invites the user to complete the navigation request, or cancel it. Navigation is canceled by invoking the Cancel
method on the ShellNavigatingEventArgs
object. Navigation is completed by invoking the Complete
method on the ShellNavigatingDeferral
token that was retrieved by the GetDeferral
method on the ShellNavigatingEventArgs
object.
Warning
The GoToAsync
method will throw a InvalidOperationException
if a user tries to navigate while there is a pending navigation deferral.
Pass data
Data can be passed as query parameters when performing URI-based programmatic navigation. This is achieved by appending ?
after a route, followed by a query parameter id, =
, and a value. For example, the following code is executed in the sample application when a user selects an elephant on the ElephantsPage
:
async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}");
}
This code example retrieves the currently selected elephant in the CollectionView
, and navigates to the elephantdetails
route, passing elephantName
as a query parameter.
There are two approaches to receiving navigation data:
- The class that represents the page being navigated to, or the class for the page's
BindingContext
, can be decorated with aQueryPropertyAttribute
for each query parameter. For more information, see Process navigation data using query property attributes. - The class that represents the page being navigated to, or the class for the page's
BindingContext
, can implement theIQueryAttributable
interface. For more information, see Process navigation data using a single method.
Process navigation data using query property attributes
Navigation data can be received by decorating the receiving class with a QueryPropertyAttribute
for each query parameter:
[QueryProperty(nameof(Name), "name")]
public partial class ElephantDetailPage : ContentPage
{
public string Name
{
set
{
LoadAnimal(value);
}
}
...
void LoadAnimal(string name)
{
try
{
Animal animal = ElephantData.Elephants.FirstOrDefault(a => a.Name == name);
BindingContext = animal;
}
catch (Exception)
{
Console.WriteLine("Failed to load animal.");
}
}
}
The first argument for the QueryPropertyAttribute
specifies the name of the property that will receive the data, with the second argument specifying the query parameter id. Therefore, the QueryPropertyAttribute
in the above example specifies that the Name
property will receive the data passed in the name
query parameter from the URI in the GoToAsync
method call. The Name
property setter calls the LoadAnimal
method to retrieve the Animal
object for the name
, and sets it as the BindingContext
of the page.
Note
Query parameter values that are received via the QueryPropertyAttribute
are automatically URL decoded.
Process navigation data using a single method
Navigation data can be received by implementing the IQueryAttributable
interface on the receiving class. The IQueryAttributable
interface specifies that the implementing class must implement the ApplyQueryAttributes
method. This method has a query
argument, of type IDictionary<string, string>
, that contains any data passed during navigation. Each key in the dictionary is a query parameter id, with its value being the query parameter value. The advantage of using this approach is that navigation data can be processed using a single method, which can be useful when you have multiple items of navigation data that require processing as a whole.
The following example shows a view model class that implements the IQueryAttributable
interface:
public class MonkeyDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
public Animal Monkey { get; private set; }
public void ApplyQueryAttributes(IDictionary<string, string> query)
{
// The query parameter requires URL decoding.
string name = HttpUtility.UrlDecode(query["name"]);
LoadAnimal(name);
}
void LoadAnimal(string name)
{
try
{
Monkey = MonkeyData.Monkeys.FirstOrDefault(a => a.Name == name);
OnPropertyChanged("Monkey");
}
catch (Exception)
{
Console.WriteLine("Failed to load animal.");
}
}
...
}
In this example, the ApplyQueryAttributes
method retrieves the value of the name
query parameter from the URI in the GoToAsync
method call. Then, the LoadAnimal
method is called to retrieve the Animal
object, where its set as the value of the Monkey
property that is data bound to.
Important
Query parameter values that are received via the IQueryAttributable
interface aren't automatically URL decoded.
Pass and process multiple query parameters
Multiple query parameters can be passed by connecting them with &
. For example, the following code passes two data items:
async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
string elephantLocation = (e.CurrentSelection.FirstOrDefault() as Animal).Location;
await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}&location={elephantLocation}");
}
This code example retrieves the currently selected elephant in the CollectionView
, and navigates to the elephantdetails
route, passing elephantName
and elephantLocation
as query parameters.
To receive multiple items of data, the class that represents the page being navigated to, or the class for the page's BindingContext
, can be decorated with a QueryPropertyAttribute
for each query parameter:
[QueryProperty(nameof(Name), "name")]
[QueryProperty(nameof(Location), "location")]
public partial class ElephantDetailPage : ContentPage
{
public string Name
{
set
{
// Custom logic
}
}
public string Location
{
set
{
// Custom logic
}
}
...
}
In this example, the class is decorated with a QueryPropertyAttribute
for each query parameter. The first QueryPropertyAttribute
specifies that the Name
property will receive the data passed in the name
query parameter, while the second QueryPropertyAttribute
specifies that the Location
property will receive the data passed in the location
query parameter. In both cases, the query parameter values are specified in the URI in the GoToAsync
method call.
Alternatively, navigation data can be processed by a single method by implementing the IQueryAttributable
interface on the class that represents the page being navigated to, or the class for the page's BindingContext
:
public class ElephantDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
public Animal Elephant { get; private set; }
public void ApplyQueryAttributes(IDictionary<string, string> query)
{
string name = HttpUtility.UrlDecode(query["name"]);
string location = HttpUtility.UrlDecode(query["location"]);
...
}
...
}
In this example, the ApplyQueryAttributes
method retrieves the value of the name
and location
query parameters from the URI in the GoToAsync
method call.
Back button behavior
Back button appearance and behavior can be redefined by setting the BackButtonBehavior
attached property to a BackButtonBehavior
object. The BackButtonBehavior
class defines the following properties:
Command
, of typeICommand
, which is executed when the back button is pressed.CommandParameter
, of typeobject
, which is the parameter that's passed to theCommand
.IconOverride
, of typeImageSource
, the icon used for the back button.IsEnabled
, of typeboolean
, indicates whether the back button is enabled. The default value istrue
.TextOverride
, of typestring
, the text used for the back button.
All of these properties are backed by BindableProperty
objects, which means that the properties can be targets of data bindings.
The following code shows an example of redefining back button appearance and behavior:
<ContentPage ...>
<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding BackCommand}"
IconOverride="back.png" />
</Shell.BackButtonBehavior>
...
</ContentPage>
The equivalent C# code is:
Shell.SetBackButtonBehavior(this, new BackButtonBehavior
{
Command = new Command(() =>
{
...
}),
IconOverride = "back.png"
});
The Command
property is set to an ICommand
to be executed when the back button is pressed, and the IconOverride
property is set to the icon that's used for the back button: