Navigating in Xamarin

I was listening to a podcast (Merge Conflict) recently about the latest changes in Xamarin, and being a curious soul, decided it was time to at least understand what was going on. This time, the big new feature was the Xamarin Live Player which promises much faster debugging through your actual phone! Instead of being stuck using the creaky old Android emulator on Windows, or waiting 2 – 5 minutes on every build with your phone plugged in, you could now, with the power of the internet, debug wirelessly almost instantly. Obviously, I was suspicious, but this sounded like fun, so I jumped in.

Getting setup was a bit of a chore, I will admit. Most of the trouble was linked to being an early adopter. You see, Xamarin Live Player wasn’t truly released yet, just part of the latest preview of Visual Studio (v15.3 to be exact). So I had to get that, install almost all features (say goodbye to about 40GB on your hard drive, sorry ultrabook owners), and then I was basically ready to go. The final step was installing their app on my phone, and I was ready to go.

Within about 10 minutes I had the demo project running on my phone, pretty good I thought. I used the Master/Detail project template and that came with some basic navigation to boot. Everything looked great…but was it?

Being a big fan of MVVM, I really have 3 things I need in place before I can start building an application.

  1. A base class implementing INotifyPropertyChanged. Luckily this project came with one called ObservableObject.
  2. A class implementing ICommand. Again, Xamarin has us covered here, with their implementation imaginatively named Command.
  3. Some kind of ViewModel first navigation system, which appears to be missing.

So, before I get into what’s missing, I want to say that obviously, it’s not Xamarin’s job to create this stuff for us. I don’t need to be spoon-fed. Also, I get that they’re trying to set us up with a working system to get us rolling, but I don’t think this is it.

I’ve never liked View-first navigation in MVVM apps. I don’t really get how you’re supposed to be able to pass anything around, unless you’re constantly creating a View, then passing the newly created ViewModel into that in the constructor. Gross. Unfortunately, that’s what this demo app does, and it took me a few hours to figure out what everything was and what was doing what, before I realized I needed a better way. In the template you get, they’re basically doing navigation from events in the code-behind of the views (or pages in this case). Again, not a fan.

My first instinct was to do my old favorite, using DataTemplates, and just defining the View / ViewModel pairs explicitly in Xaml somewhere and being done with it. Unfortunately, it looked like this wasn’t going to fly in Xamarin.

So I did what I always do, started googling it. And it worked. I came across a library called VMFirstNav. It contained most of what I wanted, but had a little more in it than I wanted. Using their implementation, you have to make all Views implement an interface telling the library what ViewModel to link it to, and then all ViewModels have to implement another interface. It wasn’t a dealbreaker, but I wanted to see what he was actually doing. The nice thing of all that was that it would automagically figure out what View to load when you change (or push) to a new ViewModel.

This is where I want to thank CodeMillMatt. Without his code, I would have taken a lot longer to figure out how this all works. It’s good stuff dude.

OK, so what I did was take his library and just boil it down to the one important class, the NavigationService, which I named Navigator. I didn’t bother making a new github project, or putting it in Nuget, as I don’t think this is really finished. I could see a need for more features depending on what you need, but this is at least a solid starting point.

Here’s the code for the class –


using LiteSourceGo.ViewModels;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms;
public class Navigator
{
static Navigator instance;
public static Navigator Instance
{
get { return instance ?? (instance = new Navigator()); }
}
INavigation FormsNavigation
{
get { return Application.Current.MainPage.Navigation; }
}
readonly Dictionary<Type, Type> _viewModelViewDictionary = new Dictionary<Type, Type>();
public void Register(Type viewModelType, Type viewType)
{
if (!_viewModelViewDictionary.ContainsKey(viewModelType))
_viewModelViewDictionary.Add(viewModelType, viewType);
}
public async Task PopAsync()
{
await FormsNavigation.PopAsync(true);
}
public async Task PopModalAsync()
{
await FormsNavigation.PopModalAsync(true);
}
public async Task PopToRootAsync(bool animate)
{
await FormsNavigation.PopToRootAsync(animate);
}
public void PopTo<T>() where T : BaseViewModel
{
var pagesToRemove = new List<Page>();
for (int i = FormsNavigation.NavigationStack.Count – 1; i >= 0; i–)
{
var currentPage = FormsNavigation.NavigationStack[i] as Page;
if (currentPage?.BindingContext.GetType() == typeof(T))
break;
pagesToRemove.Add(currentPage);
}
foreach (var page in pagesToRemove)
FormsNavigation.RemovePage(page);
}
public async Task PushAsync<T>(T viewModel) where T : BaseViewModel
{
var view = InstantiateView(viewModel);
await FormsNavigation.PushAsync((Page)view);
}
public async Task PushModalAsync<T>(T viewModel) where T : BaseViewModel
{
var view = InstantiateView(viewModel);
var nv = new NavigationPage(view);
await FormsNavigation.PushModalAsync(nv);
}
Page InstantiateView<T>(T viewModel) where T : BaseViewModel
{
var viewModelType = viewModel.GetType();
var view = (Page)GetViewType(viewModelType);
view.BindingContext = viewModel;
return view;
}
private object GetViewType(Type viewModelType)
{
Type viewType = null;
if (_viewModelViewDictionary.ContainsKey(viewModelType))
{
viewType = _viewModelViewDictionary[viewModelType];
}
else
{
string viewTypeName = viewModelType.FullName.Replace("ViewModel", "View");
viewType = Type.GetType(viewTypeName);
// We don't have it already, get it now, and add it to the dictionary
_viewModelViewDictionary.Add(viewModelType, viewType);
}
return Activator.CreateInstance(viewType);
}
}

view raw

Navigator.cs

hosted with ❤ by GitHub

And here’s how to use it, once you’ve got it in place. It has a static constructor, so it’s essentially a singleton, so you don’t need to go passing it in and out of every class. It’s a little more decoupled, but I prefer it that way.


There’s 2 ways of using it.

  1. Explicitly specify all the View / View Model pairs on startup. In the past I’ve liked this method as at least you know where they are and how it works. The other advantage of this is you can name these classes whatever you want.
     private void SetupAllViewToViewModelPairs()
     {
         var nav = Navigator.Instance;
    
         nav.Register(typeof(LoggingViewModel), typeof(LoggingView));
         nav.Register(typeof(AnotherViewModel), typeof(AnotherView));
     }
    
  2. Follow a simple naming convention, and the service will automagically figure out what View to load when you tell it to load a certain View Model. The convention can be changed (it’s your code now after all). As long as you follow the scheme below, you’ll be good.
     [ClassName]View.xaml
     [ClassName]ViewModel.cs
    

OK, then to use it, you’re basically just doing the following –

var vm = new [ClassName]ViewModel();
await Navigator.Instance.PushAsync(vm);

Easy.

Bear in mind, there is no error checking on this code, so maybe tidy that up a little if you plan on using it. The only part you would need to be more careful of is probably the automatic View locating part.

Just to wrap this up. I played with Xamarin for a couple of days, but found the limitations to be too hard to work with. I’m sure there’s a lot of uses for it, but we don’t do a lot with web services at my work, so I haven’t got any excuses to work with it.

If you’ve got any comments on the code, or you’ve built anything cool in Xamarin that you’d like to show off, let me know in the comments.