Chapter 8. Building cross-platform view models

This chapter covers

  • Creating simple and master/detail view models
  • Adding state to view models using single-value and collection properties
  • Adding behavior to view models when properties change and using commands
  • Communicating between components using messaging
  • View-model navigation

In the previous chapter we built the cross-platform model layers for our two apps, SquareRt and Countr. We looked at how you can wrap up your model layer in services and repositories that can be shared among different view models. Now it’s time to move up a layer and start coding the view models.

8.1. The view-model layer

Like the model layer, the view-model layer is a cross-platform layer (figure 8.1). The difference is that whereas the model layer represents your data and business logic in a way that makes sense to your domain (for example, using services), the view-model layer represents the state and behavior of your UI and is written in a way that makes sense to your view layer.

Figure 8.1. The view-model layer in an MVVM-based mobile app is written using cross-platform code.

This means that, in general, you have one view model per view, so for FooView you’d have FooViewModel, for BarView you’d have BarViewModel, and so on. This is very different from the model layer, where you have data models that represent the entities in your business layer, and services and repositories to manipulate and store those entities. After all, different views can show or interact with the same entities.

Throughout this chapter, we’ll be looking at the responsibilities of the view model, including the following:

  • Encapsulating state from the model layer and representing it in a way that makes sense to the view layer
  • Providing value conversion between data in the model layer and the view layer
  • Providing a way for the view layer to trigger behavior via commands or via properties changing
  • Making the behavior in the model layer accessible to the view layer

In chapter 6 we looked at the user flows for our two apps, SquareRt and Countr. Let’s review these again, and look at the state and behavior that the different view models need to represent.

8.1.1. The view-model layer inside SquareRt

The SquareRt app is very simple and only has one user flow: a user enters a number, and the square root is calculated. Figure 8.2 shows this flow and the classes you need to implement it.

Figure 8.2. The only user flow in SquareRt, and the view, view-model, and calculator classes that you need to implement it

We’ve wrapped the calculation code in the model layer in the previous chapter, SquareRtCalculator, and we also designed a UI for it. Let’s now think about how you can wire this UI up to the model via a view model by looking at state and behavior.

State

The first thing to think about is state—what data you show on screen. In this app the state is represented by two numbers: one for input that the user can edit (the number that the square root will be calculated from), and one for output that’s read-only (the square root result). It’s these two pieces of state that we need to represent in the view model, as shown in figure 8.3.

Figure 8.3. The state that will be represented by the view model

One thing to bear in mind is that the values used in the calculations are of type double, whereas most text-entry controls deal with raw text and so have string values. This means we’ll have to perform value conversion in the view-model layer.

Behavior

Once you have an idea of the state that you need to represent, you need to think about the behavior. The behavior here is also very simple: when the input number is changed, the app needs to calculate the new square root and update the result property on the UI. This is shown in figure 8.4.

Figure 8.4. The behavior that the view model will need to implement

Although it’s normal to handle behavior using commands, sometimes it’s more appropriate to handle simple behavior by using properties, such as when the value of one property is directly dependent on the value of another. In this case, the trigger for the behavior is one property changing, so there’s no need to wrap this up in a command.

The simple rule of thumb here is that if one property is dependent on the value of another, it’s usually easier to implement the behavior as part of the property change. If the behavior is triggered by an explicit user action, use a command.

8.1.2. The view-model layer inside Countr

SquareRt is a very simple app, but Countr is a bit more complicated. Rather than having one simple user flow, it has four as shown in figure 8.5.

Figure 8.5. The user flows for the counter app—showing, adding, deleting, and incrementing counters

We also have a slightly more complicated UI, with two screens. Following the pattern of one view model per screen, we’ll need to have two view models—one for the screen showing the list of counters, and one for the screen to add a new counter (figure 8.6).

Figure 8.6. The Countr app maps to a set of views, view models, and model-layer classes.

You’ll probably notice that this app, with one view model (and therefore one view) that shows a list of data, and another view model (and view) for creating, viewing, or editing an item, has a similar pattern to several other apps you use on a regular basis. For example, in an email app you’d have one view showing a list of emails in a mailbox, such as your inbox or sent mails. When you tap a button to write a new email, a new screen will appear where you can write your email. Once this email is sent, it’ll appear in the list of sent mail. Tapping on an email in the inbox will show a new screen with the contents of that email, as shown in figure 8.7. The same is true in an address book app—these apps normally show a master list of people by name, and when you tap a name it shows the details about that person. If you tap a button to create a new contact, you get a new screen to create the contact, and once you’re done, it appears in the master list.

Figure 8.7. Master/detail apps have a master list in one view and a detail view for seeing, editing, or creating an individual item.

Similarly CountersViewModel will contain a list of counters, which will each be represented by CounterViewModel instances that wrap each counter.

This pattern is called master/detail, and it refers to a master list that shows highlights of all the items your app needs to show, and a detail screen that can be used to view or edit the details for a single item, or can be used to add a new item.

The normal navigation pattern for adding new items is via a button (on iOS this is normally on the toolbar; on Android it can be a toolbar button or a floating action button), which displays a blank detail screen where you can enter the details. This detail screen will usually have Save and Cancel buttons in the toolbar. If your app supports viewing more details or editing an item, the normal navigation pattern is to tap on the item in the list, and this will navigate to the detail screen, with a back button at the top left so you can go back to the list. Details usually slide in from right to left on top of the list and slide back out from left to right when done, mimicking papers stacking up and unstacking.

In the Countr app we’ll use the master/detail pattern. The master list will show the list of all counters, and the detail screen will be for adding new counters (figure 8.8). If, in the future, we wanted to expand our app to support editing counters (such as changing the name) or viewing more details (viewing when counts were increased, or reports broken down by day, week, month), we could use the same pattern with a detail screen containing all this info.

Figure 8.8. The Countr app has a list of counters and a detail screen to add a new counter.

A popular technique for creating the view-model classes for master/detail apps is to stick with two view models—a master view model that contains a list of instances of the detail view model. The detail view model will contain all the state and behavior needed by the detail screen.

State

The app will need two view models: CountersViewModel and CounterViewModel. The state represented by CountersViewModel is a collection of counters that will be displayed on screen in a list. Each counter will be represented by an instance of CounterViewModel, which has state in the form of the name and current count of the counter. Both of these values will be read-only here, but we can also use CounterViewModel for the add-new-counter screen, where the name will be set. This is shown in figure 8.9.

Figure 8.9. The state that will be represented by the two view models: a master view model and a detail view model

Behavior

The two view models for this app have different behaviors, all triggered by user interactions. This means you can implement behavior via commands, unlike SquareRt, which will use property changes to trigger behavior (figure 8.10).

Figure 8.10. The behavior for the master and detail view models

For CountersViewModel, the master view model, you’ll need to add behavior for an Add button—this will need to navigate to a new screen so the user can set up a new counter. For CounterViewModel, the detail view model, you’ll need to add behavior for the Done button, which will navigate back to the master view after creating a new counter, and for a Cancel button, which will navigate back to the master view without creating a new counter. You’ll also need to add behavior that’s used by the items in the master list, allowing the user to increment or delete a counter.

8.2. Adding state and behavior to SquareRt

Now that we’ve reviewed the state and behavior for both of our apps, let’s write some code to implement them, starting with the SquareRt app. Launch the SquareRt solution from chapter 7—use the version that does the calculation itself rather than the one that uses the Bing API, because it will be much faster to run.

8.2.1. State inside SquareRt

The first thing we need to do is create the view model for the SquareRt app. Add a new class called SquareRtViewModel to the ViewModels folder. The following listing shows the initial code for this class.

Listing 8.1. Taking the calculator interface as a constructor parameter
using MvvmCross.Core.ViewModels;

namespace SquareRt.Core.ViewModels
{
   public class SquareRtViewModel : MvxViewModel                      1
   {
      readonly ISquareRtCalculator calculator;

      public SquareRtViewModel(ISquareRtCalculator calculator)        2
      {
         this.calculator = calculator;
      }
   }
}

  • 1 The view model is derived from the MvvmCross base view model.
  • 2 The constructor takes an instance of the square root calculator.

Once you’ve added this class, you can delete the FirstViewModel class. This class is also referred to in the App class, inside the Initialize method. Inside this method, a call is made to RegisterNavigationServiceAppStart to register the FirstViewModel as the startup view model for the app. We’ll look at this call in detail later in this chapter, but for now change this call to use the new SquareRtViewModel so that the project compiles, as shown in the following listing.

Listing 8.2. Setting the app startup view model to be your new view model
public override void Initialize()
{
   ...
   RegisterNavigationServiceAppStart<ViewModels.SquareRtViewModel>();
}

This class derives from MvxViewModel, the base view-model class from MvvmCross, which provides features such as property-changed notifications.

You need to add a couple of properties to the view model to represent the value you’ll calculate the square root of, and the result of the calculation. Although these are simple properties, you do have to put some thought into how to create them. View models are responsible for value conversion, converting values from a format that’s relevant to the view to a format that’s relevant to the model layer, and vice versa. This model layer deals with double values. In contrast, the UI has a text-entry control for entering the input, as well as a label control to show the result, and these kind of UI controls usually deal with string values.

You have two choices here—perform the value conversion inside the properties, or do it in a value converter. Let’s look at both in turn.

Value conversion inside properties

You have two properties to consider: one for the input and one for the result. The result will be calculated when you implement the behavior inside the view model, so if you’re going to perform value conversion inside the properties, you can also convert the result value to a string as soon as it has been calculated. This means you can make a simple string property for the result.

