Creating cross-platform Xamarin applications using the MvvmCross framework

Authors:

09.02.2017
The following text is an English translation of an article by Sylwester Wieczorkowski, published in the 06/2016 (49) issue of a Polish IT magazine "Programista":
https://programistamag.pl/tworzenie-wieloplatformowych-aplikacji-xamarin-z-wykorzystaniem-frameworka-mvvmcross/
The translation was performed by Leaware.

WHAT IS MVVMCROSS?


As the name suggests, MvvmCross is a framework facilitating the creation of cross-platform applications conforming with the MVVM model (Model-View-ViewModel). It supports many popular types of .NET projects, such as:
  • Xamarin.Android
  • Xamarin.iOS
  • Xamarin.Mac
  • WinRT (Windows 8.1, Windows Phone 8.1)
  • Universal Windows Platform (UWP) (Windows 10)
  • Windows Presentation Foundation (WPF)
It also provides mechanisms for data binding for platforms that natively use the MVC model (Model-View-Controller).

MvvmCross applications usually are composed of two fundamental parts:
  • A core project built around a Portable Class Library (PCL), containing all the viewmodels, models and interfaces of platform-specific services. The core PCL carries the business logic, database handling and a layer of access to web services.
  • A native project for each platform, containing the user interface and implementation of platform-specific services.
It is a good practice to create an additional PCL project in order to separate the application business logic from the data access layer. Figure 1 illustrates this structure for a project realised in the Xamarin.Android and Xamarin.iOS technologies.

Figure 1. Architecture of the MvvmCross solution

The amount of shared code changes depending on the application type. Of course, if our application uses more of the native API, a smaller part of the solution will be used again. In the case of business applications it is possible to share about 70-80% of the entire solution.

In order to start your adventure with the MvvmCross framework, you need to create a solution containing all the necessary projects: at least one PCL library and a native project for each platform you plan to support. Next, you need to add a NuGet MvvmCross Starter Pack to each of them and make a few basic configuration steps described in the files contained in the ToDo-MvvmCross directory.

The entire MvvmCross framework, together with documentation and video tutorials, is available on GitHub at https://github.com/MvvmCross/MvvmCross.


BASIC ELEMENTS OF THE FRAMEWORK

Every MvvmCross application has certain elements: an App class, a Setup class and viewmodels. This section examines those parts along with typical implementations.

Listing 1 below shows a typical example of the App class. In every MvvmCross application there is exactly one implementation of the App class, which inherits from the MvxApplication class. The Initialize method registers the entry point: the first viewmodel that will be created after entering the application (in this case ProductsViewModel). Initialize also registers types injected on the common side.

Listing 1. Example implementation of the App class in a PCL project
public class App : MvvmCross.Core.ViewModels.MvxApplication
{
    public override void Initialize()
    {
        CreatableTypes()
            .EndingWith("Service") 
            .AsInterfaces() 
            .RegisterAsLazySingleton();
RegisterAppStart<ViewModels.ProductsViewModel>; Mvx.RegisterType<IProductRepository>( () => new ProductWebRepository("http://webservice/api/product/")); } }

MvvmCross provides the static Mvx class, which functions as a container for injecting dependencies and is responsible for managing implementations registered both in the common part and in the platform-specific projects in the Setup class.

The Setup class is a kind of bootstrapper for MvvmCross, and is present in every platform-specific project. Project Xamarin.Android is an example (Listing 2). The basic task of this class is to create an instance of the App class, as well as to adjust the framework to the specifics of our application.

Listing 2. Implementation of the Setup class in the Xamarin.Android project
public class Setup : MvxAndroidSetup
{
    public Setup(Context applicationContext) : base(applicationContext)
    {
    }
protected override IMvxApplication CreateApp() { return new Core.App(); }
protected override IMvxTrace CreateDebugTrace() { return new DebugTrace(); }
protected override void InitializePlatformServices() { base.InitializePlatformServices(); Mvx.RegisterType<ICallerService, DroidCallerService>(); Mvx.RegisterType<IEmailService, DroidEmailService>(); Mvx.RegisterType<IPopupService, DroidPopupService>(); } }

The MvxAndroidSetup class, from which the Setup class inherits, provides a series of virtual methods that need to be overridden in order to, among other things, register all the platform services (referring to the native API). The platform-specific services are used in the common part in order to execute instructions specific for every platform – the Inversion of Control mechanism.

Another important element of the MvvmCross solution is the viewmodel, which functions as a container for properties and commands responsible for changing the state and retaining the view related to it.

