Thinking Out Loud: Mvvm Navigation for XAML Frameworks such as Xamarin.Forms, UWP/WinUI, WPF and Uno

One of the things that’s often given me pause for thought is the approach we take to navigation within applications. For the purpose of this post I’m going limit the scope to just XAML based applications (XF/Maui, UWP/WinUI/Uno, WPF). In all of these application platforms there is a built in capability to navigate between pages. … Continue reading “Thinking Out Loud: Mvvm Navigation for XAML Frameworks such as Xamarin.Forms, UWP/WinUI, WPF and Uno”

One of the things that’s often given me pause for thought is the approach we take to navigation within applications. For the purpose of this post I’m going limit the scope to just XAML based applications (XF/Maui, UWP/WinUI/Uno, WPF). In all of these application platforms there is a built in capability to navigate between pages. However, once we introduce view models, we want to be able to drive navigation from our view models. In this post I’m going to go through a bit of a thought journey to look at some of the existing strategies for view model navigation, discuss the pros and cons, and then look at whether we can build an alternative.

Rather than try to cover all the XAML platforms at every point along the way, I’m going to use a newly created @unoplatform application throughout this post. Towards the end we’ll look at whether we can adapt what we’ve created to the other XAML platforms – if we’ve done the job well, it should be a relatively easy thing to do.

Basic Page Navigation

As part of creating our project, MvvmNavigation, we’re given a MainPage, which is clearly the starting point of our application. To begin with, let’s:

  • Create another page, SecondPage
  • Add a Button to MainPage and an event handler that will simply navigate to SecondPage
  • Add a Button to SecondPage and an event handler to navigate back to MainPage
  • Add a TextBlock to both pages to give the pages a title

The XAML for MainPage

<Page x:Class="MvvmNavigation.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:MvvmNavigation"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="Main Page"
                   Margin="100"
                   FontSize="50"
                   HorizontalAlignment="Center" />
        <Button Content="Go to Second Page"
                Click="GoToSecondPageClick"
                VerticalAlignment="Bottom"
                HorizontalAlignment="Center"
                Margin="100" />
    </Grid>
</Page>

The GoToSecondPageClick event handler in the codebehind of MainPage

private void GoToSecondPageClick(object sender, RoutedEventArgs e)
{
    Frame.Navigate(typeof(SecondPage));
}

The XAML for SecondPage

<Page x:Class="MvvmNavigation.SecondPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:MvvmNavigation.Shared"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d"
      Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="Second Page"
                   Margin="100"
                   FontSize="50"
                   HorizontalAlignment="Center" />
        <Button Content="Go back to Main Page"
                Click="GoBackClick"
                VerticalAlignment="Bottom"
                HorizontalAlignment="Center"
                Margin="100" />
    </Grid>
</Page>

The GoBackClick event handler in the codebehind of SecondPage

private void GoBackClick(object sender, RoutedEventArgs e)
{
    Frame.GoBack();
}

And final a screenshot of each page that we’re navigating between.

What we can see from this example is that in a UWP/Uno application, navigating between pages is done by calling navigation methods on the Frame. As an aside, a UWP application can not only support multiple Windows, it can also support multiple frames. This can be very handy if you’re building complex desktop applications where you want to be able to control multiple page stacks. For the moment we’ll keep things simple and just consider a single window, single frame application.

ViewModel Navigation

Let’s augment our example by adding in some view models. To ensure we don’t pollute our view models with UI/framework specific code, we’ll add our view models to a separate .NET Standard class library, MvvmNavigation.Core. In this case I’ll create a view model for each page we currently have.

public class MainViewModel
{
    public string Title { get; } = "Main Page - VM";
}

public class SecondViewModel
{
    public string Title { get; } = "Second Page - VM";
}

One thing you’ll notice about a lot of mvvm frameworks is that they all seem to have their own base class. Where possible, I’m going to try to avoid this, in order to keep our view models as simple as possible. If you want to have bindings update the viewmodel will need to implement INotifyPropertyChanged, so having a base class with that implementation is something you’ll want to add. For the moment we’ll keep the view models as simple as possible.

Now let’s connect our view models to our pages. We’ll keep this really simple by creating the view models inline in the page as part of setting the DataContext. We’ll also update the Text on the TextBlock to bind to the Title property on the view model.

<Page x:Class="MvvmNavigation.MainPage" ... >
    <Page.DataContext>
        <vms:MainViewModel />
    </Page.DataContext>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{Binding Title}" ... />
    ...
</Page>

With the basic setup done, we can start to think about navigation. Firstly, let’s set the scene as to why the current navigation (i.e. controlled invoked in the code behind of the page) is a problem. In our MainViewModel, let’s say we have a method that does something, DoSomething.

public async Task<int> DoSomething()
{
    var rnd = new Random().Next(1000);
    await Task.Delay(rnd);
    if (rnd % 2 == 0)
    {
        // Do one thing
    }
    else
    {
        // Do something else
    }
    return rnd;
}

After delaying for a random amount of time, it decides to do one of two things. Let’s assume that if the random number is even, the app should navigate to SecondPage. However, currently the view model doesn’t have a way to do this. We could augment the return value to return a flag to indicate that it should navigate. Alternatively, we could move the decision logic into the code behind. I don’t like either of these options and it would be much clearer if the view model had a way to invoke navigation itself.

This leads us to a quick discussion on how this is currently implemented by different frameworks. Typically the solution is to pass in a service, call it something like INavigationService, into the constructor for the view model. When the view model wants to navigate it calls a method, such as Navigate, on the service. Depending on which framework it is, the parameter might be a url or it could be the type of view model to navigate to (i.e. navigate to the page that is associated with the specified view model type).

My issue with this approach is two fold. Firstly, we’ve added a dependency to the view model that doesn’t really assist the view model with what it needs to do (controlling the state for the view; loading and updating data that’s data bound to the UI etc). Secondly, we’ve added in an implicit linkable between the current view model and the view model that’s being navigated to.

If I had to pick between navigating using a url versus the type of a view model, my preference is the type of a view model, purely because this can be enforced with type safety. However, my point is that in both cases there’s an implied link that’s only visible if you walk the code of the view model.

Event Based Navigation

Instead of using an injected navigation service, what if we simply expose an event on the view model for when we need navigation to take place. For example, our MainViewModel could look like

public class MainViewModel
{
    public event EventHandler ViewModelDone;
    public async Task<int> DoSomething()
    {
        var rnd = new Random().Next(1000);
        await Task.Delay(rnd);
        if (rnd % 2 == 0)
        {
            ViewModelDone?.Invoke(this, EventArgs.Empty);
        }
        ... 
    }
}

