Chapter 3. MVVM—the model-view–view model design pattern

This chapter covers

  • A more detailed look at what’s in the model, view model, view, and binding layers
  • How a view model provides cross-platform UI logic by modeling state and behavior
  • Using property-changed notifications
  • Using commands
  • How the binding layer glues the view and view-model layers together
  • Value conversion in the view model and value converters
  • What happens in the application layer
  • Navigation patterns for MVVM apps

In the previous chapter we looked at the MVVM UI design pattern, before creating our first cross-platform example app. We’re going to examine that example app in a lot more detail, but first we need to look at the layers in an MVVM app in more depth. To do this we’ll take an example calculator app (figure 3.1) and look at how we’d write this using MVVM.

Figure 3.1. A simple square-root calculator app that calculates the square root of a given number

To understand how to build this app we need to look at how the user will interact with the UI, and see how those interactions move up and down through the layers of MVVM. Figure 3.2 shows a high-level overview.

Figure 3.2. A typical user interaction with our square-root calculator

At the end of this chapter, we’ll revisit this app diagram, breaking each layer apart and seeing all the interactions that take place between each layer. This chapter is theory rather than practice, but it’s important in understanding how to structure your app to get the most out of the cross-platform capabilities that Xamarin offers. The code examples here are simple examples and pseudocode, not parts of a fully working app. In the next chapter we’ll be taking what you learn here and using it to understand and build on the example app you built in the previous chapter.

Let’s start by looking at how this app could be split between the different layers.

3.1. The model layer

The model layer is a cross-platform layer that represents your data, your business logic, and your access to external resources such as databases or web services. The simple calculator app doesn’t need to access any external resources, but if you did need to persist data to a database or interact with web services, you’d do this in the model layer.

In our calculator example, the model layer would contain a square-root calculator class that takes a number, calculates the square root, and makes the result available, similar to the structure shown in figure 3.3.

Figure 3.3. The model layer with the classes for the calculator app

The following listing shows a possible implementation. The class has a number property, Number, a Sqrt method that calculates the square root of the number, and a read-only Result property that stores the result.

Listing 3.1. A possible implementation of SquareRootCalculator
public class SquareRootCalculator
{
   public double Number {get; set;}
   public double Result {get; private set;}

   public void Sqrt()
   {
      Result = Math.Sqrt(Number);
   }
}

The model layer is a layer—it contains one or more classes working together. As you’ll see later in this chapter, you’ll usually have one view whose name is suffixed with View for a screen (for example, SquareRootView), and one view model for that view with a name suffixed with ViewModel (such as SquareRootViewModel). It’s normal to assume that there should be a corresponding Model class providing the data and business logic for that view model, but this doesn’t have to be the case. If you want to write your code that way, go ahead, but don’t feel you have to.

There are many ways to build the model layer following many different patterns and practices (such as domain-oriented or data-centric approaches). How you build this layer is up to you, but there are a few main principles you should stick to to make this layer the first M of MVVM:

  • The code should be cross-platformOne of the reasons for using this pattern is that it allows you to reuse as much code as possible.
  • The code should be testableAnother key reason for using MVVM is testability—the segregation of the UI from its logic means you can unit-test that logic, and this same principle should apply here. Your model layer should be testable using unit tests—your classes should be well written with single responsibilities so tests can be clearly defined. Again, thinking of our calculator app, the SquareRootCalculator class is very easy to unit-test. You could write tests that set different values for Number, call Sqrt, and verify the Result property. This is a trivial example, but even in a more complicated app you’ll need to ensure that it’s testable. This way you can ensure your model works without having to always build and run your app.
  • The model should represent data and business logic at the domain level, not the UI levelThis is an important principle of the model layer—it should represent your data and logic at a level that makes sense to your domain. Any value conversion of the data in business terms to UI terms shouldn’t be performed at this layer. Thinking again about our calculator app, the UI controls for entering values and showing them usually deal with string values. Strings are no good here as you need to calculate using numbers, so the model should always think in terms of numbers. The other layers can deal with strings and conversions.
Unit testing

Unit testing is a massive topic, worthy of a book in its own right, so I won’t be going into much detail about it here. All I will be covering is how to approach writing your app using MVVM to help with writing your unit tests. If you want to read more on this topic, I recommend The Art of Unit Testing, Second Edition, by Roy Osherove (Manning, 2013).

3.2. The view-model layer

The view-model layer (the VM at the end of MVVM) is the UI logic layer. This layer is responsible for two things:

  • Value conversionFrom data in the model layer represented in a way that makes sense to your domain to the way data is represented in the UI
  • UI logicSuch as logic that determines when to show data and when and how to navigate between different views

There are a few basic principles behind a good view model:

  • Just like the model layer, it should be cross-platform.
  • Again, like the model layer, it should be easily testable using unit tests. You want to have as high-quality an app as possible, so being able to test the UI logic quickly and thoroughly using unit tests will help you achieve this goal.
  • It must be bindable. Binding is the glue that connects the view model to the view, and the view model will need to implement features such as property-changed notifications that allow the binding layer to be aware of changes so that it can keep the UI and view model in sync.
  • It should be stateless. The view model is a value conversion and logic layer. It’s not a data store, so its state should always come from the model layer. When the UI changes the state (such as when a text box is updated) the binding tells the view model that something has changed and that the view model is responsible for updating the state in the model.