Listing 3. A fragment of an example viewmodel
public class ProductsViewModel : MvxViewModel
{
    private IProductRepository productRepository;

    private List<Product> products;
    public List<Product> Products
    {
        get { return products; }
        set { SetProperty(ref products, value); }
    }

    private bool isAddButtonEnabled;
    public bool IsAddButtonEnabled
    {
        get { return isAddButtonEnabled; }
        set
        {
            isAddButtonEnabled = value;
            RaisePropertyChanged(() => IsAddButtonEnabled);
        }
    }
private IMvxCommand adddProductCommand; public IMvxCommand AddProductCommand { get { adddProductCommand = adddProductCommand ?? new MvxCommand(
() => ShowViewModel<AddProductViewModel>());
return adddProductCommand; } }
public ProductsViewModel(IProductRepository productRepository) { this.productRepository = productRepository; }
... }

The base class of the presented fragment of a viewmodel (Listing 3) contains an implementation of interfaces INotifyPropertyChanged, INotifyCollectionChanged and the methods such as SetProperty or RaisePropertyChanged. These interfaces and methods allow the system to refresh elements of a view: when certain properties change they trigger an event that conveys that change through the UI. Commands implemented using the MvxCommand class manage individual actions performed by the user, e.g. changing a view. MvxViewModel provides many useful methods for functions such as navigation between viewmodels (ShowViewModel) or management of view lifecycle. The job of the Mvx container is to automatically inject dependencies into created viewmodels.


DEFINING THE USER INTERFACE: HOW TO CREATE VIEWS

The data binding mechanism is a natural element of the Windows ecosystem (WPF, WinRT and UWP), so in Windows the method for creating views uses the native approach. Therefore, let’s concentrate on the Android and iOS platforms, for which the native model is MVC, where controllers (iOS) and activities/fragments (Android) play a key role.

An exceptional advantage of the MvvmCross framework is the fact that contrary to Xamarin.Forms all the views, layouts, widgets are defined entirely natively, using native mechanisms and tools.

Xamarin.Android

In the case of Android (Listing 4) we create xml or axml files (or axml) that use only the native API for constructing layouts for individual views. MvvmCross provides the local:MvxBind attribute, which can be used for binding properties of the elements of the view (widgets, layouts) with appropriate properties of a viewmodel according to the above listing.

Listing 4. Definition of a layout for the Android system 
<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/ res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="20dp">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginBottom="20dp">

    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="0.5"
        android:text="Add Product"
        local:MvxBind="Click AddProductCommand;
Enabled IsAddButtonEnabled" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.5" android:text="Remove All" local:MvxBind="Click RemoveAllProductsCommand; Enabled IsRemoveAllButtonEnabled" /> </LinearLayout> <MvxListView android:layout_width="fill_parent" android:layout_height="wrap_content" android:divider="@null" android:scrollbars="none" android:footerDividersEnabled="false" android:overScrollFooter="@android:color/transparent" local:MvxItemTemplate="@layout/view_product_item" local:MvxBind="ItemsSource Products" />
</LinearLayout>

A framework provides an additional set of UI controls. One of them is a widget (MvxListView) that displays a list of elements. MvxListView specifies a template of a cell of a given list using the local:MvxItemTemplate property. Implementation of an adapter is unnecessary, so we avoid excess code in the platform-specific project.

Listing 5. Implementation of activity in the Xamarin.Android project
[Activity]
public class ProductsActivity : MvxActivity
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        SetContentView(Resource.Layout.layout_products_activity);
    }
}

In MvvmCross, controllers are used only to load the view and bind it with the correct viewmodel (Listing 5). Of course, if the application requires providing certain specific functionalities/actions on a given platform, they can be implemented in the controllers. However, one must remember that this will reduce the amount of code that can be shared, and possible differences or inconsistencies in application behaviour make debugging and implementing additional changes more time consuming.

Xamarin.iOS


Creation of a view for the iOS system starts with adding a controller class together with a file with an xib extension which represents the view. Edit xib files with Xamarin Studio (the environment provided by Xamarin) or the Xcode Interface Builder tool built-in in Xcode (Figure 2).

Figure 2. Creating a view in Xcode Interface Builder

After arranging the view, hold the CTRL key and drag the elements that you want to bind with the viewmodel to the appropriate header file. The changes will be automatically synchronised to the C# language, and more specifically to the class of the created controller (Listing 6). Properties of the controller marked with the attribute Outlet are called outlets. Through them, we get access to individual elements of the user interface.

Listing 6. A section of the controller class containing synchronised outlets 
[Register ("ProductsViewController")]
partial class ProductsViewController
{
    [Outlet]
    UIKit.UIButton AddProductButton { get; set; }

