Better Navigation in Xamarin.Forms

By Jesse Liberty and Eric Grover

There are many ways to navigate from page to page in Xamarin.Forms.  The most obvious is to use the built in navigation service.  However, if you are serious about MVVM, the built in navigation presents quite a problem.

Say I’m on page1 and I want to navigate to page2, from a method in page1ViewModel.  How do I do that? And how do I pass data from Page1ViewModel to page2ViewModel?

Most solutions spend way too much time coupling the VM to the View.  Yuck.

XLabs has an elegant solution.  I use it in conjunction with MVVM Light and it works like a charm.

With XLab navigation, you navigate from Page1ViewModel directly to Page2ViewModel (!) , and Page2 is displayed and tied automagically to its view model.   From Page2 on, you no longer have to set the BindingContext, it is set to the ViewModel without your doing a thing.  That is exactly how navigation should work.

Getting going isn’t obvious at first glance, however, so this posting will walk you through a detailed but simple example.

The Example

We start by creating a Xamarin.Forms application named NavigationSample,  and we set up three folders: Model, View and ViewModel.  In View we create three XAML pages: Page1, Page2 and Page3 and in ViewModel we create the respective view model pages (e.g., Page1ViewModel).

Add the NuGet packages for MVVM Light (libs) and for XLabs Forms.

There is a bit of one time (per application) set up required, and your first page will not have the coolness of all subsequent pages.

One Time Setup

In your platform specific start up (e.g., FinishedLaunching in AppDelegate for iOS) add the following lines:

       var resolverContainer = new global::XLabs.Ioc.SimpleContainer ();
       var myApp = new XFormsAppiOS ();
       myApp.Init (this);
       resolverContainer.Register<IXFormsApp> (myApp)
                        .Register<global::XLabs.Ioc.IDependencyContainer> (t => resolverContainer);
       XLabs.Ioc.Resolver.SetResolver (resolverContainer.GetResolver ());
 
 

Next, open App.xaml.cs and in the constructor, after InitializeComponent, add these lines:

      ViewFactory.Register<Page2, Page2ViewModel> ();
       ViewFactory.Register<Page3, Page3ViewModel> ();

      var page1 = new NavigationPage (new Page1 ());

      var app = Resolver.Resolve<IXFormsApp> ();
      Resolver.Resolve<IDependencyContainer> ()
                .Register<INavigationService> (t => new NavigationService (page1.Navigation));

      MainPage = page1;

InitializeComponent ();

You must add a ViewFactory.Register statement for each page/view model you add.

The rest is one-time boilerplate.

Create the Pages

You are now ready to create your pages.  The first page is special, a bit more conventional work must be done to tie the page to its view model.

Here’s the complete XAML for Page1. Note that the page is a BaseView and thus you need a namespace for XLabs.Forms,

  <?xml version="1.0" encoding="UTF-8"?>
 <toolkit:BaseView
   xmlns="http://xamarin.com/schemas/2014/forms"
   xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
   xmlns:toolkit="clr-namespace:XLabs.Forms.Mvvm;assembly=XLabs.Forms"
   x:Class="NavigationSample.Page1">
   <ContentPage.Content>
     <StackLayout
       Padding="40">
       <Label
         Text="Page 1"
         FontSize="40" />
       <Button
         Text="Go to page 2"
         Command="{Binding GoToPage2Command}" />
     </StackLayout>
   </ContentPage.Content>
 </toolkit:BaseView>

