Repository navigation
Tip Calc The Core Project
MvvmCross application's are normally structured with:
- one shared 'core' Portable Class Library (PCL) project
- containing as much code as possible: models, view models, services, converters, etc
- one UI project per platform
- each containing the bootstrap and view-specific code for that platform
Normally, you start development from the core project - and that's exactly what we'll do here.
To create the core, you can use the Visual Studio project template wizards, but here we'll instead build up a new project 'from empty'.
Using Visual Studio, create your new PCL using the File|New Project wizard.
Call it something like TipCalc.Core.csproj
When asked to choose platforms, select .NET Framework 4.5, Windows 8, Windows Phone Silverlight 8, Windows Phone 8.1, Xamarin.Android and Xamarin.iOS - this will ensure that the PCL is in Profile259. If Visual Studio stops you selecting these targets with the error 'The selection does not match any portable APIs' then use the workaround described here: http://danrigby.com/2014/04/10/windowsphone81-pcl-xamarin-fix/
Profile259 defines a small subset of .Net including:
- Microsoft.CSharp
- mscorelib
- System.Collections
- System.ComponentModel
- System.Core
- System.Diagnostics
- System
- System.Globalization
- System.IO
- System.Linq
- System.Net
- System.ObjectModel
- System.Reflection
- System.Resources.ResourceManager
- System.Runtime
- System.Security.Principal
- System.ServiceModel.Web
- System.Text.Encoding
- System.Text.RegularExpressions
- System.Threading
- System.Windows
- System.Xml
To see the full list of assemblies, look in C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETPortable\v4.5\Profile\Profile259
Importantly for us this Profile259 includes everything we need to build our Mvvm applications.
No-one really needs a Class1 :)
In the Package Manager Console, enter...
Install-Package MvvmCross.HotTuna.MvvmCrossLibraries
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 ICalculation
{
double TipAmount(double subTotal, int generosity);
}
}
Within this folder create an implementation of this interface:
namespace TipCalc.Core.Services
{
public class Calculation : ICalculation
{
public double TipAmount(double subTotal, int generosity)
{
return subTotal * ((double)generosity)/100.0;
}
}
}
This provides us with some simple business logic for our app
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 output displays of:
- the calculated tip to leave
To represent this user interface we need to build a 'model' for the user interface - which is, of course, a 'ViewModel'
Within MvvmCross, all ViewModels should inherit from MvxViewModel.
So now create a ViewModels folder in our project, and in this folder add a new TipViewModel class like:
using Cirrious.MvvmCross.ViewModels;
using TipCalc.Core.Services;
namespace TipCalc.Core.ViewModels
{
public class TipViewModel : MvxViewModel
{
private readonly ICalculation _calculation;
public TipViewModel(ICalculation calculation)
{
_calculation = calculation;
}
public override void Start()
{
_subTotal = 100;
_generosity = 10;
Recalculate();
base.Start();
}
private double _subTotal;
public double SubTotal
{
get { return _subTotal; }
set { _subTotal = value; RaisePropertyChanged(() => SubTotal); Recalculate(); }
}
private int _generosity;
public int Generosity
{
get { return _generosity; }
set { _generosity = value; RaisePropertyChanged(() => Generosity); Recalculate(); }
}
private double _tip;
public double Tip
{
get { return _tip; }
private set { _tip = value; RaisePropertyChanged(() => Tip);}
}
private void Recalculate()
{
Tip = _calculation.TipAmount(SubTotal, Generosity);
}
}
}
For many of you, 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 simple explanations:
-
the
TipViewModelis constructed with anICalculationserviceprivate readonly ICalculation _calculation; public TipViewModel(ICalculation calculation) { _calculation = calculation; } -
after construction, the
TipViewModelwill be started - during this it sets some initial values.public override void Start() { // set some start values SubTotal = 100.0; Generosity = 10; Recalculate(); base.Start(); } -
the view data held within the
TipViewModelis exposed through properties.-
Each of these properties is backed by a private member variable
-
Each of these properties has a get and a set
-
The set accessor for
Tipis marked private -
All of the set accessors call
RaisePropertyChangedto tell the baseMvxViewModelthat the data has changed -
The
SubTotalandGenerosityset accessors also callRecalculate()private double _subTotal; public double SubTotal { get { return _subTotal; } set { _subTotal = value; RaisePropertyChanged(() => SubTotal); Recalculate(); } } private int _generosity; public int Generosity { get { return _generosity; } set { _generosity = value; RaisePropertyChanged(() => Generosity); Recalculate(); } } private double _tip; public double Tip { get { return _tip; } private set { _tip = value; RaisePropertyChanged(() => Tip); } }
-
-
The
Recalculatemethod uses the_calculationservice to updateTipfrom the current values inSubTotalandGenerosityprivate void Recalculate() { Tip = _calculation.TipAmount(SubTotal, Generosity); }
With our Calculation service and 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 PCL core project.
- this class will inherits from the
MvxApplicationclass - this class is normally just called
App - this class is responsible for providing:
- registration of which interfaces and implementations the app uses
- registration of which
ViewModeltheAppwill show when it starts - control of how
ViewModels are located - although most applications normally just use the default implementation of this supplied by the baseMvxApplicationclass.
'Registration' here means creating an 'Inversion of Control' - IoC - record for an interface. This IoC record tells the MvvmCross framework what to do when anything asks for an instance of that interface.
For our Tip Calculation app:
-
we register the
Calculationclass to implement theICalculationserviceMvx.RegisterType<ICalculation, Calculation>();this line tells the MvvmCross framework that whenever any code requests an
ICalculationreference, then the framework should create a new instance ofCalculation. Note the single static classMvxwhich acts as a single place for both registering and resolving interfaces and their implementations. -
we want the app to start with the
TipViewModelvar appStart = new MvxAppStart<TipViewModel>(); Mvx.RegisterSingleton<IMvxAppStart>(appStart);this line tells the MvvmCross framework that whenever any code requests an
IMvxAppStartreference, then the framework should return that sameappStartinstance.
So here's what App.cs looks like:
using Cirrious.CrossCore;
using Cirrious.CrossCore.IoC;
using Cirrious.MvvmCross.ViewModels;
using TipCalc.Core.Services;
using TipCalc.Core.Viewmodels;
namespace TipCalc.Core
{
public class App : MvxApplication
{
public App()
{
Mvx.RegisterType<ICalculation,Calculation>();
Mvx.RegisterSingleton<IMvxAppStart>(new MvxAppStart<TipViewModel>());
}
}
}
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 single special object - a
singleton - This
singletonlives within theMvxstatic class. - The application startup code can use the
Mvx.Registermethods in order to specify what will implementinterfaces during the lifetime of the app. - After this has been done, then later in the life when any code needs an
interfaceimplementation, then it can request one using theMvx.Resolvemethods.
One common pattern that is seen is 'constructor injection':
- Our
TipViewModeluses this pattern. - It presents a constructor like:
public TipViewModel(ICalculation calculation). - When the app is running a part of the MvvmCross framework called the
ViewModelLocatoris used to find and createViewModels - when a
TipViewModelis needed, theViewModelLocatoruses a call toMvx.IocConstructto create one. - This
Mvx.IocConstructcall creates theTipViewModelusing theICalculationimplementation that it finds usingMvx.Resolve
This is obviously only a very brief introduction.
If you would like to know more, please see look up some of the excellent tutorials out there on the Internet - like http://joelabrahamsson.com/inversion-of-control-an-introduction-with-examples-in-net/
Just to recap the steps we've followed:
-
We created a new PCL project using Profile259
-
We added the MvvmCross libraries
-
We added a
ICalculationinterface and implementation pair -
We added a
TipViewModelwhich:
- inherited from
MvxViewModel - used
ICalculation - presented a number of public properties each of which called
RaisePropertyChanged
- We added an
Appwhich:
- inherited from
MvxApplication - registered the
ICalculation/Calculationpair - registered a special start object for
IMvxAppStart
These are the same steps that you need to go through for every new MvvmCross application.
Next we'll start looking at how to add a first UI to this MvvmCross application.