    [Outlet]
    UIKit.UITableView ProductList { get; set; }

    [Outlet]
    UIKit.UIButton RemoveAllButton { get; set; }
}

After loading the view we create a set which binds properties of available outlets with the properties of a given viewmodel (Listing 7). The Bind method accepts the object (usually an outlet), which we will consider. The For method specifies the object property which will be bound with the property of the viewmodel specified by the To method. If the For method is skipped, default property of a given outlet is bound.

Listing 7. Main section of the controller class – binding data
public partial class ProductsViewController : BaseViewController
{
    public ProductsViewController() : base("ProductsViewController", null)
    {
    }

    public override void ViewDidLoad()
    { 
        base.ViewDidLoad();
        InitializeBinding();
    }

    private void InitializeBinding()
    { 
        var set = this.CreateBindingSet<ProductsViewController, ProductsViewModel>();

        var source = new ProductListDataSource(ProductList);
        ProductList.Source = source;
        set.Bind(source).For(mn => mn.ItemsSource).To(mn => mn.Products);

        set.Bind(AddProductButton).To(mn => mn.AddProductCommand);
        set.Bind(AddProductButton).For(mn => mn.Enabled).To(mn => mn.IsAddButtonEnabled);

        set.Bind(RemoveAllButton).To(mn => mn.RemoveAllProductsCommand);
        set.Bind(RemoveAllButton).For(mn => mn.Enabled).To(mn => mn.IsRemoveAllButtonEnabled);

        set.Apply();
    }
}


ADVANCED DATA BINDING – CREATING CONVERTERS AND CUSTOM BINDINGS


Frequently, composed views require additional conversion (translation) of bound data, i.e. changing the type or format of viewmodel properties, which gets bound with the property of a given UI control. For this purpose it is possible to define a converter implementing an abstract class MvxValueConverter (Listing 8).

Listing 8. Converter translating the bool value into appropriate UIColor
public class BoolToTextColorConverter : MvxValueConverter
{
    protected override UIColor Convert(bool value, Type targetType, object parameter, CultureInfo culture)
    {
        return value ? UIColor.Red : UIColor.Black;
    }
}

How to apply the defined converter? In the case of Xamarin.iOS it comes down to requesting the WithConversion method, which assumes the converter instance (Listing 9).

Listing 9. Data binding with the converter in the Xamarin.iOS project
set.Bind(EmailTextField).For(x => x.TextColor)
    .To (x => x.IsErrorVisible)
    .WithConversion(new BoolToTextColorConverter());

Xamarin.Android seems to be even more intuitive – it is enough to connect the bound property with the name of our converter (Listing 10).

Listing 10. Data binding with the converter in the Xamarin.Android project
<EditText
    style="@style/EmailEditText"
    local:MvxBind="TextColor BoolToTextColor(IsErrorVisible)" />

Frequently, it could occur that a specific property of a viewmodel determines the change of a few properties of a widget or requires changing the widget, which can be performed only by requesting calling one or a series of methods for it. In such a case the mechanism allowing for registering custom data bindings becomes necessary.

Listing 11. An example definition of a custom data binding for Xamarin.Android
public class TextViewWithHtmlBinding : MvxConvertingTargetBinding 
{
    public TextViewWithHtmlBinding(TextView textView)
        : base(textView)
    {
    }

    public override Type TargetType
    {
        get
        { 
            return typeof(TextView);
        }
    }

    protected override void SetValueImpl(object target, object value)
    {
        var textView = target as TextView;
        textView.MovementMethod = LinkMovementMethod.Instance;
        textView.SetText(Html.FromHtml((string)value), TextView.BufferType.Spannable);
    }
}

The first step is to create a class inheriting from the MvxConvertingTargetBinding class according to the presented example (Listing 11). In the presented example we defined a binding for the text value containing HTML markups. In order to interpret them correctly, the SetText method needs to be used with appropriate parameters and the property MovementMethod of the TextView widget must be changed – we are not able to do it effectively, based on default bindings.

Listing 12. Registration of a custom binding in the Setup class
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{ 
    registry.RegisterCustomBindingFactory<TextView>("TextWithHtml", x => new TextViewWithHtmlBinding(x)); 

    base.FillTargetFactories(registry); 
}

Next, overload override the FillTargetFactories method in the Setup class, registering the created binding under a selected name with appropriate type of UI control (Listing 12). The entire process is analogous to the Xamarin.iOS system. A registered binding can be successfully used in the entire application in the same way as standard predefined data bindings.