Note that I didn’t call the event NavigateToSecondPage because this would again start to build in that implicit linkage between the view models/pages. The point is that the MainViewModel doesn’t, and shouldn’t, care about what’s going to happen next, it just knows that it’s time is up and it’s job is done.

The only thing now, is that we need something to listen to the event. For the moment this will be the MainPage:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    (DataContext as MainViewModel).ViewModelDone += MainViewModelDone;
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    (DataContext as MainViewModel).ViewModelDone -= MainViewModelDone;
    base.OnNavigatedFrom(e);
}

private void MainViewModelDone(object sender, EventArgs e)
{
    Frame.Navigate(typeof(SecondPage));
}

private void GoToSecondPageClick(object sender, RoutedEventArgs e)
{
    (DataContext as MainViewModel)?.DoSomething();
}

Whilst we’ve kept our MainViewModel clear of any dependency/relationship with SecondPage or SecondViewModel, what we’ve ended up with is a bunch of wiring and navigation logic in our MainPage.

Abstracting Navigation Logic

Let’s step away from the specifics of how events and navigation logic works and think about what we want to achieve. Essentially we need a way of defining a set of behaviours such that when an event is raised on a particular object, the application will navigate to a particular page. So, for each behaviour we want to define, we need a way to declare the event that is going to be listened for and the action that should take place when that event is triggered.

The complexity in abstracting the navigation logic comes when you attempt to declare these behaviours at an application level. At a page level you can simply attach and detach event handlers, similar to what we’ve just done with MainPage in the OnNavigatedTo and OnNavigatedFrom methods. At an application level, you don’t have the instances of the pages until the user has navigated to them. As such, we’ll need to hook into the navigation event on the Frame and wire/unwire the event handlers.

Here’s a summary of the pieces we need:

  • Behaviour class – this will define the event to listen to and the action to execute
  • NavigationRoutes class – this will hold the list of behaviours for the application
  • OnNavigation method – this is the event handler for the Navigated event on the root frame of the application

Let me try to walk through these in a way that hopefully makes sense. We’ll start with the behaviour class.

public class Behaviour:IApplicationBehaviour
{
    public Action<object> Init { get; set; }
    public Action<object> Deinit { get; set; }

    private Behaviour() { }

    public static Behaviour Create<T, THandler>(Action<T, THandler> init, Action<T, THandler> deinit, THandler action)
    {
        return new Behaviour()
        {
            Init = obj => init((T)obj, action),
            Deinit = obj => deinit((T)obj, action)
        };
    }
}

public interface IApplicationBehaviour
{
    Action<object> Init { get; set; }
    Action<object> Deinit { get; set; }
}

According to what I said earlier, the behaviour class needs to define an event and an action, yet the implementation doesn’t really look like it has either. It does, they’re just masked a little.

Firstly, the challenge with an event in C# is that you can’t easily just pass the event object around and attach/detach from it. We could have just switched it out for an Action or some other delegate but that would make the programming model for the view model just a bit weird. Alternatively we could have used some hocky reflection to get the job done but … well just, no… I don’t want to be doing reflection due to the inherit overhead and potential linker issues it might create.

Instead of passing an event object around, we instead pass in an Action for wiring and unwiring the event. For example the wiring action would look something like (vm, act) => vm.ViewModelDone += act.

Next, we need the action that’s going to be invoked. Well this is easily identifiable in the Create method as the third parameter but then what do we do with it? Rather than hold a direct reference to the action, we simply combine it with the init and deinit Action delegates and assign them to the public Init and Deinit methods.

Initially this looks a bit strange as the signature on the Init and Deinit methods are Action<object> instead of Action<T>, and then we’re assigning a delegate that coerces the object into T – why not just make them Action<T>? The answer to this comes if you look at the interface IApplicationBehaviour that defines Init and Deinit methods, both as Action<object>. As you’ll see shortly, we’re going to have a collection of these behaviours defined for a variety of different pages (i.e. T). Rather than attempt to keep these as strongly typed behaviours, we’re simply going to use an interface which doesn’t include the type argument.

You might be wondering whether the type coercion is safe? Well the short answer is no, it might not be. However, as you’ll see shortly, one of the reasons why we don’t expose either a public constructor on the Behaviour class, nor expose the collection of Behaviours directly is to ensure that this type coercion is always valid.

Next up is the NavigationRoutes class, which is essentially just a wrapper around a collection of Behaviour instances.

public class NavigationRoutes
{
    private IList<Tuple<Type, IApplicationBehaviour>> Behaviours { get; } = new List<Tuple<Type, IApplicationBehaviour>>();

    public NavigationRoutes Register<T, THandler>(Action<T, THandler> init, Action<T, THandler> deinit, THandler action)
    {
        Behaviours.Add(new Tuple<Type, IApplicationBehaviour>(typeof(T), Behaviour.Create(init, deinit, action)));
        return this;
    }

    public void Wire(object page)
    {
        var typeOfPage = page.GetType();
        var behaviours = Behaviours.Where(x => x.Item1 == typeOfPage).Select(x=>x.Item2);
        foreach (var behaviour in behaviours)
        {
            behaviour.Init(page);
        }
    }

    public void Unwire(object page)
    {
        var typeOfPage = page.GetType();
        var behaviours = Behaviours.Where(x => x.Item1 == typeOfPage).Select(x => x.Item2);
        foreach (var behaviour in behaviours)
        {
            behaviour.Deinit(page);
        }
    }
}

You might be wondering why we didn’t just use a Dictionary. The answer is so that we can support multiple Behaviour definitions per page. I’m also certain there are more optimal ways to structure the Wire and Unwire methods but they’ll do for what we need right now. They simply iterate through the collection of behaviours and invoke the Init or Deinit method on behaviours that are registered for the specified page.

We also need to define an instance of the NavigationRoutes class, which for the moment we’ll place in App.xam.cs.

public NavigationRoutes Routes { get; } = new NavigationRoutes()
    .Register<MainViewModel, EventHandler>(
                (vm, act) => vm.ViewModelDone += act,
                (vm, act) => vm.ViewModelDone -= act,
                (s, e) => (Window.Current.Content as Frame).Navigate(typeof(SecondPage)))
    .Register<SecondViewModel, EventHandler>(
                (vm, act) => vm.ViewModelDone += act,
                (vm, act) => vm.ViewModelDone -= act,
                (s, e) => (Window.Current.Content as Frame).GoBack());