The view model is the meat of the MVVM pattern, and it will usually map one-to-one against the different screens or to different sections of each view. In our calculator app, we want a view model that wraps the model, called SquareRootViewModel (figure 3.4). If we had an app with multiple screens, maybe one for square roots and one for cube roots, we’d also have two view models, SquareRootViewModel and CubeRootViewModel, each accessing the model layer. Because our model layer is a layer and doesn’t map one-to-one with view models, we could have both square root and cube root in the same model class, and that one model would be used by both view models.

Figure 3.4. The view-model classes for the calculator app, with a view model that wraps the SquareRootCalculator

3.2.1. State and behavior

When considering a UI, there are really two things to think about—state and behavior.

  • State is the information you see on the screen, be it actual data, like text and numbers, or a representation of the app’s state, such as buttons being disabled or validation errors being shown around text boxes. State is a representation of the data in the model in a way that maps to the UI, using properties, just like the properties you’d put on a class.
  • Behavior is the actions that happen when a user interacts with the UI. The view model is the implementation of this. Behavior is represented using commands, objects that encapsulate some kind of logic, which is fired by interacting with the UI in a way that executes the command.

Think of driving a car. You’re driving at a certain speed, as indicated by the speedometer. By pressing the accelerator you go faster; by pressing the brake you go slower.

The state is the speed—represented in miles or kilometers per hour. The car determines its speed by measuring the speed of rotation of the driveshaft and converting this value into a vehicle speed. In this case, the driveshaft speed measurement is exposed to the speedometer as a representation of the driveshaft speed but it’s converted mathematically to the vehicle speed.

The behavior is the ability to change speed by pressing the accelerator or brake. When you press the accelerator, the engine allows more fuel/air in, making the engine go faster. When you press the brake, the wheels are slowed down using friction. The representation of how to increase speed is pressing on the accelerator pedal. The representation of how to decrease speed is pressing on the brake pedal.

The speedometer represents the engine speed, and the pedals represent the behavior of changing speed, all in a driver-friendly way. This is analogous to our MVVM layers. The model is the mechanicals of the car, and the view is the speedometer and pedals. The view model represents the vehicle speed and speed-change behavior to the speedometer and pedals in a way that’s consistent with the view.

If we consider our square-root app, we have one number and the ability to tap a button to calculate the square root and see the result. The state here is the number we want to calculate the square root of, as well as the result. The behavior is a command that encapsulates the logic to calculate the square root. By tapping the button, you command the view model to do something that does this calculation.

It probably sounds a bit contradictory to say that the view model represents state and behavior after saying that one of the basic principles is that it should be stateless. Let’s examine what’s meant by both things.

The view model represents the state of the UI in that all the values and logic that define the data shown in the UI come from the state of the view model as exposed to the view layer. The values in text box and label come from properties on the view model. The setting that defines whether a control is visible or hidden comes from the properties of the view model. In this sense, the view model provides a representation of the state of the model layer to the UI.

As a class, though, the view model should be stateless, in that it gets its state from the model layer and shouldn’t hold on to this state itself. The values in the text box and label are read from the view model, but the original source is the model layer (figure 3.5). At any time you should be able to recreate the view model from the data in the model layer, because it will not store any state itself.

Figure 3.5. The view model is a representation of the state shown in the model.

The real state is in the model layer, and the view model converts that state into state that’s appropriate for the view layer. The view model represents the state, but the model contains the state. By having the view model as a representation, you can return the state directly or perform value conversions on the state before returning it to the binding layer.

Properties and changed notifications

In its simplest form, a property of a view model is the same as any other property you may have used in C# code. It has a getter and a setter—methods that return some data or set the data. Internally in these methods, it could just return or update values, or it could have some logic. In its simplest form, a property can get and set a value on the model, as shown here.

Listing 3.2. Pass-through property that gets and sets first number value on the model
public class SquareRootViewModel                        1
{
   SquareRootCalculator sqrtCalc;                       2

   public double Number                                 3
   {
      get { return sqrtCalc.Number; }                   4

      set { sqrtCalc.Number = value; }                  5
   }

   public double Result                                 6
   {
      get { return sqrtCalc.Result; }
   }
}

  • 1 The SquareRootViewModel view model class. The convention is to name view models with the suffix “ViewModel”.
  • 2 The view model has an instance of the model stored as a private field.
  • 3 The view model exposes the number to be used in the calculation through the Number property.
  • 4 The getter for the Number property is a simple pass-through—it just returns the value of the property on the underlying model.
  • 5 The setter is also a simple pass-through, setting the value on the underlying model.
  • 6 The Result is also a pass-through, but it’s read-only on the model so it’s only a getter, not a setter.

So far, so simple. In fact, you’re probably wondering why we bother with a view model at all if it just calls straight in to the model. The reason for using a view model is because view models support property-changed notifications—the raising of an event to tell anyone who’s interested that a property has changed. Remember the binding layer? This keeps the UI in sync with the underlying data, and part of keeping this in sync is being aware of when things change. Figure 3.6 is a recap of binding, highlighting this.

Figure 3.6. The binding listens to changes in the view model and updates the view accordingly.

The way the binding layer does this is through property-changed notifications. These are events raised by the view model telling anyone who’s interested that a property has changed. In our case, the binding layer is interested, so it listens to these notifications. When it gets one, it will read the new value of the property and update the UI to match.

The standard way of implementing property-changed notifications in C# is though an interface called INotifyPropertyChanged. This interface has been around since .NET 2.0 (over a decade ago), and has only one member, an event called PropertyChanged, which uses the standard event-handler delegate, passing an object that defines the sender and some event arguments. These arguments are of type PropertyChangedEventArgs, and this type only has one member of note—PropertyName, the name of the property that has changed as a string. The following listing shows this interface.

