November 6, 2018

MvvmCross: Setup guide for Android, iOS and UWP Applications

MvvmCross is a powerful, modern MVVM framework for Xamarin. If we remember the principle of the MVVM pattern, we know that it exists as a way to decouple the UI from the implementation of business logic. As a result of this, it clearly has wondrous effects when we find ourselves developing cross-platform mobile applications! So, how does it work?

As cross-platform mobile developers, our aim is to share as much code as possible. This means that we generally create a shared class library for our core application logic. This shared core library is referenced by each platform projects. As a result of this, these public properties on the view models act as the entry point into our core application logic. The benefits here are two-fold; you write your logic once AND it’s easily testable. Above all, you will not be repeating yourself.

A working example of this article can be found on my Github here.

Noteworthy MvvmCross features

IoC

MvvmCross has built-in IoC which allows you to inject your services into the constructors of your ViewModels. In addition to the obvious benefits of DI, this is especially powerful when creating abstractions of platform-specific implementations ie. showing a dialog to the user.

Navigation

MvvmCross also deals with navigation. This means that the logic inside the ViewModel drives navigation. Navigation takes place as a “show me this ViewModel” command (MvxNavigationService.Navigate<TViewModel>). The framework then knows which view to instantiate based on which ViewModel needs to be shown.

Logging

MvvmCross also supports logging in a global manner via IMvxLog. You are also free to implement your own custom log provider if you so choose.

Plugins

MvvmCross also provides a wide array of plugins that address some of the most commonly used (and rewritten) functions. This saves you having to rewrite these implementations over and over again with each project. These include things like color, visibility, messengers and more.

Your first MvvmCross Solution

Ok, enough waffling. Let’s see this in action! In this exercise, we will create a 2-tier native solution. This is a common pattern that you will most likely use in an application of any scale. Let’s waste no time and dive right in!

The Core Project (aka. the shared code)

  • In Visual Studio, create a blank solution
  • Create a new class library of type .NET Standard and name it “TipCalc.Core”
  • Delete the auto-generated class (Class1.cs)
  • Install the MvvmCross nuget package
  • Create a folder and name it “Services”
  • Inside this folder, create a new interface and call it “ICalculationService”
namespace TipCalc.Core.Services
{
    public interface ICalculationService
    {
        double TipAmount(double subTotal, int generosity);
    }
}
  • Next, create the implementation of this interface and name it “CalculationService”
namespace TipCalc.Core.Services
{
    public class CalculationService : ICalculationService
    {
        public double TipAmount(double subTotal, int generosity)
        {
            return subTotal * ((double)generosity)/100.0;
        }
    }
}
  • Create a folder and name it “ViewModels”
  • Next, create a class inside this folder and name it “TipViewModel”
using MvvmCross.ViewModels;
using TipCalc.Core.Services;
using System.Threading.Tasks;

namespace TipCalc.Core.ViewModels
{
    public class TipViewModel : MvxViewModel
    {
        readonly ICalculationService _calculationService;

        public TipViewModel(ICalculationService calculationService)
        {
            _calculationService = calculationService;
        }

        public override async Task Initialize()
        {
            await base.Initialize();

            _subTotal = 100;
            _generosity = 10;

            Recalculate();
        }

        private double _subTotal;
        public double SubTotal
        {
            get => _subTotal;
            set
            {
                _subTotal = value;
                RaisePropertyChanged(() => SubTotal);

                Recalculate();
            }
        }

        private int _generosity;
        public int Generosity
        {
            get => _generosity;
            set
            {
                _generosity = value;
                RaisePropertyChanged(() => Generosity);

                Recalculate();
            }
        }

        private double _tip;
        public double Tip
        {
            get => _tip;
            set
            {
                _tip = value;
                RaisePropertyChanged(() => Tip);
            }
        }

        private void Recalculate()
        {
            Tip = _calculationService.TipAmount(SubTotal, Generosity);
        }
    }
}

Things to note about this ViewModel are

  • Injection of the ICalculationService we previously created
  • It inherites from MvxViewModel
  • The async initialize method used to setup the values needed by the ViewModel
  • The public properties that are exposed by the ViewModel. These will later be bound to the views in the various platform projects
The setup code

Next, you need to add some setup logic in order to fire up the MvvmCross framework.

  • Add a new class to the Core project root and name it “App”
using MvvmCross;
using MvvmCross.ViewModels;
using TipCalc.Core.Services;
using TipCalc.Core.ViewModels;

namespace TipCalc.Core
{
    public class App : MvxApplication
    {
        public override void Initialize()
        {
            Mvx.IoCProvider.RegisterType<ICalculationService, CalculationService>();

            RegisterAppStart<TipViewModel>();
        }
    }
}

Here is where our DI is defined. If you are new to DI/IoC, it is a way to loosely couple usage and implementation. What this allows us to do is simply create a new ViewModel and specify which service interfaces it will need. The framework then takes care of the instantiation and life cycle of these dependencies. With MvvmCross, you can also resolve dependencies on an ad-hoc basis through the built-in IoC provider. After we setup the DI, we then go on to define the first ViewModel that will be shown on app start.