As you can see from this code we’re registering the ViewModelDone on both MainViewModel and SecondViewModel. For the MainViewModel we’re registering an Action that navigates to SecondPage. For the SecondViewModel we’re registering an Action that calls GoBack on the Frame.

The last thing to do is to intercept the Navigated event on the Frame in order to wire/unwire the behaviours.

private object PreviousPage { get; set; }
private void OnNavigated(object sender, NavigationEventArgs e)
{
    if (PreviousPage != null)
    {
        Routes.Unwire((PreviousPage as Page).DataContext);
        PreviousPage = null;
    }

    PreviousPage = e.Content;
    Routes.Wire((PreviousPage as Page).DataContext);

}

Here you can see why we needed the Init and Deinit methods on the IApplicationBehaviour interface to be Action<object> – The Content property on the NavigationEventArgs is simply an object which we are casting it to a Page and extracting the DataContext, which is also untyped. Note that again, this is very rough at this point and obviously needs some null checking before it would be considered fit for use in production.

Testing Navigation Logic

Whilst we’ve successfully abstracted the navigation logic away from the view models, it’s still very closely tied to platform implementation. By this I mean that the actions we’ve registered rely on having a reference to the Frame and they specify the actual navigation action to carry out eg Navigate(typeof(MainPage)) or GoBack. If we wanted to write tests to validate our navigation logic, we can’t easily, unless they were UI tests that literally tested the behaviour of the application (I’m not saying these aren’t important, they’re just not something we can write as unit tests).

The next step in our journey is to put in place an abstraction of what we mean by navigation so that we can replace the Navigate and GoBack method calls with something that can be tested. If we look to existing mvvm frameworks for suggestions, we’ll remember that they typically pass a navigation service into the viewmodels. What if we do something similar – we create a navigation service interface that can be passed into our Actions. Of course, this will then require a per-platform implementation for how each navigation method should be carried out.

This sounds simply but the first issue we’ll come up against is that the Navigate method for UWP takes a type parameter being the type of the page to navigate to. If we’re abstracting navigation away from the UI platform, we need to define navigation based on types that aren’t associated with any UI platform (i.e. not MainPage or SecondPage). The logical conclusion is that in order to navigate to SecondPage, we should simply attempt to navigate to SecondViewModel and let the platform implementation worry about how to translate this to SecondPage.

Let’s start by defining the interface for our INavigationService. At this stage I’m not going to place any type constraints on the TViewModel since one of our goals is to avoid imposing a particular base class or interface on the view models we define.

public interface INavigationService
{
    Task Navigate<TViewModel>();
    Task GoBack();
}

Next up, the implementation of the NavigationService for Windows (UWP/Uno).

public class WindowsNavigationService:INavigationService
{
    private Frame NavigationFrame { get; }
    private NavigationRoutes Routes { get; }
    public IDictionary<Type, Type> ViewModelToPageMap { get; } = new Dictionary<Type, Type>();

    public WindowsNavigationService(Frame navigationFrame, NavigationRoutes routes)
    {
        NavigationFrame = navigationFrame;
        NavigationFrame.Navigated += OnNavigated;
        Routes = routes;
    }

    public async Task GoBack()
    {
        NavigationFrame.GoBack();
    }

    public async Task Navigate<TViewModel>()
    {
        NavigationFrame.Navigate(ViewModelToPageMap[typeof(TViewModel)]);
    }

    public WindowsNavigationService RegisterForNavigation<TPage, TViewModel>() where TPage : Page
    {
        ViewModelToPageMap[typeof(TViewModel)] = typeof(TPage);
        return this;
    }

    private object PreviousPage { get; set; }
    private void OnNavigated(object sender, NavigationEventArgs e)
    {
        if (PreviousPage != null)
        {
            Routes.Unwire((PreviousPage as Page).DataContext);
            PreviousPage = null;
        }

        PreviousPage = e.Content;
        Routes.Wire(this,(PreviousPage as Page).DataContext);
    }
}

There’s three main sections to the implementation. Firstly, as you’d expect the constructor is expecting a Frame, which will be used to implement the Navigate and GoBack methods. In order to call Navigate on the Frame there is a translation between the TViewModel type parameter on the Navigate method to the type of Page to navigate to. This is done by looking up the ViewModelToPageMap dictionary. This leads us to the second section, which is the RegisterForNavigation method that simply adds a mapping into the ViewModelToPageMap dictionary. It might seem weird that we’re returning the instance of the WindowsNavigationService but this is simply to allow for a more fluent way of calling the RegisterForNavigation method, which we’ll see shortly.

The last section of the implementation is actually code we’ve seen before, which simply wires and unwires the behaviours as the Frame navigates between pages. This was previously in the App.xaml.cs but makes sense to be incorporated into the WindowsNavigationServices.

In the App.xaml.cs, rather than attaching an event handler to the Navigated event on the rootFrame, we’re instead going to create an instance of the WindowsNavigationService and call the RegisterForNavigation method for each page/view model pair we have.

rootFrame = new Frame();

rootFrame.NavigationFailed += OnNavigationFailed;
var navService = new WindowsNavigationService(rootFrame, Routes)
    .RegisterForNavigation<MainPage, MainViewModel>()
    .RegisterForNavigation<SecondPage, SecondViewModel>();

The last thing we need to do is to change the implementation of the NavigationRoutes class in order to leverage the INavigationService. So far as part of registering each behaviour we’ve avoided placing any constraints on the event or the action that’s passed into the Register method. The type of the event in the init and deinit methods (i.e. THandler) simply has to match the type of the Action. The challenge with wanting to pass in the INavigationService is that we risk enforcing some sort of constraint on THandler. For example we could require THandler to inherit, or be an instance of, EventHandler<INavigationService>. If we did this, all of a sudden we’re leaking the concept of navigation back into our view model.

The work around for this is that instead of passing in an Action into the register method, we’re going to pass a function that accepts an INavigationService and returns an Action. Essentially this is a sneaky way for us to wrap an instance of the INavigationService in a way that makes it accessible to the Action, without affecting the signature of the Action itself. You can see from the following code that the signature of the event and Action is still EventHandler.

private NavigationRoutes Routes { get; } = new NavigationRoutes()
    .Register<MainViewModel, EventHandler>(
                (vm, act) => vm.ViewModelDone += act,
                (vm, act) => vm.ViewModelDone -= act,
                (nav) => (s, e) => nav.Navigate<SecondViewModel>())
    .Register<SecondViewModel, EventHandler>(
                (vm, act) => vm.ViewModelDone += act,
                (vm, act) => vm.ViewModelDone -= act,
                (nav) => (s, e) => nav.GoBack());

