Tuesday, July 29, 2014

MVVM in Xamarin Forms

Ah, another pattern to learn. But this one actually fits my way of thinking. I'm going to explain MVVM as it is used in Xamarin Forms. I'll start with a brief definition of some key terms and finish off with a walk-through and then a quick "what to do next" section.

There are other MVVM frameworks, like MvvmCross and some work in progress by the guys at Xamarin Forms Labs. This post is just about the bare metal, without third party involvement. We're going to keep it clean.

So MVVM you say

I can think of several cool things about MVVM. The first is that it's a rare FOUR (4) letter abbreviation instead of the IT industri standard of three. Don't get confused by this, it is a real pattern anyhow. If I know my IT history correctly I think it was Microsoft who first coined the expression. To be specific it was John Gossman in 2005, while working on WPF.

MVVM stands for Model-View-ViewModel. A quick definition of each component below.

Model

The model is your data... A domain model, a text-file or your data access layer. The most important point is that the data has no knowledge about the View or the ViewModel. It lives its own life. There are two different approaches.

  • The data-centric where the model is a text-file or a data access layer. 
  • The object-centric where the model is a domain model.

View

The view just renders the data. In Xamarin Forms this is a Page. The data is bound using data binding. If you use Xaml, it would look something like this:

<Label Text="{Binding Title}" VerticalOptions="Center" HorizontalOptions="Center" />

The view is like, I don't care what I'm bound to, except that it needs to have a property called Title that is a string. Binding can also be more complex, like two-way that updates when the ViewModel changes. The ViewModel-class would then have to implement the INotifyPropertyChanged interface so that the view would be aware of the change. Most other MVVM-frameworks have base classes that helps you do this. We're not going to bother with those in this post.

The view also generates a code behind file that in its best form should look something like this

using MvvmSample.ViewModels;
using Xamarin.Forms;

namespace MvvmSample
{    
    public partial class MainPage : ContentPage
    {    
        public MainPage ()
        {
            InitializeComponent ();
            BindingContext = new MainPageViewModel ();
        }
    }
}


It should in my perfect world only call InitalizeComponent cause it needs to and then directly set the BindingContext. Any logic should be handled by the ViewModel. If you start writing code in the code behind for the View, your a bad person... Simple as that...

There are many other ways to instantiate the ViewModel, for example via a Factory or Inversion of Control. Most likely you would want to defer the instantiation of the ViewModel away from the view itself.

ViewModel

This is the part I love the most about MVVM. The ViewModel is a smart class that feeds the View with data and listens to events (commands) raised by the view. It contains properties and commands that the view binds to. It mediates between the view and the model. This way we can keep the view and the model totally unaware of each other without gettings stuck in architectural hell.

For MVVM to really shine you need to implement INotifyPropertyChanged on the ViewModel. This lets the View know and respond to the fact that the underlaying data changed. The interface is very simple

Commands

Let's say we have a button in our view defined as below.

<Button Command="{Binding Click}" 
            Text="Click me!" 
            VerticalOptions="Center" 
            HorizontalOptions="Center"  />

The binding of the Command-property specifies that the ViewModel exposes an property that returns an instance of an object that implements ICommand. A very simple implementation of this could look like this.

        public ICommand Click
        {
            get {
                return new Command (() => {
                    Debug.WriteLine ("Click");
                });
            }
        }

Worth noting is that the constructor of the Command-object has four overloads. Two of them requires a Func that gets called by the view to determine if the button can be clicked or not. The cool thing is that the code to determine this is in the ViewModel, not the view.

       public Command (Action execute, Func canExecute)

Binder

The Binder is the thingy that makes sure the data and events gets passed between the View and the ViewModel. It can bind one-way or two-ways. An example of a two-way binding is when a property in the ViewModel changes, the bound element in the View also updates. In Xamarin, the binder is a property on the BindableObject class this is a base class deep down for all page types. You set bindings as showed in Xaml above or with code within your view (Page) like below.

  var label = new Label ();
  label.SetBinding (Label.TextProperty, new Binding ("Title"));

This binds the text property to a property called Title on the BoundingContext (your ViewModel that is). I find the Xaml-way easier to deal with since you don't get any code in your code behind. The code is does the same thing as the Xaml-sample earlier.

A walk-through


Initial project setup

Create a new Blank App under C#/Mobile Apps. I prefer the PCL template, but you could go with shared if it suits you better.


Add three folders called Views, ViewModels and Models. This structure is just a preferens of my own liking and you could organize your project anyway you'd like.

Add a page (view)

In the View-Folder, add a page called MainPage.xaml and add the following Xaml.

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="MvvmSample.MainPage">

    <StackLayout VerticalOptions="Start"
        HorizontalOptions="Center" 
        Padding="0,30,0,0" >
        
        <Label Text="Name" />
        <Entry Text= "{Binding Name}" />

        <Label Text="Last updated" />
        <Label Text="{Binding Updated}" />

        <Button Command="{Binding Save}" 
            Text="Save my name" 
            VerticalOptions="Center" 
            HorizontalOptions="Center"  />

    </StackLayout>