The following listing shows this property, so add this to the view model.

Listing 8.3. string property uses a backing field and notifies when the value changes
string result;                                             1
public string Result                                       1
{
   get { return result; }                                  2
   private set { SetProperty(ref result, value); }         3
}

  • 1 Both the backing field and property are of type string.
  • 2 The getter just returns the value of the backing field.
  • 3 The setter uses SetProperty to update the field.

This Result property is a simple string property with a string backing field. The getter just returns the field value, so there’s nothing too exciting here. The setter is private (after all, you’ll be calculating the value inside the view model, so there’s no need to make the setter public), and it uses SetProperty to update the value. SetProperty is a helper method provided by MvvmCross; it will check the existing value against the new value, and if it has changed, it will update the value and raise the property-changed event. If the value hasn’t changed, nothing happens.

Property-changed notifications are used to update the UI

The reason for raising a property-changed notification is to tell the binding layer that the property’s value has changed, so the UI should be updated. The binding layer will re-read the property and set the new value on the relevant UI widget.

The SetProperty method will also return a Boolean—true if the value was different and the property was updated, or false if the value was the same, and so wasn’t updated. This is helpful if you want to perform other actions when the value changes. For example, in a class with a Name property that concatenates FirstName and LastName, if the call to SetProperty inside the FirstName or LastName properties returns true, then your view model can raise a property-changed notification for Name.

The result property is easy, but for the input property you actually have to do some conversion. Helpfully, the .NET Framework provides a selection of ways to make this conversion easy. One of these is the System.Convert static class, which has methods that perform all kinds of conversions between the different primitive types, such as double, int, long, and string. The following listing shows this in action.

Listing 8.4. Converting from a string to a double and vice versa
using System;
...
double number;                                                    1
public string Number                                              1
{
   get { return Convert.ToString(number); }                       2
   set { SetProperty(ref number, Convert.ToDouble(value)); }      3
}

  • 1 The property is of type string, but the backing field is a double
  • 2 The property getter converts from a double to a string.
  • 3 The property setter converts from a string to a double.

This is very much like the Result property, except the property is a string and converts to and from a double backing field, which is of the right type to pass to the calculator in order to calculate the square root. This is shown in figure 8.11.

Figure 8.11. Value conversion inside a property

In this example, you’re not doing any validation, so if the value passed to Number isn’t a number, such as the string "Not a Number", then the conversion would throw a FormatException. Ideally you should always add validation before converting values, but in this instance it shouldn’t be too much of a problem because when we construct the UI in the next couple of chapters, we’ll restrict the text-entry controls to only allow numbers.

There are many ways to string a double

There are many ways to represent a number as a string. For example, the number 1,234.56789 can be represented in a number of ways:

  • 1,234.56789
  • 1234.56789
  • 1,234.56789000

All of these are valid, but they’re not necessarily the format you want. When converting a number to a string, you can use format specifiers to dictate how the number should be represented. Standard format specifiers are available, and you can create custom formats if you need to. You can read all about formatting types in Microsoft’s “Formatting Types in .NET” article at http://mng.bz/1Ijv.