Now that I’ve given the endgame away, let’s take a quick look at the implementation. Firstly, the Behaviour implementation has changed. Due to an increase in complexity, it now makes sense to capture the init, deinit and actionFactory parameters. The public interface for IApplicationBehaviour has also been updated.

public class Behaviour<T, THandler> :IApplicationBehaviour
{
    private Action<T, THandler> Init { get; set; }
    private Action<T, THandler> Deinit { get; set; }

    private Func<INavigationService, THandler> ActionFactory { get; set; }

    private T attachedEntity;
    private THandler action;

    public void Attach(INavigationService navService, object entity)
    {
        if(attachedEntity!=null)
        {
            Detach();
        }

        action = ActionFactory(navService);
        attachedEntity = (T)entity;
        Init(attachedEntity, action);
    }

    public void Detach()
    {
        if (attachedEntity == null)
        {
            return;
        }

        Deinit(attachedEntity, action);
    }

    private Behaviour() { }

    public static Behaviour<T, THandler> Create(Action<T, THandler> init, Action<T, THandler> deinit,Func<INavigationService, THandler> actionFactory)
    {
        return new Behaviour<T, THandler>()
        {
            Init = init,
            Deinit = deinit,
            ActionFactory=actionFactory
        };
    }
}

public interface IApplicationBehaviour
{
    void Attach(INavigationService navService, object entity);
    void Detach();
}

Then there’s the implementation of NavigationRoutes

public class NavigationRoutes
{
    private IList<Tuple<Type, IApplicationBehaviour>> Behaviours { get; } = new List<Tuple<Type, IApplicationBehaviour>>();

    public NavigationRoutes Register<T, THandler>(Action<T, THandler> init, Action<T, THandler> deinit, Func<INavigationService,THandler> actionFactory)
    {
        Behaviours.Add(new Tuple<Type, IApplicationBehaviour>(typeof(T), Behaviour<T,THandler>.Create(init, deinit, actionFactory)));
        return this;
    }

    public void Wire(INavigationService navigationService, object page)
    {
        var typeOfPage = page.GetType();
        var behaviours = Behaviours.Where(x => x.Item1 == typeOfPage).Select(x=>x.Item2);
        foreach (var behaviour in behaviours)
        {
            behaviour.Attach(navigationService, page);
        }
    }

    public void Unwire(object page)
    {
        var typeOfPage = page.GetType();
        var behaviours = Behaviours.Where(x => x.Item1 == typeOfPage).Select(x => x.Item2);
        foreach (var behaviour in behaviours)
        {
            behaviour.Detach();
        }
    }
}

The main change here is really the Register method signature and that we’re calling Attach and Detach instead of Init and Deinit.

Ok, so we have the basics of a platform agnostic navigation protocol (I’m trying to avoid the overloaded use of framework – what we’ve built is just a couple of classes that help abstract navigation, it’s definitely not a framework…. just yet).

At the beginning I stated that we’d look at the implementation for other platforms but rather than draw this post out any further, let’s list a couple of things for a future post that we need to look at:

  • Implementation of INavigationService for Xamarin.Forms, WPF and WinUI (UWP and/or Desktop)
  • Passing parameters as part of navigation
  • Returning values as part of navigating back
  • Multi-window and potentially multi-region support

Would love feedback on this – check out the repo on github: https://github.com/nickrandolph/mvvmnavigation

MVVM Navigation with Xamarin.Forms Shell – Part II

Following my previous post on Mvvm Navigation with Xmarin.Forms Shell there were a few things that I felt I hadn’t addressed adequately. Loading Data on Appearing The first thing is how to load data when navigating to a page, and perhaps to do something when the user navigates away from the page. Since it’s the … Continue reading “MVVM Navigation with Xamarin.Forms Shell – Part II”

Following my previous post on Mvvm Navigation with Xmarin.Forms Shell there were a few things that I felt I hadn’t addressed adequately.

Loading Data on Appearing

The first thing is how to load data when navigating to a page, and perhaps to do something when the user navigates away from the page. Since it’s the responsibility of the viewmodel to provide the data (i.e. via data binding), we have to invoke a method on the corresponding viewmodel when arriving at a page. In Xamarin.Forms the navigation to/from a page invokes the OnAppearing and OnDisappearing methods, which we can use to request that the viewmodel loads data.

The simplest approach is that for each page that needs to load data, the developer can override the OnAppearing method and simply call a method, for example LoadData, on the corresponding viewmodel. Since most pages are likely to have to load some data, this will quickly become a bit of a drag and something we can easily optimise. We’ll introduce an interface, INavigationViewModel, that when implemented by a viewmodel will define methods OnAppearing and OnLeaving. Then in our BasePage (which we introduced in the previous post) we simply need to check to see whether a viewmodel implements the interface before invoking the appropriate method.

public class BasePage : ContentPage
{
    protected override void OnAppearing()
    {
        base.OnAppearing();

        var vm = this.BindingContext as BaseViewModel;
        if (vm!=null && AppShell.Maps.TryGetValue(vm.GetType(), out var maps))
        {
            foreach (var map in maps)
            {
                map.Wire(vm);
            }
        }

        (vm as INavigationViewModel)?.OnAppearing();
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        var vm = this.BindingContext as BaseViewModel;
        if (vm!=null && AppShell.Maps.TryGetValue(vm.GetType(), out var maps))
        {
            foreach (var map in maps)
            {
                map.Unwire(vm);
            }
        }

        (vm as INavigationViewModel)?.OnLeaving();
    }
}

Note: If you’re going to be adapting some of this code for your project, you might want to consider making OnAppearing/OnLeaving return a Task that can be awaited.

Passing Parameter

The next point that we need to cover is how to pass a parameter from one page to the next. In our example, we have a list of items and when the user taps on an item the app navigates to the ItemDetailPage to view the details of the item. This requires some information about the selected item to be passed to the ItemDetailPage.

I’ve seen all sorts of mechanisms for passing data between pages across various application platforms. Some platforms only allow a simple query string to be passed as part of navigating between pages, whilst others allow you to pass entire objects. In the case of Xamarin.Forms the default navigation pattern doesn’t provide for a mechanism to pass data. However, because you create the instance of the new page before navigating to it, there’s a perfect opportunity to pass data from the existing page to the new page.

The following code is adapted from the code in the previous post to illustrate wiring up navigation to the ItemDetailPage based on the SelectedItemChanged event on the ItemsViewModel. Note that the selected item, passed into the event handler as the variable args, is set on the Item property of the ItemDetailViewModel.