Listing 3.3. The INotifyPropertyChanged interface
public interface INotifyPropertyChanged
{
   event PropertyChangedEventHandler PropertyChanged;
}

This event doesn’t include the value that’s changed, just the name. The binding layer will subscribe to this event, and when it’s raised it will get the name of the property from the event args, find the UI control (or controls) that’s bound to a property of that name, read the new value from the view model, and update the UI.

Notifying that all properties have changed

By convention, if you use an empty string or null as the property name in the event args when raising this event, it tells the binding layer that everything has changed, so it should reread all values and update the UI. Be warned, though; not every MVVM framework will obey this convention.

We can update the view model example to implement this. When the number changes, or the result of the square root changes, we need to notify the binding of this change via a property-changed notification. One thing to note here is that your view model should only notify if something has actually changed—if the value hasn’t changed, the event shouldn’t be raised.

Let’s add a Sqrt method for our view to illustrate this.

Listing 3.4. Adding property-changed notifications to our view model
public class SquareRootViewModel : INotifyPropertyChanged                1
{
   public event PropertyChangedEventHandler PropertyChanged;             2

   void RaisePropertyChanged(string name)                                3
   {
      PropertyChanged?.Invoke(this,
                              new PropertyChangedEventArgs(name));       4
   }

   public double Number
   {
      get { return sqrtCalc.Number; }
      set
      {
         if (sqrtCalc.Number == value) return;                           5
         sqrtCalc.Number = value;
         RaisePropertyChanged("Number");                                 6
      }
   }

   public void Sqrt()
   {
      sqrtCalc.Sqrt();
      RaisePropertyChanged("Result");                                    7
   }
   ...
}

  • 1 The view model needs to implement INotifyPropertyChanged.
  • 2 The PropertyChanged event comes from the INotifyPropertyChanged interface.
  • 3 The RaisePropertyChanged method is a helper method to raise the property-changed event for a given property.
  • 4 The event is raised using event args that contain the name of the property that has changed.
  • 5 When the Number property is set, the new value is compared to the old one, and if the value hasn’t actually changed, nothing happens: no update and no property-changed notification.
  • 6 After the Number property changes, a property-changed event is raised.
  • 7 After the Sqrt method on the model is called, the Result property is updated so the event is raised.
Simplifying RaisePropertyChanged using an attribute

C# defines an attribute called CallerMemberName that you can set on a string parameter of a method, and it tells the compiler to use the name of the calling method or property as the value for this parameter. This means you can define your property-changed method as follows:

void RaisePropertyChanged([CallerMemberName]string name = null)

Then you can call it using RaisePropertyChanged() from inside your property setter, without passing any explicit value for name to the method. The name of the property this is called from will be automatically set as the name parameter. For example, if you call this from inside the setter of the Number property, the value of the name parameter will be “Number”. A number of MVVM frameworks, including MvvmCross, use this for their raise-property-changed methods.

Property-changed notifications are the way to tell the binding layer that something has changed. You can notify about any property at any time—you don’t have to notify about the property being changed. For example, if your view model had two properties for a person’s first and last names, and a property that reflected their whole name as a concatenation of the first and last names, you’d want any changes to either the first or last name to raise a property-changed notification for the whole name, as shown in the following listing.

Listing 3.5. Property-changed notifications can be raised for any property at any time
public string Name
{
   get { return FirstName + " " + LastName; }     1
}

public string FirstName
{
   get { return model.FirstName; }
   set
   {
      if (model.FirstName == value) return;
      model.FirstName = value;
      RaisePropertyChanged("FirstName");
      RaisePropertyChanged("Name");               2
   }
}

  • 1 The Name property is dependent on the values of FirstName and LastName.
  • 2 Because of this dependency, when FirstName changes, it raises a property-changed notification for itself and for the Name property.
Collections and collection-changed notifications

In addition to using individual properties, there’s a standard way in C# of notifying the binding layer that the items in a collection have changed: using a similar interface called INotifyCollectionChanged. This is generally used with list controls—UI widgets that show a list or table of data. Just like with INotifyPropertyChanged, the binding layer subscribes to an event, and when it receives this event, it will tell the list control to reload the changes.

Unlike INotifyPropertyChanged, this isn’t an interface that the view model defines; instead, this is at the property level. The view model will expose a property of a type that implements INotifyCollectionChanged, and when the binding layer binds this property to a corresponding property on the list control, it will also subscribe to the event on that property.

An example of this, with an app that shows a list of names, is shown in figure 3.7.

Figure 3.7. Collections can be bound to list controls, and when the collection changes, the list control on the UI is updated.

This interface just contains one member, an event called CollectionChanged. This uses the standard event-handler delegate with event args of type NotifyCollectionChangedEventArgs. The following listing shows this interface.

Listing 3.6. The INotifyCollectionChanged interface
public interface INotifyCollectionChanged
{
   event NotifyCollectionChangedEventHandler CollectionChanged;
}

These event args contain a number of properties allowing you to describe the changes that have been made to the collection. This, in turn, allows the bound list control to respond appropriately if possible.

For most use cases, though, you don’t need to worry too much about this because there’s a nice helpful collection that’s already part of the .NET Framework that handles all of this for you—ObservableCollection<T>. The collection is derived from the generic List<T> and implements INotifyCollectionChanged. When you perform any action that changes the list, it will raise the event with the correct arguments.

When the underlying ObservableCollection changes (such as when an item is added to it), the event is raised, and the binding detects this and tells the list control to update and show the changes. This is shown in figure 3.8.