You should also consider locale. In the U.S., a decimal point is a period (.), whereas in some European countries it’s a comma (,). In the U.S., 1,000 means one thousand, but in Denmark it’s one. You can read about supporting different locales when converting to strings using the CultureInfo class in Microsoft’s documentation of the Double.ToString method (http://mng.bz/LpJ0).

Unit-testing your value conversion

In chapter 7 we discussed how our models couldn’t be tested manually because we don’t yet have a working app, and the same applies to our view models. We can’t test these manually, so we need to write some unit tests.

You can do this now by creating a ViewModels folder in the SquareRt.Core.Tests project and adding a new test fixture class called SquareRtViewModelTests. You’ll be mocking out the ISquareRtCalculator interface, so just like with the Countr tests in the previous chapter, you’ll need to add the Moq NuGet package (figure 8.12). In addition, your view model derives from an MvvmCross base class, so you’ll need to add the MvvmCross NuGet package as well (figure 8.13), making sure that the version of the MvvmCross NuGet package that you add matches the version used in your core project.

Figure 8.12. Adding the Moq NuGet package to the unit-test project

Figure 8.13. Adding the MvvmCross NuGet package to the unit-test project

The following listing shows the initial implementation of this test fixture.

Listing 8.5. Creating a view model from a mock calculator
using Moq;
using NUnit.Framework;
using SquareRt.Core.ViewModels;

namespace SquareRt.Core.Tests.ViewModels
{
   [TestFixture]
   public class SquareRtViewModelTests
   {
      Mock<ISquareRtCalculator> calculator;
      SquareRtViewModel viewModel;

      [SetUp]
      public void SetUp()
      {
         calculator = new Mock<ISquareRtCalculator>();              1
         viewModel = new SquareRtViewModel(calculator.Object);      1
      }
   }
}

  • 1 A mock calculator is created and used to construct the view model.

In the SetUp method of this test fixture class, you initialize a mock ISquareRtCalculator and create an instance of the view model using the mock that you can test in your test methods. You can only test the Number property at the moment—the Result property has a private setter so you can’t test it until you add behavior later in this chapter.

The following listing shows an example test to verify that you can get and set a string value on the Number property correctly.

Listing 8.6. Verifing that the Number getter returns same value passed to the setter
[Test]
public void Number_ConvertsToAndFromDoubleCorrectly()
{
   // Act
   viewModel.Number = "1234.4321";
   // Assert
   Assert.AreEqual("1234.4321", viewModel.Number);
}

This is a simple test, and if you run it, it should pass with no problems. If you want to see what happens if the string isn’t a valid number, change the test to pass in a different string that is not a valid number.

Another thing to test here is the property-changed notifications. It’s a good sanity check to ensure that the property raises a changed notification if the value changes, so the next listing is a quick test to do this.

Listing 8.7. Verifing a property-changed notification is raised when the number changes
[Test]
public void SettingNumber_RaisesPropertyChanged()
{
   // Arrange
   var propertyChangedRaised = false;
   viewModel.PropertyChanged +=
      (s, e) => propertyChangedRaised = (e.PropertyName == "Number");    1
   // Act
   viewModel.Number = "1";                                               2
   // Assert
   Assert.IsTrue(propertyChangedRaised);                                 3
}

  • 1 Wires up the PropertyChanged event
  • 2 Updates the Number property
  • 3 Checks that the property-changed event was fired

An easy way to test the PropertyChanged event is to wire up the event to a handler that sets a bool flag to true if the event is raised with a property name that matches the property you’re interested in. The name of the property that changed comes from the PropertyName property of the event args.

If you run this, you’d expect the test to pass. Try it and see what happens. What you’ll actually see is that this test fails...

This is a result of the way MvvmCross handles property changes. When you raise a property-changed event, the UI needs to be updated, and as you saw back in chapter 5 this must happen on the UI thread. Rather than forcing you to always update properties on the UI thread (something that’s hard to do in a view model), most MVVM frameworks help you out by marshaling these events onto the UI thread. This is what’s happening here—MvvmCross is helpfully raising the property-changed event on the UI thread using a dispatcher, a class whose sole purpose is to run code for you on the UI thread (figure 8.14). When you run your code inside an app running on iOS or Android, the MvvmCross setup code creates this dispatcher automatically based on your app’s UI thread. Inside unit tests there’s no UI thread and no dispatcher, so there’s nothing to run the code to raise your event.

Figure 8.14. MvvmCross view models raise property-changed events using a dispatcher.

There are a couple of workarounds. One is to create a mock dispatcher object and set MvvmCross up to use it, but this is too much hard work for our needs right now. Luckily there’s a simple shortcut—you can set a flag on your view model to raise the property-changed events on the current thread, rather than using a dispatcher. This is good enough for our tests, so make the change to the Setup method shown in the following listing, and re-run the test.

Listing 8.8. Raising the property-changed events on the current thread
...
viewModel = new SquareRtViewModel(calculator.Object);
viewModel.ShouldAlwaysRaiseInpcOnUserInterfaceThread(false);      1
...

  • 1 Tells the view model to raise the property-changed events on the current thread

You should now see the test pass.

Value conversion using a value converter

We’ve looked at value conversion inside a property, and you’ve seen how you can convert a string from the UI to a double to use in your calculation. You’ve also seen that you’ll need to convert the result of the calculation to a string to set the result property when you implement the behavior inside your view model.

This seems a bit more complicated than we might like, with conversions happening in multiple places. If we extended the app to include more calculations (such as adding a cube-root converter), we’d have to duplicate the conversion code, meaning more places for bugs, and more code to change if we wanted to make any improvements. Ideally, we’d want to do this conversion in one place, and that place is a value converter. We want to maximize code reuse—that’s why we’re building Xamarin apps using MVVM after all!

We looked at value converters back in chapter 3, but as a recap, a value converter is a class whose sole job is to convert from one type to another. They’re used by the binding layer to convert values from the type used by the view model to the type used by the view, and vice versa. They have two methods: Convert and ConvertBack. Convert converts from the view-model type to the view type, whereas ConvertBack converts from the view type to the view-model type (figure 8.15). Value converters can use types that are available in .NET Standard libraries, or they can be used for platform-specific types. If they use platform-specific types, they need to live in the relevant iOS and Android app projects, but if they use types available in .NET Standard libraries (such as doubles and strings) they can live in the core project.

Figure 8.15. Using a value converter to convert properties

Create a folder in the SquareRt.Core project called ValueConverters, and in that folder create a DoubleToStringValueConverter class. The following listing shows the code for this converter.

Listing 8.9. A value converter to go from doubles to strings
using System;
using System.Globalization;
using MvvmCross.Platform.Converters;

namespace SquareRt.Core.ValueConverters
{
   public class DoubleToStringValueConverter : IMvxValueConverter     1
   {
      public object Convert(object value, Type targetType,
                            object parameter, CultureInfo culture)
      {
         return System.Convert.ToString(value);                       2
      }

      public object ConvertBack(object value, Type targetType,
                                object parameter, CultureInfo culture)
      {
         return System.Convert.ToDouble(value);                       3
      }
   }
}

  • 1 There is no standard value-converter interface available to Xamarin apps, so we’ll use one provided by MvvmCross.
  • 2 Converts the value to a string
  • 3 Converts the value back to a double

This converter implements an interface from MvvmCross, IMvxValueConverter, which provides the same two methods that most value converters have: Convert and ConvertBack. The implementation of this converter uses the same logic as you saw earlier when converting values inside the view model itself, using the System.Convert static class to perform the conversion.

Unit-testing your value converter

You can now unit-test this converter to prove it works. Create a ValueConverters folder in the SquareRt.Core.Tests project and add a new test-fixture class DoubleToStringValueConverterTests. The following listing shows the code for some tests for converting and converting back.

Listing 8.10. Unit-testing the value converter
using NUnit.Framework;
using SquareRt.Core.ValueConverters;

namespace SquareRt.Core.Tests.ValueConverters
{
   [TestFixture]
   public class DoubleToStringValueConverterTests
   {
      [Test]
      public void Convert_ConvertsADoubleToAString()
      {
         // Arrange
         var vc = new DoubleToStringValueConverter();
         // Act
         var converted = vc.Convert(123.456, null, null, null);           1
         // Assert
         Assert.AreEqual("123.456", converted);                           1
      }

      [Test]
      public void ConvertBack_ConvertsAStringToADouble()
      {
         // Arrange
         var vc = new DoubleToStringValueConverter();
         // Act
         var converted = vc.ConvertBack("123.456", null, null, null);     2
         // Assert
         Assert.AreEqual(123.456, converted);                             2
      }
   }
}

  • 1 Converts a double to a string and ensures it’s converted correctly
  • 2 Converts a string back to a double and ensures it’s converted correctly

If you run these tests, they should all pass.

Before you can use this value converter with your view model, you need to make your view model use doubles only, with no conversion to or from strings. The next listing shows the view-model properties.

Listing 8.11. For a value converter in the binding layer, properties should be doubles
double number;                       1
public double Number                 1
{
   get { return number; }
   set { SetProperty(ref number, value); }
}

double result;                       1
public double Result                 1
{
   get { return result; }
   set { SetProperty(ref result, value); }
}

  • 1 All properties and backing fields are doubles.

You can also remove the unit tests for the view model that checked the conversion, and change the test for the property changed to use the correct type. Delete the Number_ConvertsToAndFromDoubleCorrectly test and change the assignment in SettingNumber_RaisesPropertyChanged to set the view model to a double instead of a string. With these changes made, the test should pass.

Which one to use

We’ve looked at value conversion inside properties and using value converters, so which one should you use? As with all good programming questions, the answer is “it depends.” A good rule of thumb is to think about how often this conversion needs to happen, and how complicated it is:

  • If it needs to happen for multiple properties across multiple view models, a value converter is the best bet.
  • If the conversion is slow (for example, involving a database lookup or a web service call), you need to find a way to make it happen on a background thread, in which case a value converter is out. Value converters are called by the binding layer on the UI thread, so they must be fast. In this situation, it would be better to create a Task to convert the value on a background thread when the property to be converted is set.
  • If the conversion involves multiple inputs, such as multiple properties, it’s easier to do the conversion on the properties inside the view model. Using a value converter would be much more complex, as you’d need to pass multiple properties through.
  • If the view type is platform-specific, it has to be in a value converter.

It comes down to whatever fits best for your code. I personally like to do it inside properties where I can. If I find I’m repeating the code, I refactor it into a value converter.

8.2.2. Exposing behavior via property changes

Our SquareRt app is a simple one, with a single user flow. Every time the number is changed, the square root should be calculated, and this behavior is simple enough to execute every time the number changes, rather than waiting for an explicit user action like tapping a Calculate button.

Let’s add the code to implement this behavior in the SquareRtViewModel. The following listing shows the code you need if you’re doing value conversion inside the properties, and listing 8.13 shows the code if you’re doing the value conversion in a value converter.

Listing 8.12. Calculating square as a string when Number property changes
public string Number
{
   get { return Convert.ToString(number); }
   set
   {
      if (SetProperty(ref number, Convert.ToDouble(value)))
          Result = Convert.ToString(calculator.Calculate(number));      1
   }
}

  • 1 After the number is set, the result is calculated and converted to a string.
Listing 8.13. Calculating the square root whenever the Number property changes
public double Number
{
   get { return number; }
   set
   {
      if (SetProperty(ref number, value))
          Result = calculator.Calculate(number);       1
   }
}

  • 1 After the number is set, the result is calculated.

In both cases, the result is calculated and the property is updated. When the value is calculated, it’s the Result property itself that gets updated, not the backing field. This way, a property-changed event is raised, telling the UI to update and show the new value.

Now that we have the behavior defined, let’s write a couple of unit tests to verify that the result is calculated and a property-changed event is raised whenever the number changes. The following listing shows these tests, which you can add to SquareRtViewModelTests.

Listing 8.14. Ensuring that the result changes when the number is set
[Test]
public void SettingNumber_CalculatesResult()
{
   // Act
   viewModel.Number = 4;
   // Assert
   Assert.AreEqual(2, viewModel.Result);                                1
}

[Test]
public void SettingNumber_RaisesPropertyChangedForResult()
{
   // Arrange
   var propertyChangedRaised = false;
   viewModel.PropertyChanged +=
      (s, e) => propertyChangedRaised = (e.PropertyName == "Result");   2
   // Act
   viewModel.Number = 1;
   // Assert
   Assert.IsTrue(propertyChangedRaised);
}

  • 1 Tests that the result is calculated from the number
  • 2 Verifies that a property-changed notification is raised for Result

If you run these tests, surprisingly they fail. That’s because we mocked the ISquareRtCalculator interface in the SetUp method. Mocks, by default, don’t do anything—their properties are all default values for the type (0 for numbers, null for objects), and all methods return the default values. In this case, the Calculate method is returning a default value of 0 because we haven’t set it up.

Remember, this is a unit test—a test to verify a unit of code in isolation—and we’ve mocked up the dependencies (in this case, the ISquareRtCalculator interface) so that we have control inside our tests. For example, if you were using the version of the square root calculator that used Bing search to calculate the square root instead of a mock object, every unit test would take a while to run as it made a network call, slowing down the tests. Also, running unit tests regularly (something that’s very good to do) could easily exceed the number of Bing requests you can make at the lowest price tier, so you’d have to pay more for each test to run. Mocks help eliminate these problems.

What you can do here is set up the mock to act the way you want and simulate the expected behavior. Moq has a simple syntax where you can specify the behavior you want for the methods and properties on your mock objects, either for all calls or for specific calls, based on the parameters provided. This means that for the Calculate method you could set it up to always return a specific value, or make it so that if you call it with 4, it returns 2, or if you call it with 9 it returns 3, and so on. You could even have it throw an exception if you call it with –1.

For these tests, we’ll set it up to always return 2. The following listing shows the code changes you need to make.

Listing 8.15. Setting up the Calculate method to return 2 at the start of each test
[Test]
public void SettingNumber_CalculatesResult()
{
   // Arrange
   calculator.Setup(c => c.Calculate(It.IsAny<double>()))        1
              .Returns(2);                                       1
   ...
}

[Test]
public void SettingNumber_RaisesPropertyChangedForResult()
{
   // Arrange
   calculator.Setup(c => c.Calculate(It.IsAny<double>()))        1
              .Returns(2)                                        1
   ...
}

  • 1 The Calculate method is set up so that if it’s called with any double value, it will return 2.

If you make these changes and run the tests, they should now pass.

8.3. Adding state and behavior to Countr

SquareRt is now all done, so open the Countr solution and we’ll turn our attention to this app.

8.3.1. Single-value properties

Let’s start by looking at the CounterViewModel for the Countr app. This view model needs to provide state for the name and count of a counter, backed up by an instance of the Counter data model from the model layer.

Load up the Countr solution and create a new class in the ViewModels folder of the Countr.Core project. The next listing shows the code for the first part of this class—preparation.

Listing 8.16. The implementation of CounterViewModel wraps a Counter
using Countr.Core.Models;
using MvvmCross.Core.ViewModels;

namespace Countr.Core.ViewModels
{
   public class CounterViewModel : MvxViewModel<Counter>      1
   {
      Counter counter;                                        2

      public override void Prepare(Counter counter)           3
      {                                                       3
         this.counter = counter;                              3
      }                                                       3
   }
}

  • 1 This view model derives from MvxViewModel.
  • 2 The view model uses an instance of Counter to hold the state.
  • 3 The Prepare method provides an existing counter as a backing store for this view model.

This view model needs to represent a counter, so it makes sense to use an instance of Counter as a backing store to hold this data. CounterViewModel has two jobs: in the master list it represents an existing counter, and in the new counter detail view it represents a new counter (figure 8.16). For both jobs it needs to store and expose a counter. Here you can take advantage of a slightly different base view-model class: MvxViewModel<T>. This class provides an abstract Prepare(T parameter) method that you override to prepare the view model and store the counter, and this method can be called with either an existing counter or a new one.

Figure 8.16. The counter view model has two uses—it’s an item in the list of counters, and it’s the view model for the add-new-counter screen.

You might think this is an odd way to do it. After all, for such a class you’d normally have two constructors: a default one that creates a new counter, and one that takes an existing counter as a parameter. You can’t do that here, though, because of the way MvvmCross uses view models to navigate between views—something we’ll look at in detail later on in this chapter.

Now that you have your Prepare method, let’s implement the Name and Count properties using the counter as a backing field. You won’t be able to use the SetProperty helper method here—it needs a reference to the underlying field so that it can both read and write the value. In this case, there’s no underlying field, just a property on the backing object that can’t be passed by reference. Here’s the code for this.

Listing 8.17. Wrapping the properties on the underlying counter
public string Name
{
   get { return counter.Name; }            1
   set
   {
      if (Name == value) return;           2
      counter.Name = value;                2
      RaisePropertyChanged();              2
   }
}

public int Count => counter.Count;         3

  • 1 The Name property getter returns the value from the counter.
  • 2 The Name property setter checks to see if the value has actually changed, and if so sets the value on the counter and raises a property-changed notification.
  • 3 The Count property is read-only, so it only has a getter that returns the value from the counter.

The getters for both the Name and Count properties are simple pass-through getters—they just return the value on the underlying counter. The Count property is read-only (it can only be edited via the + button, so it will be incremented using a command). The Name property is not read-only as you’ll need to set it when creating a new counter. It follows the standard logic you saw back in chapter 3—if the value hasn’t actually changed, do nothing; if it has changed, update the property on the underlying counter and raise a property-changed notification.

Let’s now write some unit tests to verify that we haven’t made any mistakes with this view model. Create a ViewModels folder in the Countr.Core.Tests project and create a new class in that folder called CounterViewModelTests. Once again, you’ll need to add the MvvmCross NuGet package, so add this to the tests project now. The following listing shows the the contents of the CounterViewModelTests class.

Listing 8.18. Unit-testing the simple pass-through properties on the view model
using NUnit.Framework;
using Countr.Core.ViewModels;
using Countr.Core.Models;

namespace Countr.Core.Tests.ViewModels
{
   [TestFixture]
   public class CounterViewModelTests
   {
      CounterViewModel viewModel;                                1

      [SetUp]                                                    1
      public void SetUp()                                        1
      {                                                          1
         viewModel = new CounterViewModel();                     1
      }                                                          1

      [Test]
      public void Name_ComesFromCounter()
      {
         // Arrange
         var counter = new Counter { Name = "A Counter" };       2
         // Act
          viewModel.Prepare(counter);                            3
         // Assert
         Assert.AreEqual(counter.Name, viewModel.Name);          4
      }
   }
}

  • 1 Creates a new counter view model to use in all tests
  • 2 Creates a new counter with a defined name
  • 3 Prepares the view model with the counter
  • 4 Asserts that the name on the view model matches the counter

This is a simple sanity check to ensure that the counter is wired up correctly, and by running this and watching it pass, you can see that everything is OK. As another sanity check for the Count property, you can duplicate this test but verify that the count is correctly passed through. You could also add a test to ensure that the property-changed notification is raised when setting the Name property, just as we did for the Number property of SquareRtViewModel. You can see examples in the source code that accompanies this book.

We now have a working view model for a counter that we can use when creating a new counter, as well as for the items in the list on the main view of the app.

8.3.2. Collections

We’ve looked at simple, single-value properties on our view model; now let’s turn our attention to properties that represent collections of data. You’ve created one view model to represent a counter, so now you need another view model to represent a list of counters—CountersViewModel. Start by creating this class in the ViewModels folder. The following listing shows the initial implementation.

Listing 8.19. Counters view model constructed using instance of counters service
using MvvmCross.Core.ViewModels;
using Countr.Core.Services;

namespace Countr.Core.ViewModels
{
   public class CountersViewModel : MvxViewModel             1
   {
      readonly ICountersService service;                     2

      public CountersViewModel(ICountersService service)     2
      {                                                      2
         this.service = service;                             2
      }                                                      2
   }
}

  • 1 This view model is derived from the MvvmCross base view model.
  • 2 The constructor takes an ICountersService instance, which will be used to get all the counters.

Again this view model derives from MvxViewModel, and the constructor for the view model takes an instance of ICountersService (the service in the model layer you created in the last chapter), which it will use to load all the counters.

Now that you have your view model, you need to expose the list of counters. The following listing shows the implementation of this.

Listing 8.20. Exposing counters as observable collection of counter view models
using System.Collections.ObjectModel;
...
public class CountersViewModel : MvxViewModel
{
   ...
   public CountersViewModel(ICountersService service)
   {
      ...
      Counters = new ObservableCollection<CounterViewModel>();          1
   }

   public ObservableCollection<CounterViewModel> Counters { get; }      1
}

  • 1 The Counters property is an ObservableCollection of CounterViewModel.

This view model exposes a collection of CounterViewModel instances. The collection is exposed as an ObservableCollection. This is a collection type that implements INotifyCollectionChanged, an interface that fires an event whenever the collection is changed. We looked at observable collections in chapter 3, and figure 8.17 recaps how they can be used.

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

When you create your views, you can bind this collection to some form of list control, and whenever the collection changes (such as when you add or delete counters) the collection-changed event will be fired, causing the binding to update the UI. This is a read-only property, and it’s initialized when the view model is created—the instance of ObservableCollection won’t change, just the contents, so there’s no need to ever change the property’s value or raise a property-changed event. Observable collections are ideal when your collection will change, but they’re not necessary if your collection is fixed and will never change. In the latter case you can store the items in any collection, such as a List<T>, and expose this property either as a list or as an IEnumerable<T>.

This observable collection is exposed as a collection of counter view models. The ICountersService exposes a method to get all the counters from the repository, but that returns a collection of Counter objects. To make these available as the right type, you can’t just expose the counters from the service directly. Instead you need to wrap them in view models. You also need to make a call to the service to load these counters in a background thread and then update the collection back on the UI thread, as shown in figure 8.18. The UI can only be updated on the UI thread, so you should ensure that every update to the observable collection happens on the UI thread.

Figure 8.18. You can load counters on a background thread, but you need to show them on the UI thread.

Ideally, you’d have an async method in your view model that’s called from the UI thread, and helpfully MvvmCross provides a method you can use, called Initialize. The Initialize method is a virtual method in the MvxViewModel base class that’s called by the MvvmCross framework, and it’s in overrides of this method that you set up your view model.

All screens in your app, regardless of platform, will undergo a lifecycle—they’re created, shown, hidden, and then destroyed. iOS and Android implement this lifecycle differently, but the basic principle holds true on both platforms. As part of this lifecycle, after the view has been created and shown, MvvmCross will call Initialize on the corresponding view model on the UI thread, allowing you to write code to set up the view model (we’ll look at this view lifecycle in more detail in chapters 9 and 10). The following listing shows this method and how you can create your view models inside it.

Listing 8.21. Creating counter view models from counters loaded from service
using System.Threading.Tasks;
...
public override async Task Initialize()                 1
{
   await LoadCounters();                                1
}

public async Task LoadCounters()                        2
{                                                       2
   var counters = await service.GetAllCounters();       2
   foreach (var counter in counters)                    2
   {                                                    2
      var viewModel = new CounterViewModel();           2
      viewModel.Prepare(counter);                       2
      Counters.Add(viewModel);                          2
   }                                                    2
}

  • 1 Initialize comes from the MvxViewModel base class, and it awaits another method.
  • 2 LoadCounters loads the counters from the service and populates the observable collection with view models prepared with counters.

In the LoadCounters method, you can make a call to the counters service to get all the counters. Then, for each counter, you can create an instance of CounterViewModel, prepared using that counter, which in turn is added to the observable collection. The LoadCounters method will load the counters from a SQLite database (via the service and repository), and this database access will be on a background thread. This means that the UI could be fully visible before you’ve loaded your counters, but because you’re using an observable collection, every time you add a counter view model to the collection, the UI is updated.

The LoadCounters method gives us the first of the four user flows we identified for the Countr app. It loads the counters from storage and makes them available to be shown on the UI (figure 8.19).

Figure 8.19. The first user flow in Countr, loading and showing counters, is implemented by the LoadCounters method on the view model.

No view model is complete without a unit test, so let’s create the fixture now. Create a class called CountersViewModelTests in the ViewModels folder of the Countr.Core.Tests project. The following listing shows the initial implementation of this test fixture, creating a mock counters service and using that to create an instance of the view model to test.

Listing 8.22. Creating an instance of the view model using a mock service
using Countr.Core.Services;
using Countr.Core.ViewModels;
using Moq;
using NUnit.Framework;

namespace Countr.Core.Tests.ViewModels
{
   [TestFixture]
   public class CountersViewModelTests
   {
      Mock<ICountersService> countersService;
      CountersViewModel viewModel;

      [SetUp]
      public void SetUp()
      {
         countersService = new Mock<ICountersService>();                1
         viewModel = new CountersViewModel(countersService.Object);     2
      }
   }
}

  • 1 Creates a mock counters service
  • 2 Uses the mock counters service to create the view model

You can now test the LoadCounters method to simulate what would happen when this view model is created by MvvmCross in the app. The following listing shows an async unit test (which returns async Task instead of void, so that you can await code inside it) to test this method.

Listing 8.23. A test to ensure that the view model wraps the counters correctly
using System.Threading.Tasks;
using System.Collections.Generic;
using Countr.Core.Models;
...
[Test]
public async Task LoadCounters_CreatesCounters()
{
   // Arrange
   var counters = new List<Counter>
   {
      new Counter{Name = "Counter1", Count=0},
      new Counter{Name = "Counter2", Count=4},
   };
   countersService.Setup(c => c.GetAllCounters())
                  .ReturnsAsync(counters);                         1
   // Act
   await viewModel.LoadCounters();                                 2
   // Assert
   Assert.AreEqual(2, viewModel.Counters.Count);                   3
   Assert.AreEqual("Counter1", viewModel.Counters[0].Name);        3
   Assert.AreEqual(0, viewModel.Counters[0].Count);                3
   Assert.AreEqual("Counter2", viewModel.Counters[1].Name);        3
   Assert.AreEqual(4, viewModel.Counters[1].Count);                3
}

  • 1 Sets up the counters service to return some counters
  • 2 Calls LoadCounters on the view model to create the counter view models
  • 3 Asserts that the counter view models are created correctly

This test starts by creating a list of Counter instances with some dummy data. It then sets up the mock counter service to return this list when GetAllCounters is called. Finally, it awaits a call to LoadCounters, and asserts that the list of CounterViewModel instances contains view models that match the canned data. Run this test now, and you should see that it passes.

That’s all you need for this view model. This is a standard pattern for master view models when building a master/detail style app. The master view model loads the models for its items from a repository, wraps them all in instances of the detail view model, and exposes them in a collection. If the collection of items can change, they should be exposed in an observable collection so the UI can be notified of any changes. If the collection can’t change, a simple IEnumerable<T> or List<T> is fine.

8.3.3. Exposing behavior using commands

SquareRt has very simplistic behavior that could be implemented inside properties. Countr, on the other hand, has more complex behavior that’s triggered by user interactions, and for this we need to use commands. We looked at commands back in chapter 3, so let’s have a quick recap now.

Commands are objects that encapsulate the ability to execute a particular action, with optionally the ability to control whether the action can be executed. They implement ICommand, an interface with an Execute method that executes the action wrapped up in the command and a CanExecute method that tells you if the command can be executed or not. You can bind these to user actions on the UI, such as button taps, so that when a button is tapped the command is executed. Commands allow you to provide cross-platform handlers for UI widget events without having to resort to platform-specific event handlers.

You can use commands for the remaining user flows in Countr. Let’s start by looking at the simpler ones, beginning with incrementing a counter (figure 8.20). Counters are not incremented directly; instead you use the CountersService to ensure the incremented value is persisted to the repository (figure 8.21).

Figure 8.20. The second user flow in Countr, incrementing a counter

Figure 8.21. The counters service increments a counter by first incrementing the value and then saving the newly incremented value.

The first thing to do is pass an ICountersService to the CounterViewModel.

Listing 8.24. Passing the counters service to the view model
using Countr.Core.Services;
...
public class CounterViewModel : MvxViewModel<Counter>
{
   ...
   readonly ICountersService service;                      1

   public CounterViewModel(ICountersService service)       2
   {                                                       2
      this.service = service;                              2
   }
}

  • 1 A readonly field to store the ICountersService
  • 2 The counters service is passed in as a constructor parameter and stored in the backing field.

You’ve changed the constructor, so you need to update the code that uses it in the CountersViewModel, as in the following listing.

Listing 8.25. Counter service needs to be passed to the constructor
public async Task LoadCounters()
{
   ...
   foreach (var counter in counters)
   {
      var viewModel = new CounterViewModel(service);       1
      ...
    }
}

  • 1 The service is passed to the constructor of the CounterViewModel.

Now you have a service. The next listing creates a command that calls it to increment the counter.

Listing 8.26. Adding a command to the counter view model to increment the counter
using System.Threading.Tasks;
...
public CounterViewModel(ICountersService service)
{
   ...
   IncrementCommand = new MvxAsyncCommand(IncrementCounter);          1
}

public IMvxAsyncCommand IncrementCommand { get; }                     2

async Task IncrementCounter()                                         3
{                                                                     3
   await service.IncrementCounter(counter);                           3
   RaisePropertyChanged(() => Count);                                 3
}                                                                     3

  • 1 Creates a new MvxAsyncCommand wrapping a method
  • 2 A public property that exposes the command
  • 3 The method called by the command increments the counter using the service and then raises a property-changed notification for the count.

This is the first time you’ve used a command, so let’s break down what’s happening here, line by line.

  • public IMvxAsyncCommand IncrementCommand { get; } This is a public property exposing the command as an IMvxAsyncCommand interface interface. This interface is derived from ICommand, the base interface for all commands, but it has extra helper methods on it to run async code. This public property can be bound to a button or similar UI widget.
  • IncrementCommand = new MvxAsyncCommand(IncrementCounter); As we discussed in chapter 3, there’s no out-of-the-box implementation of ICommand to use, but all MVVM frameworks provide an implementation. In this case, we’re using MvxAsyncCommand—an implementation of ICommand that wraps an async method, and it’s this method that’s passed in to the constructor. When Execute is called on the command, it will call the IncrementCounter method on the calling thread (if this command is executed from a button tap, the calling thread will be the UI thread). This command is async, but there’s no way button-tap events can await commands, so it’s fire-and-forget. It will call the code on the correct thread, but there’s no way of knowing when the Execute method has finished. This isn’t a problem for events, but it’s a problem for unit-testing, where you want to know that the command has completed before asserting on anything. Helpfully, MvxAsyncCommand also implements IMvxAsyncCommand (which derives from ICommand) and has async versions of the command methods, such as ExecuteAsync, which will execute the command and can be awaited. If you were calling a non-async method instead of an async method for our command’s implementation, you could use MvxCommand. The Execute and CanExecute methods on ICommand take an object parameter, but in a lot of cases this parameter is null. MvxAsyncCommand and MvxCommand encapsulate this by taking methods as their constructor parameters that have no parameters. If you want to handle a parameter, you can use MvxAsyncCommand<T> and MvxCommand<T>, where the generic type parameter T is the type of the parameter you expect the command to be called with, and where the corresponding actions passed to the command constructor will need to have parameters of type T. Using a typed parameter instead of object means MvvmCross will handle the conversion for you, throwing an exception if the command is called with the wrong parameter type.
  • async Task IncrementCounter() This is the method called by the command. It’s an async method that will use the service to increment the counter and save it to the SQLite database on a background thread (thanks to the SQLite-Net implementation). Then it will raise a property changed for the Counter property to tell the binding layer to re-read the value. The value on the underlying counter will be incremented, so the binding layer will read the new, incremented value and update the number displayed on screen.

This command is implemented in our cross-platform view model, and it’s crying out for some unit tests, so let’s add a couple. The following listing shows the code to add to CounterViewModelTests.

Listing 8.27. Testing the increment command
using System.Threading.Tasks;
using Moq;
using Countr.Core.Services;
...
Mock<ICountersService> countersService;                                   1

[SetUp]
public void SetUp()
{
   countersService = new Mock<ICountersService>();                        1
   viewModel = new CounterViewModel(countersService.Object);              1
   viewModel.ShouldAlwaysRaiseInpcOnUserInterfaceThread(false);           2
   ...
}

[Test]
public async Task IncrementCounter_IncrementsTheCounter()
{
   // Act
   await viewModel.IncrementCommand.ExecuteAsync();                       3
   // Assert
   countersService.Verify(s => s.IncrementCounter(It.IsAny<Counter>()));  4
}

[Test]
public async Task IncrementCounter_RaisesPropertyChanged()
{
   // Arrange
   var propertyChangedRaised = false;
   viewModel.PropertyChanged +=
      (s, e) => propertyChangedRaised = (e.PropertyName == "Count");      5
   // Act
   await viewModel.IncrementCommand.ExecuteAsync();                       6
   // Assert
   Assert.IsTrue(propertyChangedRaised);                                  7
}

  • 1 Defines and creates a mock counters service that’s passed to the view-model constructor
  • 2 Ensures all property-changed events are raised on the current thread
  • 3 Awaits the call to execute IncrementCommand
  • 4 Asserts that the counter has been incremented by the service
  • 5 Listens for property-changed notifications to the Count property
  • 6 Awaits the call to execute IncrementCommand
  • 7 Asserts that the property-changed notification has been raised

These tests take advantage of the ExecuteAsync method to await the execution of the command asserting that the counter has been incremented by the service and that the property-changed event has been raised. You don’t need to verify that the counter has actually been incremented—you’ve verified that the increment method on the mock service has been called, and you’ve also verified in other tests that the actual IncrementCounter method on the CountersService works, so you can be pretty sure that this code will all work together in your app and increment the counter.

You have your increment command, so the next command to look at is the one to delete a counter (figure 8.22). In a lot of master/detail apps, users can delete items from the list by swiping, so we’ll enable the same functionality here. This means you need a command on CounterViewModel that allows it to delete itself. You’ve already got everything you need in your view model, so the command is pretty simple, as shown in the following listing.

Figure 8.22. The third user flow in Countr, deleting a counter

Listing 8.28. A command in the counter view model to delete the counter
public CounterViewModel(ICountersService service)
{
   ...
   DeleteCommand = new MvxAsyncCommand(DeleteCounter);      1
}

public IMvxAsyncCommand DeleteCommand { get; }              2

async Task DeleteCounter()                                  3
{                                                           3
   await service.DeleteCounter(counter);                    3
}                                                           3

  • 1 Creates a new MvxAsyncCommand for deleting counters
  • 2 The public property for the command
  • 3 The command deletes the counter from the service.

So far this command is pretty simple, and it’s not that different from what you’ve already seen. It’s an async command that calls the counters service to delete the counter. Once you’ve added this command, add a unit test for it. (If you get stuck, there’s an example in the source code that accompanies this book.)

This isn’t quite the whole picture, though. This command deletes the counter from the repository, and you know that if the counter is removed from the ObservableCollection of counters held by the CountersViewModel, the UI will update, but we’re missing the bit in between. How does the CountersViewModel know to remove the counter from its collection? It doesn’t know, so you need to tell it, and the best way to do that is via messaging.

8.3.4. Messaging

In our coffee shop, we have a server who takes coffee orders from customers and writes them on slips of paper, which they pin up somewhere, and we have baristas who pick up these pieces of paper in sequence and make the coffees. This is quite loosely coupled—it doesn’t matter who pins the slips of paper up; the baristas just take them and make the drinks, one after the other. As our coffee shop gets more popular, we could employ multiple servers taking orders, or more baristas, and nothing needs to change. We’ll just have more people pinning up slips of paper, and more people taking them off. The drinks are still made in order, and we still have a loose coupling between server and barista.

We can follow a similar pattern in our apps by using a publish-subscribe model. In our coffee shop we have servers publishing orders on slips of paper, and baristas subscribing to these slips of paper. We can have parts of our app publishing messages and other parts subscribing to those messages and responding accordingly (figure 8.23). We could do exactly this for the delete command—when the counter is deleted, a message could be published to a queue of some description, and the counters view model could subscribe to this queue, get the message that a counter has been deleted, and update its collection accordingly, as shown in figure 8.24.

Figure 8.23. A messenger allows different components of an app to publish or subscribe to messages.

Figure 8.24. Using a messenger to send messages from the counters service to the counters view model when the list of counters changes

Most MVVM frameworks provide a messaging service of some sort—something a class can publish messages to and subscribe to messages from. MvvmCross has one available as another NuGet package, so add the MvvmCross.Plugin.Messenger NuGet package (figure 8.25) to all the projects in the Countr solution, selecting the same version as the other MvvmCross NuGet packages.

Figure 8.25. Adding the MvvmCross Messenger plugin NuGet package to the Countr.Core project

Messenger is an MvvmCross plugin component—an additional component that provides extra useful functionality. Plugins are tightly integrated into MvvmCross; they’re even automatically registered in the IoC container just by adding the NuGet package to your app (MvvmCross finds the plugins by using reflection and registers them inside its startup code). You can use this Messenger plugin to publish messages from your counters service when a counter is deleted. You can then subscribe to these messages from the counters view model, and whenever you receive a message, you can reload the counters.

When subscribing to messages, you need to be able to filter them so that you only receive the ones you’re interested in, and in the MvvmCross Messenger this is based on the class type of the message. There’s a base message type, MvxMessage, and you derive from this for each type of message you want to implement. You then publish a message as an instance of your message class. On the subscriber side, you subscribe based on a specific type, and you handle each received message either on the UI thread or on a background thread.

To implement this, you’ll need to dip back down to the model layer briefly. Let’s start by creating a message type. Add a new class to the Services folder called CountersChangedMessage, and add the following code.

Listing 8.29. A message you can publish, telling anyone that the counters have changed
using MvvmCross.Plugins.Messenger;

namespace Countr.Core.Services
{
   public class CountersChangedMessage : MvxMessage       1
   {
      public CountersChangedMessage(object sender)        2
         : base(sender)                                   2
      {}
   }
}

  • 1 This message derives from the MvxMessage base class.
  • 2 The base class takes the sender of the message as a constructor parameter.

This class defines the message, so you can publish it whenever you delete a counter. The following listing shows the changes to the CountersService.

Listing 8.30. Publishing the counters-changed message every time a counter is deleted
using MvvmCross.Plugins.Messenger;
...
readonly IMvxMessenger messenger;

public CountersService(ICountersRepository repository,
                       IMvxMessenger messenger)                   1
{                                                                 1
   this.messenger = messenger;                                    1
   ...
}

public async Task DeleteCounter(Counter counter)                  2
{                                                                 2
   await repository.Delete(counter).ConfigureAwait(false);        2
   messenger.Publish(new CountersChangedMessage(this));           3
}

  • 1 The messenger comes from a constructor parameter and is stored in a field.
  • 2 This method now needs to be async and to await the Delete call.
  • 3 Whenever a counter is deleted, the message is published.

To use the Messenger, just add a constructor parameter of type IMvxMessenger to your view model. The plugin is automatically registered in the IoC container, so you can just add it as a constructor parameter, and it’ll automatically be populated when the IoC container creates the counters service. Whenever a counter is deleted, the Publish method is called with an argument of an instance of this new message type.

The original version of DeleteCounter used to just return the Task returned from Delete, but now that you’re doing work after this call, you need to mark the method as async, await the call to Delete, and use ConfigureAwait(false), because it doesn’t matter what thread the rest of the method runs on.

You now need to handle this message in the counters view model, as shown in the next listing.

Listing 8.31. Subscribing to the new message type
using MvvmCross.Plugins.Messenger;
...
readonly MvxSubscriptionToken token;                        1

public CountersViewModel(ICountersService service,
                         IMvxMessenger messenger)           2
{
   token = messenger
      .SubscribeOnMainThread<CountersChangedMessage>
      (async m => await LoadCounters());                    3
   ...
}

public async Task LoadCounters()                            4
{                                                           4
   Counters.Clear();                                        4
   ...
}

  • 1 A field to store a subscription token
  • 2 The messenger comes from a constructor parameter.
  • 3 Subscribes to all CountersChangedMessage messages on the UI thread
  • 4 LoadCounters has been tweaked to clear all counters before reloading.

In CountersViewModel you’re subscribing on the main thread (the UI thread) for all messages of type CountersChangedMessage, and when one is received, the LoadCounters method is run. The code for LoadCounters has been changed slightly to clear all counters before loading, so that you don’t keep adding the same counters to the list again and again. You’re not going to have many counters in the list, so clearing and reloading all the counters shouldn’t be too slow.

This may seem like overkill, using a Messaging component to detect changes in the counters service, when you could just add an event to the service that the view model subscribes to. But there are advantages to using the Messenger plugin:

  • Weak subscriptionYou’ll notice that the SubscribeOnMainThread method returns a MvxSubscriptionToken that you store as a field. Subscribing to messages is a weak subscription, in that the messenger doesn’t hold a reference to the subscriber. This means that the garbage collector can collect your view model whenever your code is finished with it; the Messenger won’t be holding a reference that keeps the view model alive. If you’d used events, you’d have to manually unsubscribe from the events before the garbage collector could collect the view model, and this is something that’s easy to forget to do. The subscription token keeps the subscription alive; as soon as the token is garbage collected, the subscription ends. You can also unsubscribe at any time by disposing of the token using its Dispose method.
  • ThreadingWhen you subscribe to a message, you can choose to handle the messages on the UI thread using SubscribeOnMainThread or a background thread using SubscribeOnThreadPoolThread. This means you can handle messages using the appropriate thread. With CountersChangedMessage, you need to handle it on the UI thread so that you can update the collection. If you’d used an event for this, you’d need to find a way to ensure the event was always handled on the UI thread—that’s not easy to do in your view models.
  • Loose couplingBy using a messenger instead of events, you can loosely couple the publisher to the subscriber. This way, anything can subscribe to the messages and not care where the message came from. You could refactor your code to publish the change messages from the repository instead of the service, and everything would still work. You could add more view models or services that listen to the counters-changed message and respond accordingly, and they wouldn’t need to know about the counters service.
Messages let parts of your app communicate without being tightly coupled

Messages are very powerful. You can create as many message types as you need and add properties to them to help you pass data around. In this app there’s one message type, and when it’s received you clear and reload all counters.

For an app with only a few counters, this is fine, but for an app with a lot of items in the master list, you’d probably want to be a bit smarter. For example, you could have multiple message types. You could have one message type for a deleted item, with a property on it identifying the item that was deleted. When this is received, just the one item would be removed from the master list. You could then have another message type for when a new item is created, with a property storing the item that was added. When this is received, the new item could be added to the correct position in the master list.

Now that you’ve added messages, it’s time for a unit test. The current unit tests won’t compile with the new constructor parameter added to the view models, so you’ll need to start by mocking out the messenger in both CountersViewModelTests and CountersServiceTests. The following listing shows the code for doing this, so make these changes to both unit tests.

Listing 8.32. Mocking out the messenger
using MvvmCross.Plugins.Messenger;
...
Mock<IMvxMessenger> messenger;

[SetUp]
public void SetUp()
{
   messenger = new Mock<IMvxMessenger>();
   ...
}

After adding this code to both unit-test classes, add the messenger mock to the constructor calls for each view model by passing messenger.Object as the required parameter. You can then test that the message is published when a counter is deleted from the service by using the following code in CountersServiceTests.

Listing 8.33. Testing that the message is published when a counter is deleted
[SetUp]
public void SetUp()
{
  ...
   service = new CountersService(repo.Object,
                                 messenger.Object);        1
}

[Test]
public async Task DeleteCounter_PublishesAMessage()
{
   // Act
   await service.DeleteCounter(new Counter());             2
   // Assert
   messenger.Verify(m => m.Publish
        (It.IsAny<CountersChangedMessage>()));             3
}

  • 1 Passes the mock to the service constructor
  • 2 Deletes a counter from the service
  • 3 Verifies that the messenger publishes a message

You’ve verified that the service publishes a message, so now let’s verify that CountersViewModel handles the message correctly. The first thing to do is set this up, as shown in the following listing.

Listing 8.34. Setting up the messenger for unit-testing
using System;
...
Action<CountersChangedMessage> publishAction;                    1
...
[SetUp]
public void SetUp()
{
   ...
   messenger = new Mock<IMvxMessenger>();
   messenger.Setup(m => m.SubscribeOnMainThread                  2
                    (It.IsAny<Action<CountersChangedMessage>>(), 2
                     It.IsAny<MvxReference>(),                   2
                     It.IsAny<string>()))                        2
             .Callback<Action<CountersChangedMessage>,           2
                       MvxReference,                             2
                       string>((a, m, s) => publishAction = a);  2

   viewModel = new CountersViewModel(countersService.Object,
                                     messenger.Object);
}

  • 1 An action to store the subscription
  • 2 Sets up the subscribe method on the messenger so the action is stored

When the SubscribeOnMainThread method inside the view model is called, it’s passed an Action<CountersChangedMessage>. In the unit test, you set up this method with a callback that’s invoked whenever the SubscribeOnMainThread method is called, and in this callback you store the action that’s passed to the method. This allows you to simulate the messenger flow.

In the real messenger, the subscription action is stored, and when a message is published, all subscription actions for that message type are called. In the unit test you can simulate this by storing the subscription action and calling it to simulate a message being published. The following listing shows the code for a unit test that uses this approach.

Listing 8.35. Unit-testing that the counters are reloaded when a message is received
[Test]
public void ReceivedMessage_LoadsCounters()
{
   // Arrange
   countersService.Setup(s => s.GetAllCounters())
                      .ReturnsAsync(new List<Counter>());               1
   // Act
   publishAction.Invoke(new CountersChangedMessage(this));              2
   // Assert
   countersService.Verify(s => s.GetAllCounters());                     3
}

  • 1 Sets up a mock return value from GetAllCounters
  • 2 Calls the subscription action to simulate a message being published
  • 3 Verifies that after the message is published, the counters are reloaded

That’s three user flows down, one more to go—adding a new counter. This user flow shows a new screen, so it’s time to look into view-model navigation.

8.3.5. Navigation

Back in chapter 3, we looked at two navigation patterns for MVVM: view-first and view-model-first. View-first is where views drive navigation, with each view triggering the loading of its view model, and where navigation consists of one view loading another. View-model-first is where the view models drive navigation, with the view model triggering which view is loaded, and where navigation is one view model showing another.

Like a lot of MVVM frameworks, MvvmCross uses view-model-first navigation. The first screen of the app to be shown is defined by registering the app’s start view model. Showing and closing views is controlled by a navigation service that view models can use. MvvmCross has a built-in presenter that will find the relevant view for a view model based on its name, so when you show a view model, it will find the relevant view and show that on screen.

You can think of navigating between screens as being like paper stacking up. Each screen is like a sheet of paper, and when you navigate from one screen to another, the new screen is stacked on top, like placing a new piece of paper on top of the stack. When you close a screen, it comes back off the stack of paper, revealing the piece underneath (figure 8.26). You’ll have seen this many times over in the apps you use, such as email apps.

Figure 8.26. Navigation is like sheets of paper being stacked up and unstacked.

When you’re using MvvmCross, this is driven via the view models, so you navigate from one view model to another, and the view for the new view model is stacked on top. When you close a view model, the top view is removed from the stack. MvvmCross also allows you to pass data from one view model to the next as they stack up, although it doesn’t have anything out of the box for passing data back as you close view models off the top of the stack.

Our last user flow is adding a new counter, and this involves navigating from the counters master list screen to a new counter detail screen at the tap of a button (figure 8.27). From this screen, the user can either cancel adding a new counter and navigate back to the master list, or they can enter the name of the new counter, save it, and navigate back.

Figure 8.27. The final user flow in Countr, adding a new a counter

Let’s start by looking at the MvvmCross navigation service.

Navigation service

MvvmCross has a built-in navigation service whose sole responsibility is to handle the navigation between view models in your app, providing view-model-first navigation. When you navigate to a view model, it will look up and navigate to the corresponding view inside your platform-specific code based on its name (for example, navigating to MyViewModel will cause it to look for a corresponding view called MyView). This navigation service is exposed via the IMvxNavigationService interface, which is automatically registered inside the IoC container for you by the MvvmCross startup code. This means you can import this interface into your view models and access navigation from your cross-platform code.

This navigation service has a number of capabilities. You can use it to navigate to a view model, navigate and pass data into the target view model, navigate and await a result from the target view model, or close a view model to go back to the previous view. You can also subscribe to events so you’re notified when navigation happens. MvvmCross even supports URI-based navigation, so you can create deep navigation stacks with multiple levels (such as long signup flows) and navigate up and down with ease. We’re only going to touch on a couple of features of the navigation service here—navigating to a view model passing some data, and closing a view model to navigate back—but you can read more about the MvvmCross navigation service in the MvvmCross documentation at http://mng.bz/tJ7a.

You navigate to a view model using the Navigate method on the navigation service. There are a number of different variants of this, but the simplest takes a parameter of the type of view model you want to navigate to. For example, Navigate(typeof (MySecondViewModel)) would navigate to the MySecondViewModel view model. This is an async method that you can await. When you call this method, it will

  1. Create a new instance of the target view model, injecting all constructor parameters using the IoC container.
  2. Call Prepare on the view model, passing in a parameter if needed.
  3. Find the relevant view for the view model and create an instance of that.
  4. Show the view, binding the view model to the view.
  5. Call Initialize on the view model.

One of the other variants of interest to us is Navigate<TParameter>(Type type, TParameter parameter). This will navigate to the view model with the type specified in the first parameter, and prepare it with the parameter passed in. The target view model needs to be derived from MvxViewModel<TParameter>, an abstract base class that provides a method you have to override, called Prepare, which has a parameter of TParameter. It’s this method that’s called to prepare the view model. This call to Prepare happens on the UI thread before the view has been created. After the view is created, another method, Initialize, is called, and it’s an async method, so it’s a great place to load data or perform other asynchronous tasks. You may recognize this base view model—it’s the one we used for CounterViewModel, meaning you can navigate to this view model and prepare it with a Counter.

The final method on the navigation service of interest to us is the Close method. This method takes a view model to be closed, and it will close the view that shows the given view model. This is normally called from inside a view model, passing this as the parameter to close the current view model, but you can also use it to close other view models if you need to. For example, if you’re showing a view model as a progress dialog during a long-running action, you could close it from the calling view model.

Setting the startup view model

To get the app navigation working correctly, the first thing you need to do is set up your app start (listing 8.36). When the app is loaded, MvvmCross will first show a splash screen while it’s initializing. Then it will find the startup view-model type, and using its built-in presenter it’ll find the relevant type for the first view, create the view, create the view model, and show the view.

For Countr, the master list is the first screen that you want to show, so the app should start up using CountersViewModel. You can tell MvvmCross to use this view model when the app starts up by registering it as the app start in the App class in the core project. Delete the FirstViewModel class from the ViewModels folder, as you don’t need it any more, and make the following change.

Listing 8.36. Setting up the counters view model as the app start
public override void Initialize()
{
   ...
   RegisterNavigationServiceAppStart<ViewModels.CountersViewModel>();
}

Once you’ve deleted FirstViewModel, the iOS app will no longer compile, which shouldn’t be too much of a problem because you’re not running the mobile apps at this point, just verifying your code using unit tests. If you want to be able to successfully build everything, just comment out the whole ViewDidLoad method in the FirstView class in the Views folder of the Countr.iOS app. We’ll be working on the view layer for the iOS app in chapter 11.

Navigating to a new view

Next you need to add a command to the master view model in order to show the detail view. MvvmCross has a navigation service that handles the navigation between view models. This service is exposed as the IMvxNavigationService interface, and it’s automatically registered in the IoC container so you can easily add it as a constructor parameter on the view model. Once you have access to this navigation service, you can use it to show a different view model.

The following listing shows the code you need to add to the CountersViewModel.

Listing 8.37. Adding a command to show the counter view model
using Countr.Core.Models;
using MvvmCross.Core.Navigation;
...
readonly IMvxNavigationService navigationService;                      1

public CountersViewModel(ICountersService service,
                         IMvxMessenger messenger,
                         IMvxNavigationService navigationService)      1
{
   ...
   this.navigationService = navigationService;                         1
   ShowAddNewCounterCommand = new MvxAsyncCommand(ShowAddNewCounter);  2
}

public IMvxAsyncCommand ShowAddNewCounterCommand { get; }              3

async Task ShowAddNewCounter()
{
   await navigationService.Navigate(typeof(CounterViewModel),
                                    new Counter());                    4
}

  • 1 Injects and stores an instance of the MvvmCross navigation service
  • 2 Creates the new command
  • 3 A public property for the new command
  • 4 Shows the counter view model, initialized with a new counter

This view model now takes an instance of the IMvxNavigationService interface as part of its constructor and stores it in a field. This code also adds a new async command that uses the async Navigate method on the navigation service to navigate to the CounterViewModel, passing a new Counter as the preparation parameter.

Unit-testing navigation to a new view

As well as being a powerful way to navigate between view models, the navigation service also allows for easy unit-testing. It’s made available via an interface, so it’s trivial to mock out. You can easily verify that your new command shows the relevant view model by adding some more tests to the CountersViewModelTests unit-test class, as follows.

Listing 8.38. Unit-testing the show-add-new-counter command
using MvvmCross.Core.Navigation;
...
Mock<IMvxNavigationService> navigationService;                           1

[SetUp]
public void SetUp()
{
   ...
   navigationService = new Mock<IMvxNavigationService>();                1
   viewModel = new CountersViewModel(countersService.Object,             2
                                     messenger.Object,                   2
                                     navigationService.Object);          2
   ...
}
...
[Test]
public async Task ShowAddNewCounterCommand_ShowsCounterViewModel()
{
   // Act                                                                3
   await viewModel.ShowAddNewCounterCommand.ExecuteAsync();              3
   // Assert                                                             4
   navigationService.Verify(n => n.Navigate(typeof(CounterViewModel),    4
                                            It.IsAny<Counter>(),         4
                                            null));                      4
}

  • 1 Creates a mock of the navigation service
  • 2 Passes the mock navigation service into the view model
  • 3 Executes the command
  • 4 Asserts that the correct view model was navigated to

For this unit test you need to create a new mock of IMvxNavigationService, and once it’s created, you pass it into the view model under test. You can then add a test to execute the new command using the ExecuteAsync method exposed by MvxAsyncCommand, and verify that it calls Navigate on the service to prove that the right view model was navigated to. ExecuteAsync isn’t part of the standard ICommand interface; instead, it’s a helper method on the MvvmCross commands, and it calls the underlying async method passed to the constructor of the command, allowing you to await the completion of the command.

Closing a view and navigating back

You now have the master view model set up as your app start, and you have a command that navigates to the counter view model using a new instance of Counter. The next thing you need is to allow the user to cancel or save the new counter. To do this, you need to add two commands to the counter view model. The following listing shows the code you need to add to CounterViewModel.

Listing 8.39. Adding save and cancel commands to the view model
using MvvmCross.Core.Navigation;
...
readonly IMvxNavigationService navigationService;                       1
...
public CounterViewModel(ICountersService service,
                        IMvxNavigationService navigationService)        1
{
   this.navigationService = navigationService;                          1
   ...
   CancelCommand = new MvxAsyncCommand(Cancel);
   SaveCommand = new MvxAsyncCommand(Save);
}

public IMvxAsyncCommand CancelCommand { get; }
public IMvxAsyncCommand SaveCommand { get; }

async Task Cancel()
{
   await navigationService.Close(this);                                 2
}

async Task Save()
{
   await service.AddNewCounter(counter.Name);                           3
   await navigationService.Close(this);                                 3
}

  • 1 Injects and stores an instance of the MvvmCross navigation service
  • 2 Closes the view model, removing the view from the stack
  • 3 Adds a new counter and then closes the view model

The cancel command just calls Close (a method on the IMvxNavigationService that closes a view model) passing in the view model to close (the current view model). This causes the presenter to remove the current view from the stack and show the previous one. The save command uses the counters service to create a new counter, and then closes the view model using the navigation service.

The CounterViewModel constructor has changed, so you’ll need to add the code in listing 8.40 to pass the navigation service when creating instances of this view model inside CountersViewModel.

Listing 8.40. Passing the navigation service to the counter view model
public async Task LoadCounters()
{
    ...

    foreach (var counter in counters)
    {
        var viewModel = new CounterViewModel(service,                 1
                                             navigationService);      1
        ...
    }
}

  • 1 Passes the navigation service through to the CounterViewModel constructor

This is almost everything you need. The only thing missing is code that shows the new counter on the master view. You don’t need to worry about the counter view model telling the counters view model that there’s a new counter. Instead you can use the same pattern for adding as you did for deletes. You can change the service to publish a message when a new counter is added, keeping the master list up to date. The following listing shows the code to add to the counters service.

Listing 8.41. The master list will automatically update
public async Task<Counter> AddNewCounter(string name)
{
   ...
   messenger.Publish(new CountersChangedMessage(this));         1
   return counter;
}

  • 1 Once a counter is saved, publish the message
Unit-testing saving counters and closing views

You have your commands, so now you need to unit-test them. For the save command you need to verify that the counter is saved to the counters service and that the view model is closed. For the cancel command you need to verify that the counter isn’t saved before the view model is closed. You can verify saving using the mock counters service already set up in the unit tests, and you can verify closing the view model using a mock navigation service.

The following code shows the test for the save command, so add it to the CounterViewModelTests class.

Listing 8.42. Testing the save command
using MvvmCross.Core.Navigation;
...
Mock<IMvxNavigationService> navigationService;                          1
...
[SetUp]
public void SetUp()
{
   ...
   navigationService = new Mock<IMvxNavigationService>();               1
   viewModel = new CounterViewModel(countersService.Object,             1
                                    navigationService.Object);          1
   ...
}
...
[Test]
public async Task SaveCommand_SavesTheCounter()
{
   // Arrange
   var counter = new Counter { Name = "A Counter" };
   viewModel.Prepare(counter);
   // Act
   await viewModel.SaveCommand.ExecuteAsync();                         2
   // Assert
   countersService.Verify(c => c.AddNewCounter("A Counter"));          3
   navigationService.Verify(n => n.Close(viewModel));                  4
}

  • 1 Sets up the mock navigation service
  • 2 Executes the command
  • 3 Verifies that the counter was saved
  • 4 Verifies that the view model was closed

The code to test the cancel command is nearly identical, except you need to verify that the save wasn’t called, as shown in the following listing.

Listing 8.43. Testing the cancel command
[Test]
public void CancelCommand_DoesntSaveTheCounter()
{
   // Arrange
   var counter = new Counter { Name = "A Counter" };
   viewModel.Prepare(counter);
   // Act
   viewModel.CancelCommand.Execute();
   // Assert
   countersService.Verify(c => c.AddNewCounter(It.IsAny<string>()),      1
                                                   Times.Never());       1
   navigationService.Verify(n => n.Close(viewModel));
}

  • 1 Verifies that AddNewCounter was never called

8.4. A quick roundup

This is pretty much everything you need. You’ve created a number of new classes for the two apps. Table 8.1 shows the classes for the SquareRt app, and table 8.2 shows them for the Countr app.

Table 8.1. The classes and interfaces created for the SquareRt app

Name

Description

SquareRtViewModel The view model for the square-root app containing the state for the input number, the result, and the behavior for performing the calculation whenever the number changes. This class optionally contains value conversion.
DoubleToStringValueConverter A value converter that convert from double values in the view model to string values used by the view, and vice versa.
SquareRtViewModelTests Unit tests for SquareRtViewModel.
DoubleToStringValueConverterTests Unit tests for DoubleToStringValueConverter.

Table 8.2. The classes and interfaces created for the Countr app

Name

Description

CounterViewModel The view model representing an individual counter and the state of the counter, such as its name and count, and encapsulating behavior for incrementing and deleting a counter, as well as saving a counter from a detail view and navigating back to the list view when the user saves the new counter or cancels the creation.
CountersViewModel The view model representing the master list of counters, represented as an observable collection of CounterViewModel instances. This observable collection will tell the view to update whenever the list of counters changes. This view model also encapsulates the behavior for navigating to a detail view to add a new counter, as well as detecting changes in the list of counters stored in the repository via a message.
CountersChangedMessage A message that’s published over the MvvmCross Messenger whenever the list of messages stored in the repository changes, such as when adding or deleting a counter.
CounterViewModelTests Unit tests for CounterViewModel.
CountersViewModelTests Unit tests for CountersViewModel.

You now have complete view models for both of the apps, covering the state and behavior needed to implement all the user flows. You’ve also created unit tests to validate your code.

The unit tests you’ve built here are not only great validators for your code, they allow you to simulate the UI and verify that your app will work before you’ve even finished the app. When a property is changed, the binding layer will update the view, so testing that a property-changed event is fired allows you to test that the UI is updated. By testing commands, you can verify what will happen when users tap buttons in the UI. By testing navigation, you can verify that your app will correctly flow from one view to another. You can write unit tests that simulate everything a user can do with your app, and most importantly you can write these tests just once. Xamarin is all about building cross-platform mobile apps with large amounts of code sharing, and that’s what you’re seeing with these view models—you can write and unit-test the UI logic once, yet still use it to build apps that target both iOS and Android.

In the next chapters we’ll start building the platform-specific view layers, starting with Android.

Summary

In this chapter you learned

  • State and behavior can be easily unit-tested.
  • When the types used by the view are different from the model, you need to provide conversion either inside a property or in a value converter.
  • The master/detail pattern is a nice way to show lists of data, with a separate screen that shows more details of an item in the list.
  • MvvmCross has lifecycle methods to let you know when your view model is being shown, and it has a way to pass data to a view model before it’s shown.
  • Messaging is a great way to let parts of your app communicate with each other in a loosely coupled way.
  • MvvmCross has a navigation service to handle view-model-first navigation.

You also learned how to

  • Add state to a view model
  • Convert property values both in place and using value converters
  • Unit-test properties and property-changed notifications
  • Implement behavior in commands
  • Create master and detail view models
  • Use messaging to communicate between classes in your apps in a loosely coupled way
  • Navigate between view models, passing data using the MvvmCross navigation service
  • Unit-test navigation
..................Content has been hidden....................

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