// When the SelectedItemChanged event is raised on the ItemsViewModel
// navigate to the ItemDetailPage, passing in a new ItemDetailViewModel
// with the selected item
Maps.For<ItemsViewModel>()
    .Do(new EventHandler<Item>(async (s, args) =>
    {
        if (args == null)
        {
            return;
        }
        var page = new ItemDetailPage();
        if (page.BindingContext is ItemDetailViewModel vm)
        {
            vm.Item = args;
        }
        await Navigation.PushAsync(page);
    }))
    .When((vm, a) => vm.SelectedItemChanged += a, (vm, a) => vm.SelectedItemChanged -= a);

Note: Setting a property on the viewmodel will occur before the OnAppearing method is invoked (see previous discussion regarding loading data) which means the viewmodel can make use of whatever data you pass in when loading data. In this case we could have simply passed in the Id of the item we want to display and have the viewmodel load the rest of the data related to that item.

Returning a Parameter

One thing I’ve seen in a number of MVVM frameworks is the ability to navigate to a new page with the expectation that the page will return data at some point in the future. Whilst there are cases where this is convenient (eg prompting the user for some data) this pattern introduces a very heavy dependency between the lifecycle of two pages and their corresponding viewmodels.

An alternative is to assume that each page/viewmodel is independent and that when you arrive at a page any data that is displayed on the page should be refreshed. Of course, if you’ve got a long list of items that the user has scrolled mid-way down and you reload the list, it’s going to return to the top of the list, making the user experience quite nasty. Furthermore, if the list of items is coming from a service, you don’t want to be reloading that data every time the user goes back and forth to a details page.

Most of the above issues can be handled with appropriate caching of data in a service or manager class. If the user navigates to an item, makes changes and saves that item, the cached data in the service would be updated (along with any call required to any backend services). When the user returns to the list of items, the service would simply return the latest cached data. This addresses the latency issue of having to fetch data from a backend service but how do we address the scroll position issue?

One way is to only update the items in the list that have changed (ie catering for add, edit, delete of items). However, having to write this code for every page is again tiresome. In the past, I investigated a possible workaround for this issue when I discussed immutable data – check out posts I, II and III on working with immutable data

Summary

Again, the disclaimer here is that this isn’t a library that you can just drop in and use in your application. However, feel free to take/leave what code you find useful.

Get the latest source code

Navigate Flutter Apps with Routes

One of the most important aspects of an app is the flow or journey that the user takes through the app. Apps are often described in terms of pages, or screens, and navigating between them. In this post I’m going to cover dividing your application into routes, and how to work with the Flutter navigation … Continue reading “Navigate Flutter Apps with Routes”

One of the most important aspects of an app is the flow or journey that the user takes through the app. Apps are often described in terms of pages, or screens, and navigating between them. In this post I’m going to cover dividing your application into routes, and how to work with the Flutter navigation system.

Whilst a Flutter app is constructed out of widgets, there still needs to be a mechanism for the app to respond to user interaction. For example, tapping on an row in a list of items might display the details of that item. Subsequently tapping on the back button should return the user to the list of items. In order to support navigating between different pages, Flutter includes the Navigator class. According to the documentation the Navigator class is:

 A widget that manages a set of child widgets with a stack discipline. 

For someone who’s just got their head around how to layout widgets, this statement makes no sense. In this post we’re going to ignore this definition and cover the basics of how to navigate between pages. I’m also going to ignore the Flutter definition of a Route. For now we’ll assume that a route is roughly equivalent to a page or a screen in your app. As you can imagine, all but very basic apps have multiple pages that the user is able to navigate between. Flutter apps are no different except that you navigate Flutter apps with routes.

Flutter Navigation

Let’s get into navigating between routes. We’ll start with almost the most basic Flutter app you can create. Technically you can create a Flutter app that doesn’t start with the MaterialApp but then you’re left doing a lot of heavy lifting yourself. The following code sets up a basic app that is comprised of two classes that inherit from StatelessWidget. I could have combined these into a single widget by simply setting the value of the home attribute in the MaterialApp to be the new Container widget. However, I’ve created a separate widget, FirstNoRoutePage, to help make it easy to see how the pages of the app are defined.

void main() => runApp(MyNoRouteNavApp());

class MyNoRouteNavApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FirstNoRoutePage(),
    );
  }
}

class FirstNoRoutePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: Text('First Page'),
    );
  }
}

Ad-Hoc Routes with Navigator.push()

Now that we have the basic app structure, it’s time to navigate to our first route. We’re going to use the Navigator widget to push a newly created, or ad-hoc, route onto the stack. The following code:

  • Wraps the Text widget in a FlatButton
  • The onPressed calls the push method on the Navigator, passing in a newly constructed MaterialPageRoute.
  • The MaterialPageRoute builder is set to return a new instance of the SecondNoRoutePage widget
class FirstNoRoutePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: FlatButton(
        child: Text(
          'First Page',
          style: TextStyle(color: Colors.white),
        ),
        onPressed: () {
          Navigator.push(
              context,
              MaterialPageRoute(
                builder: (BuildContext context) => SecondNoRoutePage(),
              ));
        },
      ),
    );
  }
}

class SecondNoRoutePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: Text(
        'Second Page',
        style: TextStyle(color: Colors.white),
      ),
    );
  }
}

We now have an application that has two pages that the user can navigate between. On the first page, clicking on the “First Page” text will push a new route that shows the second page (i.e. the SecondNoRoutePage widget). Pressing the device back button (Android only) will navigate the user back to the first page. It does this by popping the route off the stack maintained by the Navigator.

Go Back with Navigator.pop()

For the purpose of this post I’ve kept the pages simple, showing only elements necessary to show which page the user is on, or to allow the user to perform an action. I’ve chosen not to use a Scaffold, which means no AppBar. This also means no back button shown in the AppBar. On iOS this makes it impossible for the user to navigate to the previous page as there is no dedicated back button, unlike Android. If you use a Scaffold in your widget, you’ll see the AppBar. The AppBar adjusts to include a back button if there’s more than one route on the stack.

Flutter provides built in support for navigating back to the previous route via the AppBar back button or, in the case of Android, the device back button. In addition, the pop method on the Navigator can be used to pop the current route off the stack. This will return the user to the previous route.

class SecondNoRoutePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: FlatButton(
        child: Text(
          'Second Page',
          style: TextStyle(color: Colors.white),
        ),
        onPressed: () {
          Navigator.pop(context);
        },
      ),
    );
  }
}

What the Current Route?

