Xamarin.Forms - Navigation to Page Not Defined in AppShell.xaml
Tags: xamarin, beginners, shell, navigation
Published: 2020-01-27 15:07:10

Once you created Xamarin.Forms Shell App, by adapting Blank template or by [reducing shell template to bare minimum](https://dev.to/petermilovcik/reduce-xamarin-forms-shell-template-to-bare-minimum-3bmd), 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](https://thepracticaldev.s3.amazonaws.com/i/1mje9fgkuqdovz0v2rgs.png)

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

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

### Register Route to New Page

Open `AppShell.xaml.cs` and change it like this:
```csharp
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`. 

```xml
<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](https://thepracticaldev.s3.amazonaws.com/i/fxjj7qn3n2gr34hqn10e.png)

```csharp
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 

```xml
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:

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

the result `MainView` content page looks like this:

```xml
<?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:

```xml
<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:

```csharp
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:

```csharp
    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:

```csharp
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`:

```csharp
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](https://thepracticaldev.s3.amazonaws.com/i/odbkt1vryai5zbva855w.png)

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

**OtherView**:

![OtherViewAfterNavigation](https://thepracticaldev.s3.amazonaws.com/i/1tvylbtzjwgdhiwz6dff.png)

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