Figure 3.8. When an observable collection is updated, an event is raised and the binding detects this and tells the UI to update.

Be aware, though, that ObservableCollection will raise the CollectionChanged event for all changes, so if you’re adding thousands of items, the UI will update thousands of times, which can slow or even lock up your UI. It’s probably better in this case to create a new collection, add all the values to it, and then set your property to this new collection—leading to only one UI update. The following listing shows an example of this.

Listing 3.7. Creating a new collection and updating the property
public ObservableCollection<string> Names {get; private set;}
void AddLotsToTheCollection(IEnumerable<string> lotsOfNames)
{
   var newCollection = new ObservableCollection<string>(Names);   1

   foreach (var name in lotsOfNames)
      newCollection.Add(name);

   Names = newCollection;                                         2
   RaisePropertyChanged("Names");                                 3
}

  • 1 A new observable collection is created, copying the values from the existing collection.
  • 2 The public property for the observable collection is updated to the newly created collection.
  • 3 A property-changed notification is raised to tell the UI to use the new collection.
Other implementations of ObservableCollection can make this even easier

There are a number of implementations of ObservableCollection available in various open source projects that provide better support for bulk operations by blocking the collection-changed event until all operations are complete. One such implementation from MvvmCross is MvxObservableCollection, which has an AddRange method that suppresses the collection-changed event, adds all the items passed to the method, and then raises the collection-changed event. This collection also provides methods for bulk deletes and replace-ments and for suppressing the collection-changed event while you perform custom operations.

Commands

The properties of the view model define its state, so the next thing to look at is how behavior is defined. The standard way to define behavior in MVVM is using the command pattern. In this pattern, everything needed to perform an action is encapsulated in an object, and you tell this object that you want it to perform its action at a certain time, giving it any extra information it needs about the particular time it’s run.

Think of a genie—your wish is its command. You tell the genie that you want a coffee, and it obeys your command using its magic, and poof, a coffee appears, as shown in figure 3.9.

Figure 3.9. Commanding a genie to bring you coffee. It would have been eternal wealth, but coffee was easier to draw.

You can think of the command pattern the same way. The command is an object that encapsulates the ability to perform an action, such as a genie who encapsulates the ability to grant your wish. You execute the command with an optional parameter, commanding the genie to bring you coffee. The command then performs the action—the genie brings you coffee.

In the C# world, ICommand is the interface for an object that implements this command pattern. It has a method you can invoke to execute the command with a parameter, a method you can call to see if you can execute the command with a parameter, and an event that gets raised when your ability to execute the command changes as shown in the following listing.

Listing 3.8. The ICommand interface
public interface ICommand
{
   void Execute(object parameter);
   bool CanExecute(object parameter);
   event EventHandler CanExecuteChanged;
}

As shown in figure 3.10, you can think of Execute as a method that commands the genie to grant your wish, and the parameter as the thing you wish for. Traditionally, a genie will only grant three wishes, so figure 3.11 shows that CanExecute will return true while you have wishes remaining, but after your third wish will return false. The CanExecuteChanged event is like the genie telling you after your third wish that you’ve run out of wishes (and disappearing in a puff of smoke back into the lamp).

Figure 3.10. Our genie is like the ICommand interface—we can make a wish (Execute) and see when we’ve run out of wishes (CanExecuteChanged).

Figure 3.11. We can also ask the genie if we can have any more wishes (CanExecute).

The command is exposed as a property on the view model, and the binding layer will have a way to wire up the command to a widget on the UI.

The classic use case is with a button. Buttons usually have a click event, or a similar event that’s run when the button is tapped. When a button is bound to a command, the click event will Execute the command. The enabled state of the button would also be bound to the CanExecute method, so if CanExecute returns true, the button is enabled, and if it returns false, the button is disabled. This would be evaluated when the button is first bound and every time the command raises the CanExecuteChanged event. This is shown in figure 3.12.

Figure 3.12. Events such as button clicks can be bound to commands, and these commands are executed when the event is raised.

In our calculator app, the Square Root button would be bound to a command that, when executed, calls the Sqrt method on the model. If you haven’t entered a number into the text box, you can’t calculate a square root, so in this case the CanExecute will return false and the button will be disabled. Once you enter text, the CanExecuteChanged will be raised to tell the binding to re-evaluate CanExecute and enable the button.

Commands don’t return a result, they just run and return once they’ve finished. The way to return a result, if one is required, is by making changes to the state of the view model and raising a property-changed event. Some commands don’t need to update a state because they don’t do anything that requires feedback on the current UI. Commands that do need to update, such as saving data and indicating that the data has been saved, will do it by updating a property that causes the UI to change.

Usually you don’t need much fancy logic with a command—just create a command object and give it a method to run when it’s executed. Unfortunately (and somewhat surprisingly) there isn’t a default ICommand implementation built into the .NET Framework that does this. Luckily there are plenty of example implementations around the internet and others are built into the various MVVM frameworks. These commands usually take in an Action that provides the method to run on execution, and optionally a Predicate (a method that returns a Boolean) to use for the implementation of CanExecute. They also provide a method you can call to raise the CanExecuteChanged event.

In our calculator view model, we can change the Sqrt method shown in listing 3.4 to a command, as follows.

Listing 3.9. Adding a command to the view model
public class SquareRootViewModel : INotifyPropertyChanged
{
   ...
   public ICommand SqrtCommand {get; private set;}      1