Earlier in the post we created a new route when navigating to the second page of the app. However, what we glossed over is the fact that the first page of the app is also a route. To show this, let’s add some code that adds the name of the current route to both pages. This will highlight where in the Flutter navigation stack the user is currently at.

class FirstNoRoutePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var route = ModalRoute.of(context).settings.name;
    return Container(
      alignment: Alignment.center,
      child: FlatButton(
        child: Text(
          'First Page - $route',
          style: TextStyle(color: Colors.white),
        ),
        onPressed: () {
          Navigator.push(
              context,
              MaterialPageRoute(
                builder: (BuildContext context) => SecondNoRoutePage(),
              ));
        },
      ),
    );
  }
}

class SecondNoRoutePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var route = ModalRoute.of(context).settings.name;
    return Container(
      alignment: Alignment.center,
      child: FlatButton(
        child: Text(
          'Second Page - $route',
          style: TextStyle(color: Colors.white),
        ),
        onPressed: () {
          Navigator.pop(context);
        },
      ),
    );
  }
}

What gets displayed on the two pages are ‘First Page – /’ and ‘Second Page – null’. This indicates that the first route is assigned a name of ‘/’, whilst the second route does not have a name (i.e. null value). The first route is created from the home property being set on the MaterialApp. It is assigned a name equal to the defaultRouteName constant from the Navigator class (i.e. ‘/’) to indicate it is the entry point for the application.

As we created the route for the second page, it’s our responsibility to name the route. In the above scenario we’re creating the route at the point where it is required (i.e. when navigating to the second page), and as such we don’t need to give it a name. That is, unless we want to see that the current route is by inspecting it’s name. Let’s update the call to Navigator.push to include a name for the second route.

onPressed: () {
  Navigator.push(
      context,
      MaterialPageRoute(
        builder: (BuildContext context) => SecondNoRoutePage(),
        settings: RouteSettings(name: '/second')
      ));
},

There’s no requirement for the route name to include the ‘/’. However, it is a convention to do so, and there are some scenarios where the ‘/’ has some implied behaviour.

Named Routes in Flutter Navigation

In the previous section we added a name to the ad-hoc route that we created. An alternative to creating routes as they’re required, is to define the routes for the app up front. For example the following code shows three routes that have been defined for an app and can be used for Flutter navigation.

class Routes {
  static const String firstPage = '/';
  static const String secondPage = '/second';
  static const String thirdPage = '/third';
}

class MyNavApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        Routes.firstPage: (BuildContext context) => FirstPage(),
        Routes.secondPage: (BuildContext context) => SecondPage(),
        Routes.thirdPage: (BuildContext context) => ThirdPage(),
      },
    );
  }
}

The Routes class includes the names of each of the three routes. Inside the constructor of the MaterialApp widget the three routes have been declared. Each route is an association between the route name and the builder method that’s used to construct the widget.

Navigate.pushNamed()

When the user clicks the button, the Navigate.pushNamed method is used to navigate to the corresponding route:

onPressed: () {
  Navigator.pushNamed(context, Routes.secondPage);
},

Skipping Over Routes with Navigator.popUntil

Using named routes not only makes it easier to manage the pages in your app, it also means that you can create conditional logic that is dependent on the route name. For example, the Navigator.popUntil allows you to keep popping routes off the stack until the predicate is true. In the following code the predicate looks at the name property of the route to determine whether it is the first page of the app.

class ThirdPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: FlatButton(
        child: Text(
          'Third Page',
          style: TextStyle(color: Colors.white),
        ),
        onPressed: () {
          Navigator.popUntil(context, (r) => r.settings.name == Routes.firstPage);
        },
      ),
    );
  }
}

Setting the Initial Route

Initially the first page of the app was defined by setting the home property. This was used to define the initial route for the app. When the app was updated to use a set of predefined routes, the initial route was inferred by looking for the route where the name matches the defaultRouteName (i.e. ‘/’). If there wasn’t a route with that name isn’t found, an error will be raised and the app will fail to start.

It is also possible to set the initialRoute property on the MaterialApp. However, this doesn’t negate the need to set either the home property, or have a route declared with a name of ‘/’. What’s really interesting about the initialRoute property is that it can be used to launch the app on a route that has other routes in the navigation stack. For example in the following code the initialRoute is set to the thirdPage, which is defined as ‘/second/third’. When this app launches, it will launch showing ThirdPage but if the user taps the back button, they will go to SecondPage and then FirstPage if they tap again.

class Routes {
  static const String firstPage = '/';
  static const String secondPage = '/second';
  static const String thirdPage = '/second/third';
}

class MyNavApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: Routes.thirdPage,
      routes: {
        Routes.firstPage: (BuildContext context) => FirstPage(),
        Routes.secondPage: (BuildContext context) => SecondPage(),
        Routes.thirdPage: (BuildContext context) => ThirdPage(),
      },
    );
  }
}

On app startup the initalRoute is split on ‘/’ and then any routes that match the segments are navigated to. In this case the app navigates to FirstPage, then SecondPage and finally ThirdPage.

Intercepting Back Button

The last topic for this post looks at how to intercept the back button. This can be done by including the WillPopScope widget and returning an appropriate value in the onWillPop callback.

class ThirdPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () {
        return new Future.value(true);
      },
      child: Container(
        alignment: Alignment.center,
        child: Text('Third Page'),
      ),
    );
  }
}

Note that the onWillPop expects a Future to be returned, allowing you to return an asynchronous result. In this case the code is simply returning the value of true to allow the current route to be popped.

Summary of Flutter Navigation

This post on Flutter navigation has covered a number of the navigation related methods on the Navigator class. As part of defining how your app looks, you should define all the routes and how the user will navigate between them. If you do this early in the development process you’ll be able to validate the flow of your app as you continue development.

Productivity Musings on App Navigation, Information Density and Work Environment

This morning I set out to explore the most recent ramblings on app navigation. I expected to come across a bunch of designers talking about their sliding, popping, whirling interface. How they are going to radicalize the way we interact with mobile applications. The first thing I was struck with was that I needed to … Continue reading “Productivity Musings on App Navigation, Information Density and Work Environment”

This morning I set out to explore the most recent ramblings on app navigation. I expected to come across a bunch of designers talking about their sliding, popping, whirling interface. How they are going to radicalize the way we interact with mobile applications. The first thing I was struck with was that I needed to get out of my own mobile-centric thought process. I immediately extended my investigation to include desktop, tablet and of course web. After coming across various sites about different navigation constructs, my thought process wandered off. I soon found myself reflecting on some of the design decisions that we live and work with daily. This lead me to some productivity musings that I think generate more questions than answers.