With that out of the way, we now have a complete core project! All that’s left to do now is create the platform projects and hook them up to the ViewModel!

The Platform Projects

Now, we can take a look at setting up the platform projects. We’ll need to do a bit of setup on each platform, as well as learn the respective binding syntax.

Android
The MainApplication class
  • Add a new project to the solution of type “Blank App (Android)” and name it “TipCalc.Droid”
  • Delete MainActivity.cs and Main.axml in the Resources\layout folder
  • Install the MvvmCross nuget package
  • Add a reference to the Core project
  • Create a new class and name it “MainApplication”
using System;
using Android.App;
using Android.Runtime;
using MvvmCross.Platforms.Android.Core;
using MvvmCross.Platforms.Android.Views;
using TipCalc.Core;

namespace TipCalc.Droid
{
    [Application]
    public class MainApplication : MvxAndroidApplication<MvxAndroidSetup<App>, App>
    {
        public MainApplication(IntPtr javaReference, JniHandleOwnership transfer)
            : base(javaReference, transfer)
        {
        }
    }
}

This class is responsible for firing up the MvvmCross framework on the Android platform, and initializes features like IoC, data-binding, ViewModels and their respective view lookups, etc.

The View
  • In the Resources\layout folder, add a new Android Layout file (.axml) and name it “TipView.axml”
  • Take a look at the XML content of the file. First off, we need to add the local res-auto namespace, which will enable the engine to make sense of the MvvmCross binding commands (among other things). Your root LinearLayout should look like this:
<?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="match_parent"
    android:layout_height="match_parent" />
  • We’ll add the necessary controls to the view, and the final view should look as follows:
<?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="match_parent"
    android:layout_height="match_parent">
    <TextView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="SubTotal" />
    <EditText
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          local:MvxBind="Text SubTotal" />
    <TextView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="Generosity" />
    <SeekBar
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:max="100"
          local:MvxBind="Progress Generosity" />
    <View
          android:layout_width="match_parent"
          android:layout_height="1dp"
          android:background="#ffff00" />
    <TextView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="Tip to leave" />
    <TextView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          local:MvxBind="Text Tip" />
</LinearLayout>
MvvmCross Android Example UI
MvvmCross Android Example UI

Important things to note here is the MvvmCross binding syntax used in Android. Most notably

local:MvxBind="Text SubTotal"

This is a binding call to the Text property of the TextView, binding to the SubTotal public property on the ViewModel we created. When RaisePropertyChanged is called, the UI is refreshed to display the new value.

The Activity (The actual V in MVVM)

Now, we’re ready to add the View class, which is just an Android Activity. This will be the View part of the MVVM pattern.

  • Create a folder called “Views” in the root of the Android project
  • Add a new Activity to the folder and name it “TipView”

It should be noted that Activities are classes on Android that provide a current context for your app.

This is how we wire up the View and the ViewModel. All we have to do is inflate the content view, and we’re done! The complete Activity looks as follows:

using Android.App;
using Android.OS;
using MvvmCross.Platforms.Android.Views;
using TipCalc.Core.ViewModels;

namespace TipCalc.Droid.Views
{
    [Activity(Label = "Tip Calculator", MainLauncher = true)]
    public class TipView : MvxActivity<TipViewModel>
    {
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.TipView);
        }
    }
}

And with that, Android project is done! Run your application and play around with the controls.

iOS
Project Creation

As always, we must create our project and install the MvvmCross nuget package.

  • Add a new project to the solution of type “Blank App (iOS)” and name it “TipCalc.iOS”
  • Install the MvvmCross nuget package

The Setup class is automatically added, although we still have the ability to add a custom Setup class if we so desire

Adding the setup stuff

Next we need to modify our AppDelegate class in order to wire up MvvmCross.

namespace TipCalc.iOS
{
    [Register(nameof(AppDelegate))]
    public class AppDelegate : MvxApplicationDelegate<MvxIosSetup<App>, App>
    {
        public override UIWindow Window { get; set; }

        // FinishedLaunching is the very first code to be executed in your app. Don't forget to call base!
        public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
        {
            var result = base.FinishedLaunching(application, launchOptions);

            return result;
        }
    }
}
Adding the View

Now, we can add the view. Note that MvvmCross works with both Storyboards and XIB files, however, in my recent experience, support for Storyboards is more complete. As a result of this, this article will be using Storyboards. It is also important to note that I personally prefer a one-to-one mapping between Storyboard and ViewController, rather than one giant Storyboard with many ViewControllers.

Hooking up the ViewController
  • Delete Main.Storyboard
  • Delete any auto-generated ViewController that may have been created
  • Create a folder and name it “Views”
  • Inside of this folder, Add new item of type Empty Storyboard and name it “TipView”
  • Add a ViewController to the Storyboard and name it “TipView”
  • Set the Storyboard ID of the ViewController to “TipView”
  • Open the Storyboard and add the following controls
iOS TipView
  • Name the TextField “SubtotalTextField”
  • Next, name the Slider “GenerositySlider”
  • Finally, name the last label “TipLabel” for displaying the calculation result