   public SquareRootViewModel()
   {
      SqrtCommand = new MvxCommand(o =>                 2
         {
            sqrtCalc.Sqrt();
            RaisePropertyChanged("Result");
         });
   }
}

  • 1 The Sqrt method has been removed, and a new property of type ICommand has been added, called SqrtCommand.
  • 2 Creates a new instance of the command

In the constructor for the view model, the SqrtCommand is set to a new instance of MvxCommand, the command class from MvvmCross that takes an Action<object> to execute (Execute takes an object as its parameter, so the action needs to have an object parameter). The action is invoked when the command is executed, and in this example the action calls Sqrt on the model and raises a property change to indicate the Result has changed.

3.2.2. Value conversion

The model contains data in a way that’s relevant to the domain or business logic; the UI handles data in a format that can work with the widgets on screen. Some of the time these formats will be the same, but other times they won’t match.

When they don’t match, the view model will need to convert the state of the model to a state that the UI can use. The view model will represent the state of the model using the converted values. Similarly, if the UI is updated, this needs to be reflected by updating the state represented by the view model. This means setting a value using data in the format relevant to the UI, and then converting it to the format used by the model. It’s the view model that’s responsible for this value conversion.

As you’ve probably noticed in our square-root calculator, the model deals with numbers as doubles. This is the business layer, so doubles are fine. UIs, on the other hand, don’t normally deal in doubles. Text boxes like the one used for entering the number usually deal in strings, and so do labels like the one we’re using to show the result.

This is where the view model comes in—part of its job is value conversion from the model layer to the UI layer. In this case it should be responsible for converting from strings in the UI to numbers in the model and vice versa.

Let’s look at how the view-model class code should work.

Listing 3.10. Handling value conversion from the model (doubles) to UI (strings)
public class SquareRootViewModel
{
   ...
   public string Number                                         1
   {
      get { return sqrtCalc.Number.ToString(); }                2
      set
      {
         if (value == Number) return;                           3
         sqrtCalc.Number = double.Parse(value);                 4
         RaisePropertyChanged("Number");
      }
   }

   public string Result
   {
      get { return sqrtCalc.Result.ToString(); }                5
   }
}

  • 1 The Number property on the view model is a string.
  • 2 To return a string, the getter calls ToString() on the double value from the model.
  • 3 Compares the value to the existing value using the property on the view model instead of the property on the model. The value is a string, so you need to compare it with a string instead of the double on the model.
  • 4 To set a double on the model, the setter parses the string into a double. This could fail, so in the real world you’d need to ensure the string value is always a valid number—most UI text boxes can limit which characters the user can enter to numbers and decimal points.
  • 5 Again, to return a string the view model calls ToString() on the value from the model.
View models are responsible for value conversion

The layers above the view model think in terms of the UI, and the layers below think in terms of the business logic and domain. The view model is responsible for converting from one to the other as data passes through this layer.

The model layer has data as doubles. The view-model layer converts these values to strings, and represents the state of the model layer through string properties. This state is in the right format for the UI layer, so the binding layer can set the text on the UI controls to these string values. Once a string value on the UI is updated, the binding layer updates the string representation of the number on the view model, which converts the value to a double and updates the data on the model.

There are times where you might want platform-specific value conversion rather than cross-platform conversion in a view model, and you can do this using a value converter. We’ll look at these later in the chapter.

3.2.3. Testability

Like the model layer, the view-model layer should be built with testability in mind. View models not only provide cross-platform logic, but when they’re well built you can write unit tests to verify that their logic is correct: one code base for this logic, one set of tests, one place to find and fix bugs. This is one of the major reasons behind the original invention of MVVM—you can write unit tests against your UI logic. It’s very easy to do thanks to the way view models encapsulate state and behavior. You can test user interactions with the UI by writing test code that replicates the way the binding would update the view model.

For example, to test a user typing into a text box, you don’t actually need a text box. Instead you can write code that acts like the binding layer and sets the value of the property on the view model that would be bound to the text box. To test updates coming to the UI from the view model, you just need to listen for property-changed or collection-changed events, and when these happen verify that the property or collection has the correct value. To test a user clicking a button, you just need to execute the command and verify what happens.

When building your view models, you should always think about unit testing. Your view models should be well decoupled and use techniques like interface over implementation, the same as for models. It’s also worth seeing what your MVVM framework offers to help you with this. For example, some frameworks provide a messenger to allow your view models to communicate indirectly with other view models (or other classes in your app) without having to be aware of each other.

To improve the testability of the SquareRootCalculator view model, we should decouple it from the model by exposing an interface on the model and passing an instance of that interface when the view model is constructed inside our app. From a unit test, we can create a mock model that implements this interface, and then use this when we construct the view model. This way we have complete control over what the model will do in the test.

As mentioned earlier, unit testing is a huge topic, and mocking is an important part of it. It’s outside the scope of this book, but if you always build your model and view-model layer code to prefer interface to implementation, you’ll be well set up for unit testing.

3.3. The view layer

Put simply, the view layer is the UI. Everything that has to be platform-specific because it deals with UI widgets is in the view layer. This layer should be as thin as possible and just contain code to define which widgets are needed on screen and the values of any of their properties that won’t change based on logic inside the view model. When you’re building your view, if you find yourself adding any logic, move it to the view model.

Thinking back to our calculator example, we’d need to create two views called SquareRootView, one in the iOS app and one in the Android. This naming is in keeping with the convention of the view and view model having the same name with a different suffix, as shown in figure 3.13.

Figure 3.13. The view layer is not cross-platform so the views have to be created twice.

