Lecture 5 : Navigation
Demo made in class: Navigation
- One of the very first steps in designing an app is planning how to navigation in and out of the various pages.
- You’re first project milestone will be creating a mockup of the app including how the user should navigation across pages.
- So far we’ve used
Navigation.Pushasync()
to push new pages on the stack - There exists four ways of navigating through pages:
- Tab bar navigation
- Flyout menu navigation
- Stack Navigation (what we’ve been using so far)
- Route Navigation
Stack Navigation
-
In Lab0, we used
await Navigation.PushAsync(new Page());
to push a new page on the stack and this page becomes the active page. -
This type of navigation is called “Stack Navigation” or modal navigation
-
Other methods exist under the same
INavigation
interface:PushModalAsync
(task in the active page must be completed similar to a pop-up)PopAsync
PopModalAsync
RemovePage
(doesn’t work on Android)PopToRootAsync
-
This way of navigation is easy
-
Passing in data to the pushed page is very easy:
-
Through the constructor
await Navigation.PushAsync(new Page(int data,...));
-
Through the Binding context
var mypage = new Page(); mypage.BindingContext = someObject; await Navigation.PushAsync(mypage);
-
-
It’s especially useful when the app has a specific flow:
- Sequential tasks:
- you must complete one task before moving to the next.
- Display Hierarchical data:
- you want to show more details.
- you are expanding the data on the page.
- Sequential tasks:
-
In order to remove the current page, we would use
Navigation.PopAsync();
Shell
-
It’s a
XAML
Page that “provides the fundamental UI features that most app require” -
The app shell provides a different interface for navigation
-
The shell provides a
skeleton
of your app, this is where you can define the hierarchy of the views -
You can define a
FlyoutMenu
and/or aTabBar
-
You can include all your pages and specify routes for those pages
-
You can define the look and feel of the
NavBar
-
But! It’s more complex than Stack Navigation.
Flyout Navigation
If you wish to access the views used in this demo clone the Demos
repos
-
The Flyout is sometimes called “hamburger” menu and is often represented with this icon:
-
When clicking on the icon a side bar appears with clickable items allowing the user to navigate to other pages in the app
Creating a simple FlyoutMenu
- To create a flyout menu within your .NET MAUI app, open to the
AppShell.xaml
file: - The default MAUI project only contains a
ShellContent
routing to the main page:
<Shell
x:Class="DemoNavigation2.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DemoNavigation"
Shell.FlyoutBehavior="Disabled">
<ShellContent
Title="Home"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />
</Shell>
-
Remove the line
Shell.FlyoutBehavior="Disabled"
to enable the flyout behavior -
You can now add more
ShellContent
and you’ll notice that I flyout menu appears on the top left corner containing a route to theMainPage
twice:
<Shell
x:Class="DemoNavigation2.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DemoNavigation">
<ShellContent
Title="Home"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />
<ShellContent
Title="Home"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />
</Shell>
- To be able to refer to the views inside the
./Views
folder, add thexmlns:views
namespace toAppShell
:
<Shell
x:Class="DemoNavigation2.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DemoNavigation"
xmlns:views="clr-namespace:DemoNavigation.Views">
- You can now include views inside this folder, and make use of the
Icon
property to give the flyout menu a nicer look
<ShellContent
Title="Home"
Icon="home.png"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />
<ShellContent
Title="Info"
Icon ="setting.png"
ContentTemplate="{DataTemplate views:InfoPage}"/>
Add two more pages to get this result:
- You should now be able to navigate to each one of these pages as you click on their icons:
UsingFlyoutMenuItem
- Flyout Menu items are simply buttons added to the flyout menu to make certain functionality available everywhere throughout the app
- For example, I added a menu button to toggle between light mode and dark mode:
<MenuFlyoutItem Text="Switch Theme" Clicked="Btn_SwitchTheme_Clicked"/>
- In the
AppShell.xaml.cs
code behind I defined the event handler:
private void Btn_SwitchTheme_Clicked(object sender, EventArgs e)
{
switch (App.Current.UserAppTheme)
{
default:
case AppTheme.Light:
App.Current.UserAppTheme = AppTheme.Dark;
break;
case AppTheme.Dark:
App.Current.UserAppTheme = AppTheme.Light;
break;
}
}
Customizing the FlyoutMenu
:
- You can customize the
Header
andFooter
of your MAUI app to your liking
- Let’s try to add the
dotnet_bot.png
as the header and a horizontal stack layout containing this image and a label saying “I hope you are enjoying this!”:
<Shell.FlyoutHeader>
<!--Some code.. -->
</Shell.FlyoutHeader>
<Shell.FlyoutFooter>
<!--Some code.. -->
</Shell.FlyoutFooter>
Challenge (Easy): Microsoft’s phases of the moon:
- Follow Microsoft .NET MAUI training module: https://learn.microsoft.com/en-us/training/modules/create-multi-page-apps/3-exercise-implement-flyout-navigation to create a flyout menu for their Astronomy app.
Tab Bar navigation
The next navigation style we will learn about is Tab bar navigation:
- This is a permanent horizontal bar that appears generally on the bottom of the screen and holds various icons to navigate to some pages of the app.
- Warning! You might not necessarily need at Tab Bar, they increase the complexity of your app layout.
Tab Bar Design Guidelines
-
Microsoft’s recommendations:
“Use tabs only for the pages that will be frequently used by the user”
“Use tabs only if your app has a few pages of equal importance”
“Use 3-4 tabs to avoid over crowded apps”
“Avoid using tab bars if your data is traversed from general to more specific”
-
Apple’s recommendations:
“Use tab bars to support navigation not provide actions”
“Aim for a few tabs with short titles”
“Keep tabs visible even when their content is unavailable”. Otherwise your app’s interface seems “unstable” (frustrates the user)
Creating a simpleTabBar
- To add a Tab bar simply use the
TabBar
object as a child of theShell
.
Then include ShellContent
pages as children of the TabBar
.
<TabBar>
<ShellContent
Title="Home"
Icon="home.png"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />
<ShellContent
Title="Search"
Icon="search.png"
ContentTemplate="{DataTemplate views:SearchPage}"/>
<ShellContent
Title="Videos"
Icon="video.png"
ContentTemplate="{DataTemplate views:VideosPage}"/>
<ShellContent
Title="Profile"
Icon="user.png"
ContentTemplate="{DataTemplate views:ProfilePage}"/>
</TabBar>
Implicit conversions
-
Note: MAUI actually wraps each
ShellContent
in aTab
object. But due to implicit conversions, the hierarchy seems simplified. In this case:The real hierarchy is:
- Shell
- TabBar
- Tab
- ShellContent
- Tab
- TabBar
Adding sub pages
- Shell
-
You can add multiple
ShellContent
pages under the sameTab
to create “grandchildren” pages<TabBar> <ShellContent Title="Home" Icon="home.png" ContentTemplate="{DataTemplate local:MainPage}" Route="MainPage" /> <ShellContent Title="Search" Icon="search.png" ContentTemplate="{DataTemplate views:SearchPage}"/> <Tab Icon="more.png" Title="New Post"> <ShellContent Title="Select photos" Icon="select.png" ContentTemplate="{DataTemplate views:FilePage}"/> <ShellContent Title="Camera" Icon="camera.png" ContentTemplate="{DataTemplate views:NewPostPage}"/> </Tab> <ShellContent Title="Videos" Icon="video.png" ContentTemplate="{DataTemplate views:VideosPage}"/> <ShellContent Title="Profile" Icon="user.png" ContentTemplate="{DataTemplate views:ProfilePage}"/> </TabBar>
-
This will result in having a hierarchical structure within your pages, in this case the
FilePage
andNewPostPage
are grandchildren of theTabBar
After this step, this is the current hierarchy
Shell
TabBar
MainPage
SearchPage
Tab
FilePage
NewPostPage
VideosPage
Profile
Note: I am omitting the Tabs
added by the implicit conversions
Combining TabBar
and FlyoutMenu
-
The
Shell
can only contain either aFlyoutItem
object or aTabBar
object.- If you wish to combine both you must make the
TabBar
a child of theFlyoutItem
- If you wish to combine both you must make the
-
To combine the flyout menu and the tab bar, each
ShellContent
you wish to display in theFlyout
should be wrapped within aFlyoutItem
instead of theTabBar
and keep theShellContent
you wish to keep in the Flyout as direct children of theShell
<FlyoutItem Title="Home" Icon="home.png">
<ShellContent
Title="Home"
Icon="home.png"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />
<ShellContent
Title="Search"
Icon="search.png"
ContentTemplate="{DataTemplate views:SearchPage}"/>
<Tab Icon="more.png" Title="New Post">
<ShellContent
Title="Select photos"
Icon="select.png"
ContentTemplate="{DataTemplate views:FilePage}"/>
<ShellContent
Title="Camera"
Icon="camera.png"
ContentTemplate="{DataTemplate views:NewPostPage}"/>
</Tab>
<ShellContent
Title="Videos"
Icon="video.png"
ContentTemplate="{DataTemplate views:VideosPage}"/>
<ShellContent
Title="Profile"
Icon="user.png"
ContentTemplate="{DataTemplate views:ProfilePage}"/>
</FlyoutItem>
<ShellContent Title="Groups"
Icon="group.png"
ContentTemplate="{DataTemplate views:GroupsPage}"/>
<ShellContent Title="Info"
Icon="info.png"
ContentTemplate="{DataTemplate views:InfoPage}"/>
<ShellContent Title="Settings"
Icon="setting.png"
ContentTemplate="{DataTemplate views:Settings}"/>
<MenuFlyoutItem Text="Switch Theme" Clicked="Btn_SwitchTheme_Clicked"/>
- This might be counter intuitive at first, but with XAML’s implicit binding the children
ShellContent
are wrapped inTab
s then in aTabBar
The hierarchy is now:
Shell
-
Flyout
-
TabBar
-
MainPage
SearchPage
Tab
FilePage
NewPostPage
-
VideosPage
Profile
-
GroupsPage
InfoPage
Settings
-
Note: I am omitting the Tabs
and the FlyoutItems
implicitly added by the Shell
Challenge (Easy): Microsoft’s Sunrise Page:
- Follow Microsoft .NET MAUI training module: https://learn.microsoft.com/en-us/training/modules/create-multi-page-apps/5-exercise-implement-tab-navigation
Shell Navigation
So far we’ve seen how to access pages from the TabBar
and the FlyoutItem
, we have also seen how to Push
a page into the stack and use the Back button to return to the previous page.
Shell navigation will enable us to navigate to any page using Uri links called routes. This type of navigation is especially useful if you wish to navigate to a particular page without having to visit the entire hierarchy of pages and return to the previous page without having to visit backwards all the pages on the navigation stack.
Routes
- Routes are Uris similar to a website’s uri
- They allow us to navigate through the hierarchy of the app.
- If you inspect the
ShellContent
of theMainPage
, there is a setRoute
attribute.
<ShellContent
Title="Home"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage"/>
- If the
MainPage
was at the Root of the app, you could navigate to this page using:
await Shell.Current.GoToAsync("//MainPage");
- UPDATE: Add the various routes of the pages,…
<FlyoutItem Route="Home" Title="Home" Icon="home.png">
<ShellContent
Route="MainPage"
ContentTemplate="{DataTemplate local:MainPage}"/>
<ShellContent
Route="SearchPage"
ContentTemplate="{DataTemplate views:SearchPage}"/>
<!--Some code...-->
<Tab Icon="more.png" Title="New Post" Route="AddPost">
<ShellContent
Route="FilePage"
ContentTemplate="{DataTemplate views:FilePage}"/>
<!--Some code...-->
<ShellContent
Title="Camera"
Icon="camera.png"
Route="NewPostPage"
ContentTemplate="{DataTemplate views:NewPostPage}"/>
</Tab>
<ShellContent
Route="VideosPage"
ContentTemplate="{DataTemplate views:VideosPage}"/>
<ShellContent
Route="ProfilePage"
ContentTemplate="{DataTemplate views:ProfilePage}"/>
</FlyoutItem>
<ShellContent
Route="GroupsPage"
ContentTemplate="{DataTemplate views:GroupsPage}"/>
<ShellContent
Route="InfoPage"
ContentTemplate="{DataTemplate views:InfoPage}"/>
<ShellContent
Route="SettingsPage"
ContentTemplate="{DataTemplate views:SettingsPage}"/>
Explicit routes Registration in C#
-
Routes can get messy in complex hierarchies, just like for websites
-
We want to avoid as much as possible magic strings in our app, to solve this:
-
Use the name of the page as its route
-
Use the
nameof()
method to register the route in the code behind
In
AppShell.xaml.cs
:public AppShell() { InitializeComponent(); Routing.RegisterRoute(nameof(CommentsPage), typeof(CommentsPage)) // UPDATE: Add other pages that are not part of the Flyout or TabBar... }
-
-
Note: You may define hierarchical routes too at this point using forward slashes in the routes arguments,
Routing.RegisterRoute($"//Home/{nameof(MainPage)}", typeof(MainPage));
Performing Navigation using Shell.Current.GoToAsync()
-
Let’s add a
New Post
button inside theGroupsPage
-
Since the
NewPostPage
is part of theTabBar
, you should use the absolute route to navigate to it (using “//”). Why? What happens if we don’t use “//”private async void Btn_AddPost_Clicked(object sender, EventArgs e) { await Shell.Current.GoToAsync($"//Home/AddPost"); }
Challenge (Easy):
-
I have disabled the Tab Bar inside the the
NewPagePage
:<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="DemoNavigation2.Views.NewPostPage" BackgroundColor="Black" Shell.TabBarIsVisible="False" Title="New Post"> </ContentPage>
- Include an “x” button to cancel the operation of taking a picture and use the event handler of this button to navigate to the main page
- In the event handler, navigate to the main page using absolute routing (“//”) and using the
nameof()
method - Refer to Microsoft’s documentation for the various route formats.
Challenge question (Medium):
What’s the point of using routing to go to the MainPage
? Why not simply PushAsync
the MainPage
?
Challenge (Medium): Navigating to CommentsPage:
-
You’ll notice that the
CommentPage
is not in the Tab Bar hierarchy -
Previously (Lab1) we were simply using
PushAsync
to push a newCommentsPage
everytime. -
Let’s try to reuse the same page and simply navigate to it using
GoToAsync
-
Modify the event handlers in the
MainPage
and theVideosPage
to navigate to thisCommentPage
- Without passing arguments (only passing the route as argument to
GoToAsync()
) - With arguments: provide a
UserId
,ProfilePic
andComments
.
Hint: Use a dictionary<string,object> and QueryProperty
- Without passing arguments (only passing the route as argument to
Challenge (Easy): Using Backward navigation
- You’ll notice that I disabled the
NavBar
and theTabBar
in theCommentPage
- Modify the event handler inside the
CommentsPage
to return to the previous back (regardless of which page it is) - Refer to Microsoft’s documentation for the various route formats.
Passing Arguments to page constructor using GoToAsync
Passing simple data types as strings
or int
can be done using string formatting
await Shell.Current.GoToAsync($"{nameof(MyPage)}?SimpleArgument={12345}");
To pass multiple arguments and complex objects you need to use a dictionary Dictionary<string, object>
:
await Shell.Current.GoToAsync(nameof(MyPage), new Dictionary<string, object> {
{"Arg1", value1},
{"Arg2", value2},
{"Arg3", value3}
});
The page receiving this data must have QueryProperty
and associated public properties:
[QueryProperty(nameof(Property1), "Arg1")]
[QueryProperty(nameof(Property2), "Arg2")]
[QueryProperty(nameof(Property3), "Arg3")]
public partial class MyPage : ContentPage
{
public int Property1 { get; set; }
public string Property2 { get; set; }
public List<int> Property3 {get; set;}
/// Some code...
}