Mobile is Not Desktop

The launch point for my productivity musings was that a couple of the sites reminded me of the panorama and pivot controls from Windows Phone. I think some credit should go to Microsoft’s Metro Modern Fluent (whatever it’s now called) design system. The Panorama and Pivot controls, when done well provided an immersive experience that let users rapidly dig into information.

Fast forward to Windows 8 and Microsoft decides to expand the Windows Phone design system to big-Windows….. #FAIL. We tried. We really did try. And in some cases we produced some amazing applications. However, fundamentally the design system wasn’t designed to scale. The Panorama didn’t have the right information density for a desktop application. One of the reasons that the Mail and Calendar apps for Windows were never able to take on Outlook, is that the information density is just too low.

Stupid Apps

I feel we would be remiss for not chastising Apple for a generation of “stupid apps”. When the iPhone came out, it set about revolutionizing what the world meant when it said smartphone. It was like all the devices prior to it weren’t smart. This simply isn’t true – there were plenty of devices in market that from a functionality perspective were smart. What Apple did was to bring smartphones to the masses. They did that by dumbing it down. Apps did one thing. You could run one App at a time. You can only get Apps from the App Store. There was only one devices that had Apps, the iPhone.

This lead the way for a new breed of developers, “App Developers”. These were developers who were able to string a couple of screens together and call it an app. These developers were in a league of their own. Web and desktop developers looked over and saw “App Developers” making money from apps that did one thing. Ina lot of cases the apps didn’t even do that one thing well, in other words, stupid apps.

Over time the mobile ecosystem has evolved. App Developers have matured and today most app developers build apps for both iOS and Android. Apps themselves have evolved and increased in complexity and sophistication. AI and Machine Learning is being integrated to allow mobile applications to solve a range of tasks. However, despite all this innovation, the majority of mobile apps still do only a small number of functions.

Information Density

As my productivity musings continue, if we look at apps designed for desktop, we can see that they have more functions and much higher information density. Apps such as Word, Excel, VS Code and Photoshop are heavily optimised for mouse and keyboard. As such there is a much high information density, allowing more data to be presented and manipulated on any given screen.

Of course, there’s a trade off between ease of use and information density. Actually, let me correct that somewhat. It’s not necessarily ease of use that decreases with increase information. Rather, it’s the ability for new users to learn an application. Take Excel for example. There are plenty of spreadsheet alternatives out there. I’m continually amazed by the number of people that use Google Sheets. This is because they’ve only ever learnt the first 10% of spreadsheet capabilities. Those that you pick up when you first start using a spreadsheet. Most users barely get past using a spreadsheet as a glorified list making tool. If you look at how spreadsheets are used in finance or engineering, it’s staggering how much data can be calculated, displayed and interpreted on a single screen.

What struck me about the links that I read regarding navigation, is that very few of them deal with high information density. All the designs seem to be focused on building slick interfaces that work well with a single hand, or respond well when the browser is resized. There’s almost no mention of how to deal with complex data sets, or multiple windows, or the learning process for complex applications.

Multiple Windows

At this point, my productivity musings turned towards a pet annoyance, which is the lack of support for multiple windows. Since around the time of Windows 8, it seems that most developers have forgotten that both PC and Mac support applications with multiple windows. Both Apple and Microsoft realise the significance of the window metaphor. Whilst it was Microsoft that adopted the name Windows for the OS, both companies continue to support and evolve the window metaphor.

It should be noted that in Windows 8, Microsoft made a daft move by trying to push apps full screen. How did it make sense to have an operating system called Windows that doesn’t support applications running in a windows? Unfortunately I think the lasting impact of this, coupled with the birth of app developers who knew how to build “stupid apps,” has meant that we’ve had to put up with a host of single-windowed apps.

Take for example this comment from Derik about wanting to pop out an editor window to drag to another screen:

Desktop apps, for both PC and Mac, need to start by thinking in multiple windows. Don’t just make it an after thought.

Window Management

We’re approaching the end of my productivity musings. At this point I went on a slight tangent along the idea of multiple windows and thinking about the way that they’re managed. Both Windows and MacOS have mechanisms for closing, opening, restoring, minimising, splitting windows. However, the one thing I routinely notice among Mac users is that they struggle with basic window management. Perhaps they’ve never bother to spend the time to work out how to manage windows; perhaps it’s harder to do on a Mac. Personally I stick with Windows as that’s where I’m comfortable. If you can’t manage your application windows effectively, that’s on you to learn how to do it.

Multi-Monitor

Moving on from multiple windows we get to multiple monitors. This one is a criticism of companies that choose to invest in finding the right staff but then fail to equip them to do their job. If your staff do work that’s heavily computational, make sure they have a desktop PC or Mac to work on. Regardless of whether you equip them with a desktop or a laptop, when your staff are working at a desk, they need to have at least two external monitors to work on and ideally external mouse and keyboard. You’ll spend a little more up front setting up their work station but you’ll get that back in productivity within the first month, if not the first week!

That’s Not a Mouse!

If you decide that you’re going to supply MacBook or iMac or some other Apple product to your staff, please make sure you do the right thing and buy them a real mouse and keyboard set.

A mouse has more than just a single function. Go get a mouse that will really get the job done.

A keyboard isn’t a work of art. Go get a keyboard that will really make you productive. It doesn’t have to be split like in the following image but it is worth considering split keyboards as they are supposed to be more ergonomic.

Productivity Musings in Summary

This post is a bit of a ramble about the hits and misses of app development. However, it’s worth consider where we are in the software development industry. Are we so focused on the latest technology; the latest hot trend; the latest design metaphor, that we’ve lost the ability to build productivity software?


App Navigation Links

The following are a selection of links that I browsed when investigating some of the latest trends when it comes to navigation within applications. Whilst they’re not strictly a productivity musing, they did form the genesis for this post and I’d recommend taking a read as they may influence how you design the experience of your next application.

ViewModel to ViewModel Navigation in a Xamarin.Forms Application with Prism and MvvmCross

ViewModel to ViewModel Navigation in a Xamarin.Forms Application with Prism and MvvmCross

I’m a big fan of the separation that the Mvvm pattern gives developers in that the user interface is encapsulated in the view (Page, UserControl etc) and that the business logic resides in the ViewModel/Model. When structuring the solution for an application I will go so far as to separate out my ViewModels into a separate project from the views, even with Xamarin.Forms where the views can be defined in a .NET Standard library.

