Bootstrap Series – Xamarin.Forms in MvvmLight

Welcome! This is the first entry in a three(?) part series where I explore MVVM frameworks that can be used with Xamarin.Forms. As a by-product of my investigation, I’ll have a handy bootstrapped project of each framework to help you get started using the framework discussed!

tl;dr – source code

Over the course of the series, I’ll be examining the pros and cons of the following MVVM Frameworks:

  1. MVVM Light (source)
  2. Prism.Forms (source)
  3. Xamvvm (source)

In this post, we’ll dive in to one of the most ubiquitous MVVM frameworks available, MVVM Light (which you may recall from my earlier post on MVVM Best Practices). Let’s take a look:

Getting Started

It always starts with a NuGet package. MvvmLight is a little unique compared to the other frameworks I’ll be discussing in that it is super old. So, searching just for “mvvmlight” is likely to turn up a while slew of other packages, deprecated or otherwise. Be sure to run the following command from your package manager console, or search “mvvmlightlibs” from the nuget UI:

Install-Package Portable.MvvmLightLibs -Version 5.0.2

Don’t forget! You’ll need to add the package and any dependencies to each project; your PCL, and every platform specific you are supporting. Visual Studio, may add some files for you, but (because I am a creature of habit), I tend to clear these auto-generated files out immediately and build my own structure from scratch, like so:
Screen Shot 2017-09-04 at 8.36.27 PM

At this time, let’s go ahead and clear out any views created by default – I find it easier at this point to recreate them in the new folder structure rather than spend time fiddling with namespaces. Before we get to far along, let’s go ahead and set some stuff up in App.xaml.cs:

        public App()
        {
            InitializeComponent();

	    var mainPage = new Views.AppShell();

	    // Init Navigation Service
	    ((NavigationService)ServiceLocator.Current.GetInstance<INavigationService>()).Initialize((NavigationPage)mainPage.Detail);
	    MainPage = mainPage;

	    // Init Dialog Service
	    ((DialogService)ServiceLocator.Current.GetInstance<IDialogService>()).Initialize(MainPage);
        }

Our code won’t compile at this time; there’s a few things we still need to create, each of which will be discussed in this post. For now, add some stubs in the following locations:

  • NavigationService.cs in the Services folder of our PCL project
  • DialogService.cs in the Services folder of our PCL project
  • ViewModelLocator.cs in the Startup folder of our PCL project
  • IoCConfig.cs in the Startup folder of our PCL project

Also, add this line as part of the resources dictionary in App.xaml, substituting the namespace as appropriate:

<vm:ViewModelLocator xmlns:vm="clr-namespace:MvvmLight.Startup;assembly=MvvmLight" x:Key="Locator" />

Dependency Injection

Let’s examine ViewModelLocator.cs:

	public class ViewModelLocator
	{
		/// <summary>
		/// Initializes a new instance of the ViewModelLocator class.
		/// </summary>

		static ViewModelLocator()
		{
			var iocConfig = new IoCConfig();
			iocConfig.RegisterSettings();
			iocConfig.RegisterRepositories();
			iocConfig.RegisterServices();
			iocConfig.RegisterViewModels();
			iocConfig.RegisterNavigation();
		}

		public static void Cleanup()
		{
			// TODO Clear the ViewModels
		}

		/*
         * Define ViewModels
         */
		public HomePageViewModel HomePage => ServiceLocator.Current.GetInstance<HomePageViewModel>();
		public MenuPageViewModel MenuPage => ServiceLocator.Current.GetInstance<MenuPageViewModel>();
        public SamplePageViewModel SamplePage => ServiceLocator.Current.GetInstance<SamplePageViewModel>();
        public SettingsPageViewModel SettingsPage => ServiceLocator.Current.GetInstance<SettingsPageViewModel>();

		// TODO INIT: Add new ViewModels Here
	}

Here we initialize our IoCContainer and register any ViewModels we may need. Since MvvmLight comes with an IoC container out of the box, we can jump right in to setting up our dependency injection right away. Full details are here, but let’s look at some important parts of IoCConfig.cs. In the constructor, set our locator provider:

		public IoCConfig()
		{
			ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
		}

We need to register any View Models we need like so:

SimpleIoc.Default.Register<ViewModels.HomePageViewModel>();

We need to create our navigation service and register views:

		/// <summary>
		/// Handles navigation wire up
		/// </summary>

		public void RegisterNavigation()
		{
			var navigationService = new NavigationService();
			navigationService.Configure(nameof(Views.HomePage), typeof(Views.HomePage));
			// TODO INIT: Register new Views here

			SimpleIoc.Default.Register<INavigationService>(() => navigationService);
		}

Once our View, ViewModel, and any needed services are registered, we can initialize a ViewModel with the needed services easily:

public HomePageViewModel(INavigationService navigationService, IDialogService dialogService)

App Shell

AppShell.cs is the container in which our app lives. All of our actions and navigation happen within the “shell” of this class – hence the name. This is a pattern I turn to frequently, but let’s see what a master/detail navigation scheme looks like in this context with MvvmLight:

	public class AppShell : MasterDetailPage
	{
		public AppShell()
		{
            Detail = new NavigationPage(new HomePage())
			{
				BarTextColor = Color.White,
				BarBackgroundColor = Color.FromHex("#394A76"),
			};

			Master = new MenuPage()
			{
				Title = "Menu",
			};
		}

		protected override void OnAppearing()
		{
			base.OnAppearing();
			MessagingCenter.Subscribe<MenuPageViewModel>(this, "CLOSE_MENU", (vm) =>
			{
				if (Device.Idiom != TargetIdiom.Desktop && Device.Idiom != TargetIdiom.Tablet)
				{
					this.IsPresented = false;
				}
			});
		}

		protected override void OnDisappearing()
		{
			base.OnDisappearing();
			MessagingCenter.Unsubscribe<MenuPageViewModel>(this, "CLOSE_MENU");
		}
	}

Here, we establish a few things. We define which of our pages will act as the host navigation page (the “detail” part of master/detail) and which page will act as our navigation “master”. Furthermore, we register some actions using MessagingCenter to ensure the Master pane closes (when in an applicable format) when navigation occurs.

Commanding

Thus far, we’ve largely discussed things that have to happen behind the scenes. However, commanding, or any sort of user interaction, in inherently tied to the UI. In order to preserve the MVVM goodies, and keep our logic out of the view, we need to set the binding context of a view to it’s corresponding ViewModel. Some MVVM frameworks will do this by convention such that any Views and ViewModels that follow a defined naming convention will automatically be associated. MvvmLight provides no such mechanism, so we have to manually associate a view using the the ViewModel definitions we defined in ViewModelLocator.cs:

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MvvmLight.Views.HomePage"
BindingContext="{Binding Source={StaticResource Locator}, Path=HomePage}"
Title="{Binding Title}">
	<ContentPage.Content>

Check your spelling: if your Views aren’t getting the proper binding context check these lines first. Once we’ve established the association, we can bind to properties and commands from the ViewModel. Once the association is made, bind to a command from the view like so:

<Button Text="Show Action Sheet" Command="{Binding ShowActionSheetCommand}"/>

Which we then define in the ViewModel thusly:

public RelayCommand ShowActionSheetCommand = new RelayCommand(async () => await ShowActionSheet());

Navigating

Navigation

Navigating within the MVVM universe requires us to implement the INavigationService interface that is part of the MVVMLight library. My full implementation exists here, but let’s look at some important parts in detail. Relatively speaking MvvmLight is, as the name would suggest, light. Since it is up to the developer to implement the interface, we have some flexibility on the implementation details. Personally, some of the other MVVM frameworks I’ve used provide more functionality out of the box, so I extend the default interface to add additional navigation features:

	/// <summary>
	/// Navigation service that extends <see cref="GalaSoft.MvvmLight.Views.INavigationService"/>. Add any
	/// navigatioon techniques you need that aren't part of the MvvmLight interface here
	/// </summary>

	public interface INavigationService : GalaSoft.MvvmLight.Views.INavigationService
	{
		void GoBackToRoot();
	}

From the ViewModel, we can use inject the navigation service into the constructor, then use it like so:

public HomePageViewModel(INavigationService navigationService, IDialogService dialogService)