Wiring up our ViewController

Now, we’ll open our ViewController that was automatically created (Named TipView)

By default, it will inherit from UIViewController. Change this to the following:

[MvxFromStoryboard("TipView")]
public partial class TipView : MvxViewController<TipViewModel>

It is important to note the use of the MvxFromStoryboard attribute. This wires up the Storyboard view and the ViewController. In this case, TipView is the Storyboard ID.

Now, we’ll add our binding code to the overridden ViewDidLoad method, as follows:

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

            var set = this.CreateBindingSet<TipView, TipViewModel>();
            set.Bind(TipLabel).To(vm => vm.Tip);
            set.Bind(SubTotalTextField).To(vm => vm.SubTotal);
            set.Bind(GenerositySlider).To(vm => vm.Generosity);
            set.Apply();
        }

One last thing to do is open the Info.plist file and remove the reference to Main.storyboard

	<key>UIMainStoryboardFile</key>
	<string>Main</string>

We’re done! Build and run your project. Play around with the amount and slider.

UWP

Finally, we’ll look at hooking up a UWP project to our core logic.

  • Create a new project of type ‘Blank App (Universal Windows) and name it “TipCalc.Uwp”
  • Delete MainPage.xaml
  • Install the MvvmCross nuget package
  • Open App.xaml.cs and delete everything except the constructor
public sealed partial class App
{
    public App()
    {
        InitializeComponent();
    }
}
  • Next, we need to hook into the MvvmCross framework. In the same file, define a new class as follows:
public abstract class TipCalcApp : MvxApplication<MvxWindowsSetup<Core.App>, Core.App>
{
}

This class will be used in the XAML code, as it extends MvxApplication. The App.xaml.cs file shoul now look as follows:

using MvvmCross.Platforms.Uap.Core;
using MvvmCross.Platforms.Uap.Views;

namespace TipCalc.UWP
{
    public sealed partial class App
    {
        public App()
        {
            InitializeComponent();
        }
    }

    public abstract class TipCalcApp : MvxApplication<MvxWindowsSetup<Core.App>, Core.App>
    {
    }
}

Now, edit App.xaml. Open the file and replace all content with the following:

<local:TipCalcApp x:Class="TipCalc.UWP.App"
                        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:local="using:TipCalc.UWP"
                        RequestedTheme="Light">
    <Application.Resources>
        <x:String x:Key="WelcomeText">Hello World!</x:String>
    </Application.Resources>
</local:TipCalcApp>
MvvmCross UWP Example UI
MvvmCross UWP Example UI

This code sets the App base class to type of TipCalcApp.

Additionally, every MvvmCross app requires a Setup class, but if your app is simple, such as this example, you can safely use the default one.

Adding the View

Now, add the View by doing the following:

  • Create a folder and name it “Views”
  • Inside this folder, add a new Xaml blank page and name it “TipView”
  • As expected, this generates a xaml view and a .cs code behind
  • Open the TipView.cs file and make it inherit from MvxWindowsPage, like so:
public sealed partial class TipView : MvxWindowsPage

Now, add an MvxViewForAttribute

[MvxViewFor(typeof(TipViewModel))]
public sealed partial class TipView : MvxWindowsPage

The complete file should look as follows:

using MvvmCross.Platforms.Uap.Views;
using MvvmCross.ViewModels;
using XamNativeShowcase.Core.ViewModels;

namespace XamNativeShowcase.Uwp.Views
{
    [MvxViewFor(typeof(TipViewModel))]
    public sealed partial class TipView : MvxWindowsPage
    {
        public TipView()
        {
            this.InitializeComponent();
        }
    }
}

Now open the XAML file and add the same controls as the previous platforms. The file should look as follows:

<views:MvxWindowsPage
    x:Class="XamNativeShowcase.Uwp.Views.TipView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:XamNativeShowcase.Uwp.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:views="using:MvvmCross.Platforms.Uap.Views"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Margin="12,0,12,0">
            <TextBlock Text="SubTotal" />
            <TextBox Text="{Binding SubTotal, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            <TextBlock Text="Generosity" />
            <Slider Value="{Binding Generosity,Mode=TwoWay}" 
                SmallChange="1" 
                LargeChange="10" 
                Minimum="0" 
                Maximum="100" />
            <TextBlock Text="Tip" />
            <TextBlock Text="{Binding Tip}" />
            <Button Content="Navigate to Map" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Command="{Binding NavigateToMapViewCommand}"/>
            <Button Content="Navigate to SignalR" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Command="{Binding NavigateToSignalRCommand}"/>
        </StackPanel>
    </Grid>
</views:MvxWindowsPage>

Now, build and run the application and play around with the UI. Your UWP project is now complete!

Conclusion

In conclusion, you have seen how to scaffold an MvvmCross solution for Android, iOS and UWP. I hope that you can see the benefits as a result of using this framework for your projects.

A working example of this article can be found on my Github here.

You may also like...

1 Response

  1. November 6, 2018

    […] This assumes you’ve already set up MvvmCross. If not, I recommend following my guide here. […]

Leave a Reply