loading...

Xamarin.Forms - Navigation to Page Not Defined in AppShell.xaml

petermilovcik profile image PeterMilovcik ・4 min read

Once you created Xamarin.Forms Shell App, by adapting Blank template or by reducing shell template to bare minimum, you might also need to know how to navigate to content page which is not defined in AppShell.xaml.

Here are steps on how to achieve that:

Add New Page

Let's add new page called OtherView (under the Views directory) which will be content page we will navigate from MainView.

AddNewPage

Change the Label text in created OtherView.xaml so that it's clear that it's different page when we navigate to it:

            <Label
                HorizontalOptions="CenterAndExpand"
                Text="Welcome to Other Page!"
                VerticalOptions="CenterAndExpand" />

Register Route to New Page

Open AppShell.xaml.cs and change it like this:

using Xamarin.Forms;
using XamarinNavigation.Views;

namespace XamarinNavigation
{
    public partial class AppShell : Shell
    {
        public AppShell()
        {
            InitializeComponent();
            RegisterRoutes();
        }

        private void RegisterRoutes()
        {
            Routing.RegisterRoute("other", typeof(OtherView));
        }
    }
}

What we added is private method called RegisterRoutes in constructor of AppShell. Its implementation contains registration of a global route to our new page called OtherView.

For more information, see:

https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/navigation=

Add a Button for Navigation

Add a Button in MainView.xaml.

<StackLayout>
    <Label
        HorizontalOptions="CenterAndExpand"
        Text="Welcome to Xamarin.Forms!"
        VerticalOptions="CenterAndExpand" />
    <Button Text="Press me!" />
</StackLayout>

When we press this button now, nothing happens. This is expected, because we didn't bind it to any command yet.
Let's do that in next step.

Add MainViewModel

Create new class called MainViewModel in ViewModels directory.

AddMainViewModel

namespace XamarinNavigation.ViewModels
{
    public class MainViewModel : BaseViewModel
    {
    }
}

This new class derives from BaseViewModel class, so that we don't have to implement again INotifyPropertyChanged interface again.

Join View and ViewModel

There are multiple ways on how to join View and ViewModel to work together.
Here is one way how to do that:

Adapt MainView.xaml. Add

xmlns:vm="clr-namespace:XamarinNavigation.ViewModels"

to <ContentPage> declaration so that we have reference to ViewModels directory (namespace).

Note, that vm is just name for the declared namespace, you can name it as you like (e.g. viewModels).

and add BindingContext like this:

    <ContentPage.BindingContext>
        <vm:MainViewModel />
    </ContentPage.BindingContext>

the result MainView content page looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    x:Class="XamarinNavigation.Views.MainView"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:d="http://xamarin.com/schemas/2014/forms/design"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="clr-namespace:XamarinNavigation.ViewModels"
    mc:Ignorable="d">
    <ContentPage.BindingContext>
        <vm:MainViewModel />
    </ContentPage.BindingContext>
    <ContentPage.Content>
        <StackLayout>
            <Label
                HorizontalOptions="CenterAndExpand"
                Text="Welcome to Xamarin.Forms!"
                VerticalOptions="CenterAndExpand" />
            <Button Text="Press me!" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

When we press the button now, it still doesn't do anything, but now we have MainViewModel that we can use for creating a Command for binding the button.

Add Command to Button

Change a button in our MainView.xaml like this:

<Button Command="{Binding PressMeCommand}" Text="Press me!" />

We defined Command property on button and bind it to property called PressMeCommand on our MainViewModel view model.

This property doesn't exist yet on view model, so let's create it:

using Xamarin.Forms;

namespace XamarinNavigation.ViewModels
{
    public class MainViewModel : BaseViewModel
    {
        public MainViewModel()
        {
            PressMeCommand = new Command(PressMe);
        }

        public Command PressMeCommand { get; }

        private void PressMe(object obj)
        {
        }
    }
}

The Command implementation comes from defined using Xamarin.Forms.
With this implementation the button is always enabled and when button is pressed, PressMe method is called.
Parameter object obj is optionally used to bind from xaml to pass additional information.
Currently it will be null.
Convension says that we usually name our command properties with suffix Command (e.g. SaveComand, OpenCommand, TapCommand,...)

Navigate to Other Page

Now, when we have implemented a button with MVVM (Model-View-ViewModel) binded to Command and registered OtherView content page in our AppShell.xaml.cs, let's implement actual code used to navigate to this page:

    public class MainViewModel : BaseViewModel
    {
        public MainViewModel()
        {
            PressMeCommand = new Command(async obj => await PressMe(obj));
        }

        public Command PressMeCommand { get; }

        private async Task PressMe(object obj)
        {
            await Shell.Current.GoToAsync("other");
        }
    }

For more information, see this link:

https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/navigation

Let's explain little bit what's going on there:

There are couple of important changes:

  • Implemented PressMe(object obj) method:
await Shell.Current.GoToAsync("other");

"other" parameter to GoToAsync method represents globally defined route for the OtherView page.

  • Changed return value of PressMe(object obj) method to async Task:
private async Task PressMe(object obj)

(Very) simply said, if you awaits some async method (like we did with await Shell.Current.GoToAsync("other");)
you need to change declaration of containing method to async and change return type in case of void to Task.

What it means is that actual navigation to other page will be performed in different thread.

For more information about asynchronous programming in C# see link:
https://docs.microsoft.com/en-us/dotnet/csharp/async

  • The way how PressMeCommand is created is changed and instead of new Command(PressMe) there is new Command(async obj => await PressMe(obj)). Reason for that is that we changed void to Task and added async, so instead of passing delegate parameter we need to pass async lambda. Note, that from asynchronous programming point of view, it's still fire and forget.

Now, if we build and start the application here is the navigation in action:

MainView:

MainViewWithPressMeButton

When you press PRESS ME! button located at the bottom of the page, you will navigate to other view:

OtherView:

OtherViewAfterNavigation

Notice that back buttom is automatically added at the top left corner and it can be used to navigate back to the MainView.

Posted on by:

petermilovcik profile

PeterMilovcik

@petermilovcik

Software engineer passionate about continuous learning and improvement.

Discussion

markdown guide