Xamarin.Forms and MVVM
Xamarin.Forms allows to write UIs in XAML and also presents the options of using bindings to populate your UI with data. This again allows to write apps in a MVVM-Style. I’m a huge fan of Laurent Bugnions MVVM Light framework which since a few months not only supports Xamarin.Forms but also iOS, Android and Windows (WPF, Store Apps, Silverlight et al). It comes with many little helpers for implementing the MVVM pattern and giving you a nice performance boost i.e. no time lost hassling around with boilerplate code.
In this post I’ll show you how to:
- Install MVVM Light to your project(s)
- Setup the Inversion of Control (IoC) Container
- Bind the ViewModel to the View
- Implement a Command
- Calling code behind from your ViewModel
Installing MVVM Light
MVVM Light is a NuGet package that you can simply download by using the NuGet package manager. Simply right click on your solution, Manage NuGet Packages for Solution…, under Online search for MVVM Light. Finally install the MVVM Light libraries only package to all the projects in your solution.
Setting up the Inversion of Control Container
First we have to do a little bit of setup and configuration. This might seem as an overhead at first but over time this will really pay off as we will be able to use the IoC container to easily switch between stubs and real implementations simply by changing a line of code.
Lets create a basic ViewModel that will provide a List of full names from a list of people that is provided over a service.
public class MainViewModel : ViewModelBase{ private readonly IPeopleService _peopleService; ObservableCollection<Person> People { get; set; } public MainViewModel(IPeopleService peopleService) { if (peopleService == null) throw new ArgumentNullException("peopleService"); _peopleService = peopleService; } public async Task Init() { if (People != null) return; People = new ObservableCollection<Person>(await _peopleService.GetPeople()); }}
Now lets create the people service that will be an implementation of the following interface.
public interface IPeopleService{ Task<IEnumerable<Person>> GetPeople();}
Which results in the following class.
public class PeopleServiceStub:IPeopleService{ public Task<IEnumerable<Person>> GetPeople() { const int numberOfPeopleToGenerate = 100; return Task.Run(() => GeneratePeople(numberOfPeopleToGenerate)); } private IEnumerable<Person> GeneratePeople(int personCount) { var people = new List<Person>(personCount); for (int i = 0; i < personCount; ++i) { people.Add(new Person(NameGenerator.GenRandomFirstName(), NameGenerator.GenRandomLastName())); } return people; }}
Now we have to setup the IoC container which will automatically inject the person service we just defined.
public class Locator{ /// <summary> /// Register all the used ViewModels, Services et. al. witht the IoC Container /// </summary> public Locator() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); // ViewModels SimpleIoc.Default.Register<MainViewModel>(); // Services SimpleIoc.Default.Register<IPeopleService, PeopleServiceStub>(); } /// <summary> /// Gets the Main property. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This non-static member is needed for data binding purposes.")] public MainViewModel Main { get { return ServiceLocator.Current.GetInstance<MainViewModel>(); } }}
If we ever wanted to consume the list of people over a web service we could now simply implement a new class according to the interface. In the Locator class we would then have to simply change the lines to use the new implementation and all classes that require the interface in the constructor would receive the new implementation. This becomes a very powerful method for developing mobile applications without having the backend yet finished or present as it might also still be in development or you want to do a demo and not rely on network connectivity.
In a last step we have to register the Locator to the application context. This is done in the App.cs class by adding the following lines:
private readonly Locator _locator;public Locator Locator{ get { return _locator; }}public App(){ _locator = new Locator(); ...}
Binding the ViewModel to the View
Now that we have all the “business logic” setup lets display it to a Xamarin.Forms XAML page. Here fore add a new Xamarin.Forms XAML page to the PCL project. In the code behind set the binding context.
public partial class MainPage : ContentPage{ public MainPage() { InitializeComponent(); BindingContext = App.Locator.Main; } // ...}
And adjust the XAML to display our list.
<?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="XamarinFormsMVVMLight.MainPage"> <ListView x:Name="PeopleListView" ItemsSource="{Binding People}" ItemSelected="ListView_OnItemSelected" > <ListView.ItemTemplate> <DataTemplate> <TextCell Text="{Binding FullName}" /> </DataTemplate> </ListView.ItemTemplate> </ListView></ContentPage>
If we run the app now it will display all the names we have generated. In a next step lets navigate to a detail view that presents the selected person.
public partial class MainPage : ContentPage{ // ... private void ListView_OnItemSelected(object sender, SelectedItemChangedEventArgs e) { Navigation.PushAsync(new DetailPage((Person) PeopleListView.SelectedItem)); }}
On the detail page lets add a button that allows that we will connect to a RelayCommand in the ViewModel. The RelayCommand is a neat helper provided by MVVM Light helping you create an ICommand implementation with minimal code.
public class DetailViewModel : ViewModelBase{ public DetailViewModel() { // ... ClickMeCommand = new RelayCommand(ClickMeCallBackAction); } public ICommand ClickMeCommand { get; set; } // ...}
#
Invoking UI Code from your ViewModel
When selected we will display the selected user name in a popup. Now we do not want to have any UI dependent code in our ViewModel as this would mean we could not port our ViewModel to other platforms e.g. Xamarin.Android, Xamarin.iOS or Windows. Further we would not be able to write any unit tests as it would always require a the UI thread to be present. So we will have to display the MessageBox in our code behind. For this we can implement a small Callback function in our ViewModel:
public class DetailViewModel : ViewModelBase{ public DetailViewModel() { // ... } // ... public Action ClickMeCallBackAction { get; set; }}
Which is set in our code behind with the appropriate action:
public partial class DetailPage : ContentPage{ public DetailPage(Person person) { // ... viewModel.ClickMeCallBackAction = () => DisplayAlert("Hello", viewModel.Person.FullName, "Ok"); }}
This allows our ViewModel to call UI code without having to know the implementation. I recommend setting the callback actions to empty functions in the constructor to prevent any null exceptions.
public class DetailViewModel : ViewModelBase{ public DetailViewModel() { ClickMeCallBackAction = () => { }; // ... } // ...}
Conclusion
MVVM is a great pattern for separating your UI from your business logic. MVVM Light is a great helper integrating MVVM without having to invest a lot of time into boiler plate code i.e. focus your time on bringing the biggest value to your customer continuously over the duration of the project.
You can find the entire code on GitHub.
Technorati Tags: Xamarn.Forms,MVVM,XAML,MVVM Light