One of the abstractions that this lends itself to is what is referred to as ViewModel to ViewModel navigation – rather than the ViewModel explicitly navigation to a page, or bubbling an event up to the corresponding view to get the view to navigate to the next page, ViewModel to ViewModel navigation allows the ViewModel to call a method such as Navigation(newViewModel) where the newViewModel parameter is either the type of the ViewModel to navigate to, or in some frameworks it may be an actual instance of the new ViewModel.

MvvmCross

Let’s see this in action with MvvmCross first – I’m going to start here because ViewModel to ViewModel navigation is the default navigation pattern in MvvmCross. I’ll start with a new project, created using the MvxScaffolding I covered in my previous post, using MvvmCross in a Xamarin.Forms application. The single view template already comes with a page, HomePage, with corresponding ViewModel, HomeViewModel. To demonstrate navigation I’m going to add a second page and a second ViewModel. Firstly, I’ll add a new class, SecondViewModel, which will inherit from BaseViewModel (a class generated by MvxScaffolding which inherits from MvxViewModel that’s part of MvvmCross).

public class SecondViewModel : BaseViewModel
{
}

Next, I’ll add a new ContentPage called SecondPage (note the convention here that there is a pairing between the page and the ViewModel ie [PageName]Page maps to [PageName]ViewModel)

image

MvvmCross supports automatic registration of pages and ViewModels but it does require that the page inherits from the Mvx base class, MvxContentPage. I just need to adjust the root XAML element from

<ContentPage …

to

<views:MvxContentPage x_TypeArguments=”viewModels:SecondViewModel” …

The inclusion of the TypeArguments means that the generic overload of MvxContentPage is used, providing a helpful ViewModel property by which to access the strongly typed ViewModel that is databound to the page.

Now that we have the second page, we just need to be able to navigate from the HomePage. I’ll add a Button to the HomePage so that the user can drive the navigation:

<Button Text=”Next” Clicked=”NextClicked” />

With method NextClicked as the event handler (here I’m just using a regular event handler but in most cases this would be data bound to a command within the HomeViewModel):

private async void NextClicked(object sender, EventArgs e)
{
     await ViewModel.NextStep();
}

And of course we need to add the NextStep method to the HomeViewModel that will do the navigation. The HomeViewModel also needs access to the IMvxNavigationService in order to invoke the Navigate method – this is done by adding the dependency to the HomeViewModel constructor.

public class HomeViewModel : BaseViewModel
{
     private readonly IMvxNavigationService navigationService;


    public HomeViewModel(IMvxNavigationService navService)
     {
         navigationService = navService;
     }
     public async Task NextStep()
     {
         await navigationService.Navigate<SecondViewModel>();
    }
}

As you can see from this example the HomeViewModel only needs to know about the SecondViewModel, rather than the explicit SecondPage view. This makes it much easier to test the ViewModel as you can provide a mock IMvxNavigationService and verify that the Navigate method is invoked.

Prism

Now let’s switch over to Prism and I’ve used the Prism Template Pack to create a new project. To add a second page I’ll add a SecondPageViewModel, which in the case of Prism inherits from ViewModelBase and requires the appropriate constructor that provides access to the INavigationService. Note that the naming convention with Prism is slightly different from MvvmCross where the ViewModel name is [PageName]PageViewModel (ie both the page and the viewmodel have the Page suffix after the PageName eg SecondPage and SecondPageViewModel)

public class SecondPageViewModel : ViewModelBase
{
     public SecondPageViewModel(INavigationService navigationService) : base(navigationService)
     {
     }
}

I’ll add a new ContentPage called SecondPage but unlike MvvmCross I don’t need to alter the inheritance of this page. Instead what I do need to do is register the page so that it can be navigated to. This is done in the App.xaml.cs where there is already a RegisterTypes method – note the additional line to register SecondPage.

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
     containerRegistry.RegisterForNavigation<NavigationPage>();
     containerRegistry.RegisterForNavigation<MainPage, MainPageViewModel>();
     containerRegistry.RegisterForNavigation<SecondPage, SecondPageViewModel>();
}

Similar to the MvvmCross example, I’ll add a button to the MainPage (the first page of the Prism application created by the template) with code behind to call the NextStep method on the MainViewModel

private async void NextClicked(object sender, EventArgs e)
{
     await (BindingContext as MainPageViewModel).NextStep();
}

Note that because the MainPage just inherits from the Xamarin.Forms ContentPage there’s no property to expose the data bound viewmodel. Hence the casting of the BindingContext, which you’d of course do null checking and error handling around in a real world application.

public async Task NextStep()
{
     await NavigationService.NavigateAsync(“SecondPage”);
}

The NextStep method invokes the NavigateAsync method using a string literal for the SecondPage – I’m really not a big fan of this since it a) uses a string literal and b) requires the the ViewModel knows about the view that’s being navigated to. So let’s adjust this slightly by changing the way that pages and ViewModels are registered. The RegisterForNavigation method accepts a parameter that allows you to override the navigation path, meaning we can set it to be the name of the ViewModel instead of the name of the page.

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
     containerRegistry.RegisterForNavigation<NavigationPage>();
     containerRegistry.RegisterForNavigation<MainPage, MainPageViewModel>(nameof(MainPageViewModel));
     containerRegistry.RegisterForNavigation<SecondPage, SecondPageViewModel>(nameof(SecondPageViewModel));
}

The navigation methods would then look like:

public async Task NextStep()
{
     await NavigationService.NavigateAsync(nameof(SecondPageViewModel));
}

But I think we an improve this further still by defining a couple of extension methods

public static class PrismHelpers
{
     public static void RegisterForViewModelNavigation<TView, TViewModel>(this IContainerRegistry containerRegistry)
         where TView : Page
         where TViewModel : class
     {
         containerRegistry.RegisterForNavigation<TView, TViewModel>(typeof(TViewModel).Name);
     }


    public static async Task<INavigationResult> NavigateAsync<TViewModel>(this INavigationService navigationService)
         where TViewModel : class
     {
         return await navigationService.NavigateAsync(typeof(TViewModel).Name);
     }
}

Using these extension methods we can update the registration code:

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
     containerRegistry.RegisterForNavigation<NavigationPage>();
     containerRegistry.RegisterForViewModelNavigation<MainPage, MainPageViewModel>();
     containerRegistry.RegisterForViewModelNavigation<SecondPage, SecondPageViewModel>();
}

And then the navigation code:

public async Task NextStep()
{
     await NavigationService.NavigateAsync<SecondPageViewModel>();
}

The upshot of these changes is that there’s almost no difference between the MvvmCross method of navigation and what can be done with a little tweaking with Prism.