If we use the first means, there is no additional work required, assuming the p[age we are navigating to is already registered (View and ViewModel, properly associated). That said, the second example is particularly useful, especially when we consider cases such as ListViews and Pickers. We first need to create a property on the ViewModel called SelectedItem (or another appropriate name), in which we establish the action we wish to take whenever the value is set to something besides null:

		private CopyItem _selectedItem;
		public CopyItem SelectedItem
		{
			get
			{
				return _selectedItem;
			}
			set
			{
				Set(() => SelectedItem, ref _selectedItem, value);
				if (_selectedItem == null)
					return;
				this.SelectItemCommand.Execute(value);
				SelectedItem = null;
			}
		}

Then, we need to establish the proper bindings on the View.

 <ListView SelectedItem="{Binding SelectedItem, Mode=TwoWay}" ...

Once triggered, the newly set value is passed to the command and we execute our navigation. Since we are changing pages, we need to modify the constructor of the view we navigate to. This makes me a little uneasy, as it seems to break the MVVM pattern in the purest sense, but realistically other frameworks are doing something similar behind the scenes. Here’s what this looks like on the target page:

    public partial class SamplePage : ContentPage
    {
        // Default constructor keeps Forms Previewer happy and covers us in the event no parameter is passed
        public SamplePage()
        {
            InitializeComponent();
        }

        public SamplePage(CopyItem parameter = null)
        {
            InitializeComponent();

            // Check that our BindingContext exists, then assign the value(s) as appropriate
            var viewModel = this.BindingContext as ViewModels.SamplePageViewModel;
            if(viewModel != null && parameter != null)
            {
                viewModel.Subject = parameter.Title;
                viewModel.Copy = parameter.Body;
            }
        }
    }

Notice that, after we navigate, we can also use this technique to set a property on the resulting page, which is especially valuable in cases where we want the end result to show some detail from the previous page.

Show Alerts & Dialogs

Like navigating, getting user interaction using MVVM can be a bit confusing at first. In many cases, we may want to present the user with an alert dialog (such as when an error occurs, or when the app needs user confirmation), or an action sheet (such as when the app should present the user with many options).

For these cases, we need to implement another MvvmLight interface, IDialogService. As with INavigationService, I’m keen on extending the standard implementation, so I can add my own  functionality that I expect of other frameworks.

	/// <summary>
	/// Extends <see cref="GalaSoft.MvvmLight.Views.IDialogService"/>
	/// </summary>

	public interface IDialogService : GalaSoft.MvvmLight.Views.IDialogService
	{
		Task ShowError(string message, string title, string cancelText, string retryText, Action<bool> afterHideCallback);
		Task ShowError(Exception error, string title, string cancelText, string retryText, Action<bool> afterHideCallback);
		Task ShowActionSheet(string title, string cancel, string destruction, string[] buttons, Action<string> afterHideCallback);
		Task ShowActionSheet(string title, string cancel, string[] buttons, Action<string> afterHideCallback);
	}

My full implementation can be found here, but let’s review some highlights first. If you are familiar with the standard IDialogService interface, you’ll notice that I am overloading the two ShowError methods, so that we can take different actions based on the user selection. In practice, this is used like so from the ViewModel:

            await _dialogService.ShowError("This is a fake error to demonstrate the alert", "Yikes!", "Cancel", "Retry", response =>
            {
                if(response)
                {
                    // TODO: If response is true, the user wants to "retry" the action.
                    // Do something here to attempt the action again
                }
                else
                {
                    // TODO: If response is false, the user has canceled the attempted action
                    // So perform an action here that makes sense; we may want to naviagte to
                    // a previous page or just sit idle
                }
            });

We also add the ability to show the Action Sheet, which (curiously) is not part of the default IDialogService.

            await _dialogService.ShowActionSheet("Actions", "Cancel", "Exit", new string[] { "Action1", "Action2" }, response =>
            {
                // TODO: Like with the Error Dialog, we want to perform different actions based on the user selection
                switch(response)
                {
                    case("Action1"):
                    case("Action2"):
                    default:
                        _dialogService.ShowMessage($"Selected {response}", "I did it!");
                        break;
                }

            });

Conclusion

We did it! That concludes our tour of MvvmLight in Xamarin.Forms. Feel free to browse the complete source code, or leave a comment if you have questions!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s