Getting-started

Fundamentals

Advanced

Platforms

Plugins

Samples

Tutorials

Upgrading

Contributing

The Core Project

Edit

MvvmCross applications normally consist on:

  • A ‘Core’ project in the form of a .NET Core library, which will contain all the shared code (so you want to maximize the amount of code placed in this project). The Core will contain Models, ViewModels, Services, Converters, …
  • One ‘Platform’ project per targeted platform. These projects will contain some framework initialization code, Views and SDK dependant code.

Normally, you start development from the Core project - and that’s exactly what we’ll do here.

Although it is recommended that you install any of the community made solution templates, we’ll use a blank solution on this tutorial.

Create the new .NET Core library

Using Visual Studio, create your new .NET Class Library project using the File New Project wizard. Choose .Net 7 as project framework.

Call it something like TipCalc.Core and name the solution TipCalc.

Delete any auto-generated class

No-one really needs something like Class1.cs :)

Install MvvmCross. Yey!

Open the Nuget Package Manager and search for the package MvvmCross.

If you don’t really enjoy the NuGet UI experience, then you can alternatively open the Package Manager Console, and type:

Install-Package MvvmCross

Add the Tip Calculation Service

Create a folder called Services.

Within this folder create a new interface, which will be used for calculating tips:

namespace TipCalc.Core.Services;
public interface ICalculationService
{
    double TipAmount(double subTotal, int generosity);
}

Within the Services folder now create an implementation for the interface:

namespace TipCalc.Core.Services;
public class CalculationService : ICalculationService
{
    public double TipAmount(double subTotal, int generosity)
    {
        return subTotal * ((double)generosity)/100.0;
    }
}

This provides us with some simple business logic for our app.

Add the ViewModel

At a sketch level, we want a user interface that:

  • Uses our calculation service to calculate the tip
  • Has inputs of:
    • The current bill (the SubTotal)
    • A feeling for how much tip we’d like to leave (the generosity)
  • Has an output for the calculated tip to leave

To represent this user interface we need to build a ‘model’ for it. In other words, we need a ViewModel.

Within MvvmCross, all ViewModels should inherit from MvxViewModel.

So now let’s create a folder called ViewModels, and inside of it a new class named TipViewModel. This is what it should look like:

using MvvmCross.ViewModels;
using TipCalc.Core.Services;

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);
    }
}

It is possible that this TipViewModel will already make sense to you. If it does, then skip ahead to ‘Add the App(lication)’. If not, then here are some further explanations:

  • the TipViewModel is constructed with an ICalculationService service, which is injected using the MvvmCross Dependency Injection engine.
readonly ICalculationService _calculationService;

public TipViewModel(ICalculationService calculationService)
{
    _calculationService = calculationService;
}
  • After construction, the TipViewModel runs the Initialize method, which is part of the ViewModel lifecycle - during this it sets some initial values.
public override async Task Initialize()
{
    await base.Initialize();

    _subTotal = 100;
    _generosity = 10;

    Recalculate();
}
  • The view data held within the TipViewModel is exposed through properties, where:
    • Each of these properties is backed by a private member variable
    • Each of these properties has a getter and a setter
    • All of the set accessors call RaisePropertyChanged to tell the base MvxViewModel that the data has changed
    • The SubTotal and Generosity set accessors also call 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);
    }
}
  • The private Recalculate method uses the _calculationService to update Tip from the current values of SubTotal and Generosity.
private void Recalculate()
{
    Tip = _calculationService.TipAmount(SubTotal, Generosity);
}

Add the App(lication) class

With our CalculationService and our TipViewModel defined, we now just need to add the main App code. This code:

  • Will sit in a single class within the root folder of our .NET Standard project.
  • Will inherit from the MvxApplication class
  • Is normally just called App
  • Is responsible for providing:
    • Registration of which interfaces and implementations the app uses
    • Registration of which ViewModel the App will show when it starts

‘Registration’ here means creating an entry on the ‘Inversion of Control’ Container - IoC -. This record tells the IoC Container what to do when anything asks for an instance of the registered interface.

Our “Tip Calculation” App class will register the ICalculationService as a dynamic service:

Mvx.IoCProvider.RegisterType<ICalculationService, CalculationService>();

The previous line tells the IoC Container that whenever any code requests an ICalculationService reference, an object of type CalculationService should be created and returned.

Also note that the single static class Mvx acts as a single place for both registering and resolving interfaces and their implementations.

Within the App class we also decide that we want the app to start with the TipViewModel:

RegisterAppStart<TipViewModel>();

The previous line tells the MvvmCross framework that TipViewModel should be the first ViewModel / View pair that should appear on foreground when the app starts.

In summary, this is what App.cs should look like:

using MvvmCross.ViewModels;
using MvvmCross;
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>();
    }
}

Our Core project is complete :)

Just to recap the steps we’ve followed:

  1. We created a new .NET Standard project
  2. We added the MvvmCross libraries via NuGet
  3. We added a ICalculationService interface and implementation pair
  4. We added a TipViewModel which:
    • Inherits from MvxViewModel
    • Declares a dependency on ICalculationService on its constructor
    • Presents a number of public properties each of which called RaisePropertyChanged internally
  5. We added an App which:
    • Inherits from MvxApplication
    • Registers the ICalculationService/CalculationService pair
    • Registers a ViewModel to use for when the app starts

These are the same steps that you need to go through for every new MvvmCross application.

The next step is about building a first UI for our MvvmCross application.

Next!

Side note: What is ‘Inversion of Control’?

We won’t go into depth here about what IoC - Inversion of Control - is.

Instead, we will just say that:

  • Within each MvvmCross application, there is a very special object, which is a singleton.
  • This singleton lives within the Mvx.IoCProvider singleton instance.
  • The application startup code can use the Mvx.Register... methods to let Mvx.IoCProvider know how to resolve certain requests during the lifetime of the app.
  • After the registration has been done, then when any code asks for an interface implementation, it can do that by using the Mvx.IoCProvider.Resolve methods.

One common pattern the app is also using is ‘constructor injection’:

  • Our TipViewModel uses this pattern.
  • It presents a constructor like: public TipViewModel(ICalculationService calculationService).
  • When the app is running a part of the MvvmCross framework called the ViewModelLocator is used to find and create ViewModels.
  • When a TipViewModel is needed, the ViewModelLocator uses a call to Mvx.IocConstruct to create one.
  • This Mvx.IocConstruct call creates the TipViewModel using the ICalculationService implementation that it finds using Mvx.IoCProvider.Resolve

This is obviously only a very brief introduction.