</ContentPage>


In the code-behind, add this:

using MvvmSample.ViewModels;
using Xamarin.Forms;

namespace MvvmSample
{    
    public partial class MainPage : ContentPage
    {    
        public MainPage ()
        {
            InitializeComponent ();
            BindingContext = new MainPageViewModel ();
        }
    }
}


Add the ViewModel

The ViewModel is the glue between your view and your data. Add a class called MainPageViewModel in the ViewModels folder.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Xamarin.Forms;

namespace MvvmSample.ViewModels
{    
    public class MainPageViewModel : INotifyPropertyChanged
    {
        public MainPageViewModel ()
        {
            Updated = DateTime.Now.ToString();
            Name = "Minecraft Steve";
        }

        private string _name;
        public string Name
        {
            get 
            { 
                return _name; 
            }
            set 
            {
                _name = value;
                OnPropertyChanged();
            }
        }

        private string _updated;
        public string Updated
        {
            get 
            {
                return _updated;
            }
            set 
            {
                _updated = value;
                OnPropertyChanged ();
            }
        }

        public ICommand Save
        {
            get {
                return new Command (() => {

                    // TODO - Save the data somewhere
                    Updated = DateTime.Now.ToString();
                });
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) 
            {
                handler(this, new PropertyChangedEventArgs (propertyName));
            }
        }
    }
}


This is where the bulk of our logic sits. The main points to focus at is the following:

  • The ViewModel implements INotifyPropertyChanged that defines an event called PropertyChanged. Simply raise this event every time a property changes.
  • The ViewModel is in charge of talking to the model. This is stubbed out in the Save command. Also notice that the save command updates the Updated property. This in turn leads to the event PropertyChanged being raised, which makes the View update (Courtesy of the binding mechanism in MVVM).
  • In the OnPropertyChanged function, notice the CallerMemberName attribute. It's syntactic sugar that tells the compiler to replace the argument with the name of the member who calls it. In this case, the property name. I makes your code look a little nicer, but it could be replaced with the actual name in each call from the properties setters. 

Tell the app where to begin

In App.cs, alter the GetMainPage method to return a new instance of the MainPage.

using System;
using Xamarin.Forms;

namespace MvvmSample
{
    public class App
    {
        public static Page GetMainPage ()
        {    
            return new MainPage ();
        }
    }
}

The finished app

Ok, this is pretty much the lamest app in the book, but here's the result. You can change the name and when you hit "Save my name" the ViewModel is updated resulting in the view rebinding to the new data.

What about the model?

Let's add a fake backend to this incredible app. Add the following code in the Models folder. Split it in two files if it makes you feel better.

using System;

namespace MvvmSample
{
    public class Person
    {
        public string Name {
            get;
            set;
        }

        public string Updated {
            get; 
            set;
        }
    }

    public class PersonRepository
    {
        private Person _theOnlyPerson;
    
        public PersonRepository ()
        {
            _theOnlyPerson = new Person () {
                Name = "Johan Karlsson",
                Updated = DateTime.Now.ToString()
            };
        }

        public Person GetPerson(int id)
        {
            return _theOnlyPerson;
        }

        public void Update(Person person)
        {
            _theOnlyPerson = person;
        }
    }
}

Open up the ViewModel again and replace it with the code below. This doesn't change the way the app works but it gives you a boiler plate for how to relate to the model. I prefer to let the ViewModel fetch the back end objects and map them to properties of the View Model and then refetch and update in the Save command. This way you get a single "transactable" call to the repository if you're using one.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Xamarin.Forms;

namespace MvvmSample.ViewModels
{    
    public class MainPageViewModel : INotifyPropertyChanged
    {
        private PersonRepository _repository;

        public MainPageViewModel ()
        {
            // This should be injected
            _repository = new PersonRepository();

            // Populate the ViewModel
            var person = _repository.GetPerson(42);
            Name = person.Name;
            Updated = person.Updated;
        }

        private string _name;
        public string Name
        {
            get 
            { 
                return _name; 
            }
            set 
            {
                _name = value;
                OnPropertyChanged();
            }
        }

        private string _updated;
        public string Updated
        {
            get 
            {
                return _updated;
            }
            set 
            {
                _updated = value;
                OnPropertyChanged ();
            }
        }

        public ICommand Save
        {
            get {
                return new Command (() => {

                    // Perform some logic
                    Updated = DateTime.Now.ToString();

                    // Store data to back end
                    var person = _repository.GetPerson(42);
                    person.Name = _name;
                    person.Updated = _updated;
                    _repository.Update(person);
                });
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) 
            {
                handler(this, new PropertyChangedEventArgs (propertyName));
            }
        }
    }
}


What now?

When you feel comfortable with how the View, Model and ViewModel interact you should look at Inversion of Control. I think that is the next logical step to achieve a good base for your mobile app.

No comments:

Post a Comment