I wrote a couple articles a while back on how to perform view model first navigation within Xamarin.Forms. One article concentrating on the simple stack based navigation and another that puts Master/Detail pages into the mix.

Now when I talk about view model first navigation - I'm talking about the ability to navigate to a page based on its backing view model... from another view model.

In other words, something like this call from one view model class:

await NavigationService.PushAsync<ChildViewModel>();  

Would instantiate the ChildViewModel and also navigate to the ChildView page ... setting the BindingContext of the ChildView to the newly created ChildViewModel.

How's that for a mouthful? :)

Turns out I used the techniques enough described in those 2 articles that I finally thought I should put together a NuGet package for it ... this way I can put the latest updates into it (that haven't been reflected in the 2 previous articles), it'll be easier for me to use in the future (super important!), and hopefully will help somebody else out too.

What I want to do in this article is lay out how to use this new lightweight view model first navigation library for Xamarin.Forms called CodeMill.VMFirstNav. All the source code and a demo project can be found at that link.

Installation and Setup

So there's some basic installation and setup that you have to go through in order to use this library.

Installation

The installation is pretty straight forward - head on out to NuGet and search for CodeMill.VMFirstNav. Or you could run Install-Package CodeMill.VMFirstNav from the package manager console.

At this time, this package will need to be installed into both the platform and core projects.

The library is .netstandard1.0 with a dependency on Xamarin.Forms 2.3.4.247 and above.

Setup

The library keeps an internal dictionary that maps the view models-to-views. But you do not need to manually insert items into that dictionary, the library will find the appropriate mappings for you.

There are 2 interfaces included as part of this library that are needed to do that. IViewModel and IViewFor<T>.

IViewModel is a marker interface only - meaning it does not hold any properties or functions to be implemented.

Every view model that you want to participate in the VM-first navigation should implement this interface.

IViewFor<T> has a full definition of IViewFor<T> where T : class, IViewModel.

In other words - it's saying - I'm a view for this particular view model. There is one property to be implemented and that's T ViewModel { get; set; }.

So in order for the view to take part in the VM-first navigation, it must implement IViewFor<T> where the T is the backing view model type.

That does mean you need to go around to all of your views and view models and make them implement those 2 interfaces.

And I would recommend the T ViewModel { get; set; } property be implemented as follows:

ChildViewModel vm;  
public ChildViewModel ViewModel {  
    get => vm; 
    set
    {
        vm = value;
        BindingContext = vm;
    }
}

This way the BindingContext of the Page gets set to the view model as soon as the library sets the view model up.

Initialization

At runtime then, the library will take care of finding the view -> view-model pairs when RegisterViewModels is called and it's passed the assembly of where the views and view models live. (Currently they have to both be in the same assembly).

You would do this in either the AppDelegate or MainActivity class:

NavigationService.Instance.RegisterViewModels(typeof(App).Assembly);  

The API

A quick thank you to Chris Zirkel for some suggested API changes from what I had in the APIs in the articles above. Most helpful!

Now with all of the necessary administrative work out of the way, you can move on to the more satisfying stuff of actually implementing the view model first logic.

In addition to the IViewModel and IViewFor<T> interfaces there are several more interfaces and classes.

As you may have noted above - everything runs through a class called NavigationService. You don't instantiate it directly, because it needs to keep track of that internal view->view model dictionary, but rather accessed through a singleton called Instance. This way it'll stay in memory for the life of the app.

It conforms to another interface called INavigationService whose definition looks like the following:

public interface INavigationService  
{
    void RegisterViewModels(System.Reflection.Assembly asm);
    void Register(Type viewModelType, Type viewType);

    Task PopAsync();
    Task PopAsync<T>(Action<T> reInitialize = null) where T : class, IViewModel;
    Task PopModalAsync();
    void PopTo<T>() where T : class, IViewModel;
    Task PopToRootAsync(bool animate);

    Task PushAsync<T>(T viewModel) where T : class, IViewModel;
    Task PushAsync<T>(Action<T> initialize = null) where T : class, IViewModel;
    Task PushModalAsync<T>(T viewModel) where T : class, IViewModel;
    Task PushModalAsync<T>(Action<T> initialize = null) where T : class, IViewModel;

    void SwitchDetailPage<T>(T viewModel) where T : class, IViewModel; 
    void SwitchDetailPage<T>(Action<T> initialize = null) where T : class, IViewModel;    
}

As I break these down one by one - when I refer to a view->view model combination being on the stack, I'm really only referring to the view (or Page) residing in the Xamarin.Forms INavigation.NavigationStack and then being able to find it's associated view model via the internal dictionary. So... instead of saying that... it's easier to refer to them as both being on the stack.

Registration

  • void RegisterViewModels(System.Reflection.Assembly asm) - invoked in the AppDelegate or MainActivity to have the service enumerate all the view->view model pairs and register them in the internal dictionary.
  • void Register(Type viewModelType, Type viewType) - used to register a single view->view model pair in the dictionary manually.

Popping

  • Task PopAsync() - when used inside a view model, pops the associated view (or Page) off the navigation stack.
  • Task PopAsync<T>(Action<T> reInitialize = null) where T : class, IViewModel - used to pop the current view->view model combination off the stack. If the next view model in the stack is of type T it will run whatever action passed in on that view model. In otherwise it will only perform a PopAsync().
  • Task PopModalAsync() - removes the current view->view model combination from the stack.
  • void PopTo<T>() where T : class, IViewModel - pops everything off the stack until it finds a view->view model combination where the view model is of the type T. This is kind of hacky - as you may or may not see all of the previous pages being pulled from the stack.
  • Task PopToRootAsync(bool animate) - removes everything from the navigation stack except for the root view->view model combination.

Pushing

  • Task PushAsync<T>(T viewModel) where T : class, IViewModel - used to push a new view->view model combination onto the navigation stack. Here the view model has already been instantiated before this function is called.
  • Task PushAsync<T>(Action<T> initialize = null) where T : class, IViewModel - used to push a new view->view model combination onto the stack - and optionally invoke an action on the view model before the combo is pushed. Here the view model is instantiated by the library.
  • Task PushModalAsync<T>(T viewModel) where T : class, IViewModel - used to push a modal view->view model combination onto the stack when the instantiated view model already is in hand.
  • Task PushModalAsync<T>(Action<T> initialize = null) where T : class, IViewModel - used to push a modal view->view model combination onto the stack and optionally invoke an action on the view model before the combo is pushed. The view model is instantiated by the library.

Switching

Used with MasterDetailPages to switch the MasterDetailPage.Detail page or the App.MainPage.

  • void SwitchDetailPage<T>(T viewModel) where T : class, IViewModel - used to switch the page when the view model is already instantiated.
  • void SwitchDetailPage<T>(Action<T> initialize = null) where T : class, IViewModel - used to switch the page and optionally invoke a function on the view model before the view->view model combination is make the active page.
A Word On Master/Detail Pages

The library also provides a class to help with MasterDetailPages when the master page is composed of a list with options that when selected open a new list. MasterListItem<T>. It contains one property, DisplayName that is meant for display in the ListView, and the T is the IViewModel.

The idea here is that when the item gets selected from the ListView, you'll have a MasterListItem<T> class. From that you can create extract the T, new up the view model, and then swap the Detail page.

It's a bit hacky right now. See here for an example.

I'll get it cleaned up and add a new function to INavigationService that does all of that work in the next release.

Suffice it to say - you can do Master/Detail navigation - but it's not ideal quite yet.

What's To Come? (or what are the bugs?)

As I mentioned, I want to clean up the Master/Detail navigation a bit.

I also want to be sure the manual void Register(Type viewModelType, Type viewType) has some validation around it, currently you could pass anything into it.

All of the view->view model combinations need to be in the same assembly right now - I want to change that.

Ideally, I would have the library do self registration of all view->view models without the need to call Register. I'm debating on that yet.

I would love to hear from you too! Please open up an issue on GitHub if there's anything you'd like to see or if there's any bugs you find. I also take PRs if you want to contribute! :)