As this layer isn’t cross-platform, you can add all the fancy UI goodness you want in this layer—nice looking widgets, animations, effects, and anything else you want that’s specific to the platform to make your UI look amazing. Just remember that because this layer isn’t cross-platform, everything has to be written twice, once for iOS and once for Android, so everything that can be shared (such as logic) should be shared in the view-model layer.

On both iOS and Android, there are two parts to any UI:

  • A layout fileContains details of the widgets defined in XML and can be used with a visual designer. Android calls these layout resources; iOS has two types of these, storyboards and XIB files.
  • A code-behind fileProvides any logic needed by the UI and defines its lifecycle (such as when the view is shown and when it’s hidden). On Android this is called an activity; on iOS this is a view controller.

We’ll look at these layout and code-behind files in more detail in chapters 9 through 12 when we look at building UIs for iOS and Android.

There’s not much more to add about the view layer in terms of MVVM. Most of the magic of MVVM is in the view-model layer, so the only thing to consider here is what you can put in the view-model layer and what has to be in the view layer. As a good rule of thumb, you want to do as much as possible in the layout file, and as little as possible in the code-behind. If you’re adding code that can’t be in the layout file for whatever reason, you should consider whether it’s generic logic that should be in the view model (and therefore shared between platforms) or if it’s platform-specific and must be in the view layer. For example, if you’re showing or hiding a label based on the value of a property in the view model, the logic for this should also be in the view model. If, on the other hand, you’re choosing which of a set of platform-specific animations you’ll use based on a property in the view model, this logic would go in the view layer—albeit ideally in a separate, self-contained class that could be unit-tested.

3.4. Binding

Binding is the magic that links together the view and the view model in a loosely coupled way. It’s responsible for connecting properties on the view to properties on the view model and keeping them in sync, and for connecting events on the view to commands on the view model so that these commands are run when the user interacts with the UI. When binding, you link up a named property on the view to a named property on the view model, and behind the scenes the binding framework will find the actual properties with the given names and wire them up—setting the view to match the value in the view model, and monitoring for changes so it can keep these values in sync.

There’s nothing in the .NET Framework to help bind everything together. Instead, you have to either write the logic yourself or use a framework such as MvvmCross, MVVM Light, or Caliburn.Micro to do it.

There are a couple of binding concepts to be aware of—what the source and target are, and what the binding mode is. You also need to be aware that binding isn’t really cross-platform, so it can help if you need to bind to properties whose types aren’t supported in your cross-platform code.

3.4.1. Source and target

When you bind a view to a view model, you connect a target to a source:

  • The source is the original source of data (the view model).
  • The target is the original target of the data (the view).

It’s easy to see how these definitions can be confusing—for a text-entry box on a new-user screen, the “source” of the data could be considered what the user enters, but from a binding perspective the source is always the view model and the target is always the view.

You will often hear the term binding source mentioned, and this refers to the view model. The binding source is also sometimes referred to as the binding context (Xamarin.Forms uses this name) or data context (if you’ve done WPF before, you’ll recognize this).

3.4.2. Binding mode

There are four possible modes for binding:

  • One timeThe binding happens once when the view is bound. The value in the view is set from the property in the view model once, and all changes are ignored. This is useful for static text or images that can’t change.
  • One wayThe binding goes from source to target only. Every time the view model changes, the view is updated. This makes sense for static controls such as labels where the value in the view can never be user-updated, but the view model may update due to changes from the model layer (such as getting a new value from a web service).
  • One way to sourceThe binding goes from target to source only. Every time the value on the view changes, the view model is updated. This isn’t used very often.
  • Two wayThe binding goes from source to target and target to source. Every time the property on the view model changes, the value on the view is updated, and every time the value on the view changes, the property on the view model is updated. For controls like text boxes, tick boxes, or radio buttons, this is usually the default binding mode.

3.4.3. Binding is not cross-platform

Binding is platform-specific, and it’s always set in the view layer. It needs to be, as it needs to understand the UI widgets to be able to set the data on them and listen for updates.

In our square-root calculator, we need to bind the text box that the user uses to enter the number to the Number property on the view model, bind the button to the SqrtCommand property, and then bind the result label to the Result property.

The binding has to be platform-specific to understand the UI widgets well enough to monitor for view-layer value changes. In figure 3.14 the binding needs to know how to detect changes to the text in the text box (for example, by handling a text-changed event), and how to detect a tap on the button (by handling a click event). On the view-model side, the binding will listen for property-changed notifications from the INotifyPropertyChanged interface to know when the view model has been updated. Once it gets this notification, it needs to know how to instruct the UI to update, such as knowing how to tell the label to show the result.

Figure 3.14. Binding connects the view and view model together in a loosely coupled way, but it needs to be platform-specific to know which properties and events in the view layer to use.

Binding uses reflection, so make sure your properties are visible

Binding needs to be able to find the properties on the source and target (view model and widget). How good the binding framework is at finding these depends on the framework, but it’s a good general practice to make your properties public and to verify how the framework works.

3.4.4. Value converters

Binding your cross-platform view model to your platform-specific code is great, but what about the times when types and even values are different between platforms? For example, with text boxes on both iOS and Android, you can bind the text to a string property in your view model—this works on both platforms. The problem comes if you want to show or hide the text box. On Android, visibility is controlled by an enum called ViewStates; on iOS it’s a Boolean called Hidden. Normally on your view model, you want a readable property such as ShowLabel that returns true for the widget being visible and false for it being hidden. This doesn’t map to the Android enum or the iOS Hidden property (it’s the inverse, because on iOS true means the widget is hidden, so not visible).