The code-behind on this first page is pretty standard. You declare a ViewModel object and set your BindingContext to that ViewModel.  You do this only on the first page (Page1.xaml.cs

using XLabs.Forms.Mvvm;

namespace NavigationSample
{
  public partial class Page1 : BaseView
  {
    Page1ViewModel vm;
    public Page1 ()
    {
      InitializeComponent ();
      vm = new Page1ViewModel ();
      BindingContext = vm;
    }
  }
}

Notice, again, that you are deriving from XLab’s BaseView. If you want to give yourself more flexibility, create an interim class that derives from BaseView and that your other classes derive from.  This allows you to add functionality as needed, and has the side benefit of removing the need for the toolkit namespace in your XAML (you can just use local).

The button on the first page is bound to GoToPage2Command in Page1ViewModel.

public class Page1ViewModel : XLabs.Forms.Mvvm.ViewModel   {     private Command goToPage2Command;

    public Command GoToPage2Command {
      get {
        return goToPage2Command ?? (goToPage2Command = new Command (
          async () => NavigationService.NavigateTo<Page2ViewModel> (),
                                       () => true));

This navigation will do a few things:

  • It will navigate to Page2ViewModel
  • It will create the binding from Page2 to Page2ViewModel
  • It will cause Page2 to be displayed

 Note that I’m using the standard Command rather than the MVVM Light RelayCommand as I’m tied to a button.  But RelayCommand works brilliantly when handling, for example, a selection from a ListView. It does, however, have a slightly different syntax.

 private RelayCommand<AnObject> myCommand;
public RelayCommand<AnObject> MyCommand {
get {
return myCommand ?? (myCommand = new RelayCommand<AnObject> ((item) => {
NavigationService.NavigateTo<NextPageViewModel> (true, item);
}));

This code sends AnObject to the next page.

 

Page 2 and Forward

The XAML for Page 2 is what you’d expect, modulo that you use a BaseView, as you did in page 1:

  <?xml version="1.0" encoding="UTF-8"?>
 <toolkit:BaseView
   xmlns="http://xamarin.com/schemas/2014/forms"
   xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
   xmlns:toolkit="clr-namespace:XLabs.Forms.Mvvm;assembly=XLabs.Forms"
   x:Class="NavigationSample.Page2">
   <ContentPage.Content>
     <StackLayout>
       <Label
         Text="Page 2"
         FontSize="40" />
       <Label Text="{Binding Name}" /> 
       <Button Text="Go to Page 3" Command="{Binding GoToPage3Command}" /> 
     </StackLayout>
   </ContentPage.Content>
 </toolkit:BaseView>

We create a label to announce that you are on page 2, and we create a label whose text is bound to the Name property in Page2ViewModel. We also supply a button whose job is to take you to Page3

Check out the code behind page (Page2.xaml.cs):

  public partial class Page2 : BaseView
   {
     public Page2 ()
     {
       InitializeComponent ();
     }
   }

That’s the entire class.  Much cleaner than usual, with no reference at all to the view manager, but they are tied together nonetheless.  Page2ViewModel provides the property to bind to and handles the command to go to page 3.

 public class Page2ViewModel : ViewModel
 {
     public string Name { get; set; }

    public Page2ViewModel ()
    {
      Name = Jesse;
    }

We’re simplifying here, not using RaisePropertyChanged and just setting the property in the constructor

Passing Parameters

What if we want to go to page 3, but pass along the name from page 2?  No problem, we add two parameters to the NavigateTo command.  The first is whether or not to use animation, the second is a params argument, allowing us to pass in one or more objects.

      private Command goToPage3Command;

    public Command GoToPage3Command {
      get {
        return goToPage3Command ?? (goToPage3Command = new Command (
          async () => NavigationService.NavigateTo<Page3ViewModel> (true, Name),
                                       () => true));
      }
    }

Unfortunately, the parameter is passed to the constructor of Page3, rather than to the ViewModel.  This means we have to jump through one more hoop, getting the data from the constructor to the VM.

That isn’t terribly hard, (though it is a bit ugly).  We just put the parameter into a field and then override OnBindingContextChanged.  

    public partial class Page3 : BaseView
   {
     private string name;
     private Page3ViewModel vm;

    public Page3 (string name)
    {
      InitializeComponent ();
      this.name = name;
    }

    protected override void OnBindingContextChanged ()
    {
      base.OnBindingContextChanged ();
      vm = this.BindingContext as Page3ViewModel;
      vm.DoSomethingWithName (name);
    }
  }

 

The BindingContext was set up for you, so you can assign your local Page3ViewModel to the object’s BindingContext, casting it to a Page3ViewModel (second line in OnBindingContextChanged).

That’s all there is to it.  Create a UI for Page 3, and create the method DoSomethingWithName in the Page3ViewModel and you’re done (at least for this simple example).  The good news is that this pattern works with not-so-simple examples; in fact it is robust enough to use in production code.

 

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in Essentials, Navigation, Xamarin. Bookmark the permalink.

6 Responses to Better Navigation in Xamarin.Forms

Comments are closed.