CHANGING THE STANDARD NAVIGATION SCHEME


Each platform-specific MvvmCross package contains a default presenter implementing the IMvxViewPresenter interface. The presenter is responsible for providing a navigation scheme between certain views. By default it uses the mechanism of reflection for associating controllers with viewmodels corresponding to them, therefore the key element is to give names to controllers which correspond to the names of viewmodels used in the PCL project. If for some reason we want to change the default navigation scheme, it is enough to override appropriate methods of the default presenter (Listing 13), and after that return it in the CreateViewPresenter method in the Setup class (Listing 14). We deal with this situation, for example, when there is a need to associate a certain group of viewmodels with fragments displayed in the area of main activity – flyout navigation.

Listing 13. Overriding of the default presenter MvxAndroidViewPresenter in the Xamarin.Android project
public class DroidPresenter : MvxAndroidViewPresenter
{
    public override void Close(IMvxViewModel viewModel)
    {
        Activity.FinishAffinity();
    }
public override void Show(MvxViewModelRequest request) { base.Show(request); } }
Listing 14. Overriding the default presenter in the Setup class
protected override IMvxAndroidViewPresenter CreateViewPresenter()
{
    return new DroidPresenter();
}


UNIT TESTS


Undoubtedly, unit tests are some of the most important elements in the process of ensuring high quality of the created software. Using the MvvmCross framework requires programmers to create a testable architecture of the entire solution, thanks to which, without too much workload, we are able to write unit tests for particular elements of our business logic. The NUnit framework recommended by Xamarin executes this function perfectly.
Writing tests must start with adding to our testing project the following set of NuGet packages: MvvmCross, MvvmCross.Core and MvvmCross.Tests. Naturally, one also needs to ensure that references are attached added to the project with the business logic of the application, as well as tools allowing for quick imitation of objects – one of the more popular is the Moq framework.

Listing 15. Creating unit tests using NUnit and MvvmCross.Tests
[TestFixture] 
public class AddProductViewModelTests : MvxIoCSupportingTest
{ 
    [SetUp]
    public new void Setup()
    { 
        base.Setup();

        Ioc.RegisterType(() => new Mock<IProductRepository>().Object);
    }

    [TestCase("", "2.5", false)]
    [TestCase("Product 1", "", false)]
    [TestCase("", "", false)]
    [TestCase(null, null, false)]
    [TestCase("Product 1", "2.5", true)]
    public void IsAddButtonEnabled_NamePrice(string name, string price, bool expectedValue) 
    {
        var addProductViewModel = Mvx.IocConstruct<AddProductViewModel>();

        addProductViewModel.Name = name;
        addProductViewModel.Price = price;
        var actualValue = addProductViewModel.IsAddButtonEnabled;

        Assert.AreEqual(expectedValue, actualValue);
    }
}

The MvvmCross.Tests package contains the MvxIoCSupportingTest class, which is a base class for every newly created testing class (Listing 15). Using the Ioc property we register all the types necessary for creating a viewmodel which we are going to test (in the example I mock IProductRepository using the Moq framework). The Mvx container is responsible for creating a viewmodel and providing all the necessary dependencies. Next, we assign test values to specific properties of the viewmodel and we test the behaviour of the remaining ones. In practice, they will determine the changes of the view state associated with the tested viewmodel. The SetUp class and test instances are specified by using standard attributes of the NUnit framework.


PLUGINS AND ADDITIONAL COMPONENTS


The MvvmCross framework provides a large number of plugins and libraries available on GitHub at https://github.com/MvvmCross/MvvmCross-Plugins, as well as in the form of NuGet packages. They are, among others, components for simplifying database operation, network availability, connections, locations, operations on files, sending e-mails, integration with social networks, downloading and storing data in cache memory. All we need to do is add an appropriate package to all the projects in a solution, and then use an available API in the common part. There is also a possibility to create your own internal plugins or developing existing ones in accordance with the instruction available at https://github.com/MvvmCross/MvvmCross/wiki/MvvmCross-plugins.


SUMMARY

This article presents only selected, most significant mechanisms of the MvvmCross framework. The discussed solution is continuously developed, providing more and more new possibilities. It is undoubtedly the best solution for complex and demanding Xamarin business applications. Thanks to native methods of building a user interface we can deliver a great UX, and at the same time share a significant part of the entire solution including testable business logic of the application. I had the pleasure of participating in complex projects realised with the use of this technology composed of dozens of screens and functionalities, such as mobile banking applications for serious foreign clients, which from the moment of publication have received the highest ratings from hundreds of users, which is the best proof that the presented solution is effective.