The way around this is through value converters. As you might recall, the view model is a value-conversion layer (as well as a UI logic layer) so it can do some things, but because it’s cross-platform it can’t convert values to platform-specific ones. This means we must have a small part of our value conversion in platform-specific code, using value converters. These are classes with the singular purpose of converting from view-model types to view types, and converting back from view types to view-model types. Although we want to keep as much UI logic in cross-platform code as we can, platform-specific value converters are sometimes necessary, as they have to know about the platform-specific implementations, and they can be encapsulated in a way that makes them unit-testable.

When binding, you can tell the binding framework to use a particular value converter. When a property on the view model is updated, the binding framework reads the new value from the view model, converts the value using the value converter, and sets the converted value on the view. Conversely, when the view updates, the binding framework will read the value from the view, convert it back using the value converter, and set the value on the view model. This is shown in figure 3.15.

Figure 3.15. Value converters allow you to convert a value from the view model to a type that the view is expecting.

In contrast to property-changed notifications, there isn’t a standard interface for value converters available everywhere. Microsoft defined one called IValueConverter for use in WPF applications, but this isn’t available in .NET Standard libraries, iOS, or Android apps. Instead, a number of MVVM frameworks provide their own versions, which are identical.

In MvvmCross there’s IMvxValueConverter. This interface is identical to IValueConverter and has two methods—Convert to go from source to target (converting the view-model value to one the widget is expecting), and ConvertBack to go from target to source (converting from the widget value to one the view model is expecting). This interface is shown in the following listing. To create a value converter, you can implement this interface in your class and pass your class to the binding layer.

Listing 3.11. The IMvxValueConverter interface
public interface IMvxValueConverter
{
   object Convert(object value, Type targetType,                   1
                 object parameter, CultureInfo culture);

   object ConvertBack(object value, Type targetType,               2
                        object parameter, CultureInfo culture);
}

  • 1 Converts changes from the source (view-model) value to the target (view) value
  • 2 Converts changes from the target (view) value to the source (view-model) value

The first parameter in both these methods (value) is the value you want to convert. These methods then return the converted value. The targetType parameter tells you what type the method should convert to, though this is normally ignored as value converters are usually pretty specific.

The parameter parameter can be useful if you want to have the value converter support a few different conversions and tweak the behavior when it’s called. For example, you could have a value converter that converts numbers that represent amounts of money to strings in particular currencies, and use parameter to specify what currency to use (such as £ or $). The culture parameter is useful if you’re supporting multiple languages, because it allows you to change your output based on the current localization settings. For example, if you’re converting a number to a string, you can change the decimal symbol to either a period or a comma based on the user’s country by passing the culture info to the ToString method on the number.

As with commands, no value converters are provided out of the box with the .NET Framework, but most MVVM libraries provide a few standard ones, such as converting Booleans to visibility flags. For example, MvvmCross provides MvxVisibilityValueConverter to map true values to visible and false values to invisible, and MvxInvertedVisibilityValueConverter to do the opposite.

3.5. The application layer

Most of the application layer is provided for you by the platform-specific code that’s built into the Xamarin iOS and Android SDKs. When you create your app projects, a few files will be autogenerated for you, containing some application configuration. Any modifications to this will generally be platform-specific changes, such as handling notifications on iOS or wiring up background services on Android.

There are some small things that can be configured in cross-platform code, but this depends very much on your MVVM framework. The main thing you can control here is the startup process.

Normally, the main Android activity or iOS view controller that’s loaded on application startup is defined at the application level, but a good MVVM framework will allow you to define this in cross-platform code. This is usually done by specifying the first view model to use. This allows you to put logic in the application layer that can be shared across both platforms and be unit tested.

A good example of a situation in which you might do this is an app that requires a user login. When the app is first loaded, your shared application code can see if there’s already a valid user account from some shared user-management code. If there is a valid account, it can load the main screen of your app from its view model, and if not, it can load the login screen view model. If this logic is in cross-platform code, you’ll only have to write this once, not once per platform.

In addition, the application layer can define how the different classes in your app are connected. For example, it can ensure that the SquareRootViewModel is constructed automatically using an implementation of SquareRootCalculator as the ISquareRootCalculator constructor parameter.

3.6. Navigation

The view-model layer provides as much UI logic as possible, and part of this UI logic is related to navigation—the act of moving from one screen to another in the app.

Imagine a company directory app that has two screens: one with a list of employees, and one that shows the details about an employee. The app provides navigation from the list to a single employee—when you tap on a person’s name in the list on the first screen, a new screen shows the details about that person, as shown in figure 3.16.

Figure 3.16. Navigating from one screen to another by tapping on a name in the people list

This kind of navigation is cross-platform in that regardless of how the UI is updated, we want to provide this navigation on both platforms. Both platforms will show the new screen and pass it data about which person was selected. The implementation on each platform is very different.

  • Android conceptualizes each screen as a separate activity that the user is undertaking, and the user has to express an intent to change their activity.
  • iOS thinks about each screen as a view on a part of the app, and the user segues from one view to another.

Both implementations mean the same thing from the user’s perspective—you see a different screen—but the terms used and the underlying classes and method calls are very different.

What is a “screen”?

Many different terms are used to define what we see on an app. At any one time, your app will fill the screen of the device and display some UI widgets showing state or providing behavior. At various times in your app, usually when you tap something, the whole screen is replaced with another full screen of widgets.

In this book I’ll use the term screen to refer to each distinct full screen UI, so in our calculator app there’s just one screen showing the square-root calculator. In the company directory app, there would be two screens—the first one showing the list of people, and the second showing the details of a specific person.

To see how we can solve this problem in a cross-platform way using MVVM, we first must consider what we really mean when we think of the screens in an app from an MVVM perspective. When we see a screen in our app, what we’re really seeing is a view and a view model—the view provides the UI widgets, and the view model provides the state and behavior. When we change screens, we’re changing both the view and view model that are shown. So what controls this changing of screens? Which layer handles the navigation?

You can have multiple views and view models in a screen

In our simple examples, there’s one view and one view model per screen, but nothing stops you from having more. You could have a screen made up of multiple parts, and each part would be its own view and view model.

For example, with the company directory app on a tablet in landscape orientation, there would be enough space to have the list of people on the left side and the details about the person on the right. That’s one parent view showing two views and view models. In portrait orientation, the app would show one view and view model for the list, and tapping on a person would replace that view with the person view and view model.

Something has to ensure that the right view model is bound to the right view, so there needs to be a link between the view and the view model. There are two ways of doing this: view-first and view-model–first. Both of these approaches rely on there being something, usually in the app layer, that defines these links.

3.6.1. View-first

View-first means the view is the driver behind the navigation (figure 3.17). At app startup, the app layer will load a view, and when the view is loaded, something (maybe some code in the app layer, or even in the view itself) will create the corresponding view model and bind it up. When you navigate to another screen, the view is responsible for this. It will know which view it needs to navigate to and will show that view, which in turn causes its view model to be created and bound up.

Figure 3.17. View-first navigation—the view drives the creation of view models and navigates to other views.

3.6.2. View-model–first

View-model–first means the view model is the driver behind the navigation (figure 3.18). The app layer will load a view model at app startup, and this loading of the view model will cause the view to be created, bound to the view model, and shown. When you navigate to another screen, the view model is responsible for this. It will know which view model to navigate to and will interact with something (usually provided by the MVVM framework you’re using) to create the new view model and its associated view.

Figure 3.18. View-model–first navigation—the view model drives the creation of views using the MVVM framework and navigates to other view models.

3.6.3. Which one to use?

The most popular approach by far is view-model–first. If you have the logic to load views in the view layer, you have more platform-specific code, and this platform-specific code is hard to test except manually. Writing unit tests against UI code is harder than writing them against non-UI code. If the logic is in the view model, you have more logic in your cross-platform layer, so there’s less code to write and more code that you can unit-test.

Most MVVM frameworks provide a navigation service of some description—a service that allows you to navigate to different views or view models. This service is always exposed via an interface that you can use from your view models (and mock out for testing) and it allows you to navigate in a way that’s not tightly coupled to a view class. In some frameworks, this is done by navigating to a view model, and in others it’s by navigating using a key (such as a unique string value) that has been linked to a view and view model. MvvmCross navigates via view model, and it’s this navigation we’ll be using in this book.

3.7. Revisiting the square-root calculator app

You’ve seen the square-root calculator app broken down layer by layer, so let’s take a moment to step back and view the bigger picture, using a bigger picture. At the start of the chapter I presented a figure that showed user interactions with the app. We’re now in a position to expand on this figure, filling in all the different interactions between the different layers. This is shown in figure 3.19.

Figure 3.19. The complete square-root calculator, showing the interactions between all the layers of MVVM

Take a moment to study this diagram and follow the flow through the app. It shows a lot of what we’ve talked about already.

The app starts up and launches a view and view model, ideally using view-model–first navigation. As these are created, the binding wires up the state and behavior on the view model to the UI—the properties are bound to a text box and a label, and the command is bound to a button. As the user enters text, the view model is updated via the binding, which in turn pushes the value to the model. Clicking the button executes a command that calculates the square root and raises a property-changed notification. This property change is detected by the binding, which updates the UI.

This flow seems simple, but it encompasses the bulk of MVVM:

  • The model is a separate layer that has business logic and uses properties of types that make sense in the business domain.
  • The view model wraps the model layer and exposes state and behavior to the layers above, converting the state from business types to UI types.
  • The binding sits above the view model and “glues” it to the view.
  • The view exposes the state and behavior via widgets on the screen that the user can understand and interact with.

We have a model layer that’s distinct, cross-platform, unit-testable, easier to maintain, and easier to evolve. We also have a view-model layer that’s distinct, cross-platform, unit-testable, easier to maintain, and easier to evolve. We have a binding layer and a thin UI layer that’s platform-specific.

Now you’re armed with more knowledge about MVVM. In the next chapter we’ll take a look back at the Hello World example from chapter 2 and see what’s happening in the code. We’ll also extend the app using a cross-platform Xamarin plugin to make it say “Hello” to you.

Summary

In this chapter you learned that

  • Models are cross-platform and unit-testable, and they represent data at the business-logic or domain level, not at the UI level.
  • View models are cross-platform and unit-testable, and they represent state and behavior through properties and commands. View models act as a conversion layer between data or actions at the UI level and data or methods in the model.
  • The platform-specific view layer and the cross-platform view model communicate though a binder, a loosely coupled layer that’s usually provided by a framework that keeps the view (binding target) and view model (binding source) in sync.
  • To navigate between different screens in your app you can use view-first navigation to have the view manage the navigation, or view-model–first navigation to have the view model manage it. View-model–first is preferable, as you can unit-test this navigation.
  • The .NET Framework has some interfaces and classes that help to implement your app using MVVM, but to fully implement the pattern you can use a third-party framework such as MvvmCross, MVVM Light, or Caliburn.Micro.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.253.152