I tried to understand the instantiation of ViewModels and Service classes and wrote it down for others. Please correct/add-on where needed.
The instantiation of ViewModels and Services is not done in the most-common way. It's done using reflection.
In the TipCalc you have:
public class FirstViewModel : MvxViewModel
{
private readonly ICalculationService _calculationService;
public FirstViewModel(ICalculationService calculationService)
{
_calculationService = calculationService;
}
...
}
and
public class App : Cirrious.MvvmCross.ViewModels.MvxApplication
{
public override void Initialize()
{
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
...
}
}
During Initialize() Interfaces and classes designed as Service (name ends with Service) are paired up using reflection and the interface names and class names (IPersonService and PersonService). This is later used to reverse lookup the instance of the class (the lookup table contains lazy references to singleton instances of the service classes. Services are created when null.
public FirstViewModel(ICalculationService calculationService) references an instance of CalculationService. This is done by using the lookup table earlier created.
Instantiation of ViewModels is done through the Mvx framework. When the MvxFramework is 'asked' for an instantiated ViewModel, it will reflect the ViewModel and determine what constructors there are on that class. If there's a parameterless constructor, then that will be used. If there is a constructor with a parameter and the parameter is the interface of a service class, then the (singleton) instance of that service will be used as the parameter.
Services are instantiated in a similar way; their constructors reflected and parameters instantiated (singleton). And so on.
The ideas that are in use here are:
There are lots of articles and introductions available on this - some good starting places are Martin Fowler's introduction and Joel Abrahamsson's IoC introduction. I've also made some animated slides as a simple demonstration.
Specifically within MvvmCross, we provide a single static class Mvx
which acts as a single place for both registering and resolving interfaces and their implementations.
The core idea of MvvmCross Service Location is that you can write classes and interfaces like:
public interface IFoo
{
string Request();
}
public class Foo : IFoo
{
public string Request()
{
return "Hello World";
}
}
With this pair written you could then register a Foo
instance as a singleton which implements IFoo
using:
// every time someone needs an IFoo they will get the same one
Mvx.RegisterSingleton<IFoo>(new Foo());
If you did this, then any code can call:
var foo = Mvx.Resolve<IFoo>();
and every single call would return the same instance of Foo.
As a variation on this, you could register a lazy singleton. This is written
// every time someone needs an IFoo they will get the same one
// but we don't create it until someone asks for it
Mvx.RegisterSingleton<IFoo>(() => new Foo());
In this case:
Foo
is created initiallyMvx.Resolve<IFoo>()
then a new Foo
will be created and returnedOne final option, is that you can register the IFoo
and Foo
pair as:
// every time someone needs an IFoo they will get a new one
Mvx.RegisterType<IFoo, Foo>();
In this case, every call to Mvx.Resolve<IFoo>()
will create a new Foo
- every call will return a different Foo
.
If you create several implementations of an interface and register them all:
Mvx.RegisterType<IFoo, Foo1>();
Mvx.RegisterSingleton<IFoo>(new Foo2());
Mvx.RegisterType<IFoo, Foo3>();
Then each call replaces the previous registration - so when a client calls Mvx.Resolve<IFoo>()
then the most recent registration will be returned.
This can be useful for:
IUserInfo
implementation with a real one.The default NuGet templates for MvvmCross contain a block of code in the core App.cs
like:
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
This code uses Reflection to:
creatable
- i.e.:
abstract
Technical Note: the lazy singleton implementation here is quite technical - it ensures that if a class implements IOne
and ITwo
then the same instance will be returned when resolving both IOne
and ITwo
.
The choice of name ending here - Service
- and the choice to use Lazy singletons are only personal conventions. If you prefer to use other names or other lifetimes for your objects you can replace this code with a different call or with multiple calls like:
CreatableTypes()
.EndingWith("SingleFeed")
.AsInterfaces()
.RegisterAsLazySingleton();
CreatableTypes()
.EndingWith("Generator")
.AsInterfaces()
.RegisterAsDynamic();
CreatableTypes()
.EndingWith("QuickSand")
.AsInterfaces()
.RegisterAsSingleton();
There you can also use additional Linq
helper methods to help further define your registrations if you want to - e.g. Inherits
, Except
. WithAttribute
, Containing
, InNamespace
... e.g.
CreatableTypes()
.StartingWith("JDI")
.InNamespace("MyApp.Core.HyperSpace")
.WithAttribute(typeof(MySpecialAttribute))
.AsInterfaces()
.RegisterAsSingleton();
And you can also, of course, use the same type of registration logic on assemblies other than Core - e.g.:
typeof(Reusable.Helpers.MyHelper).Assembly.CreatableTypes()
.EndingWith("Helper")
.AsInterfaces()
.RegisterAsDynamic();
Alternatively, if you prefer not to use this Reflection based registration, then you can instead just manually register your implementations:
Mvx.RegisterSingleton<IMixer>(new MyMixer());
Mvx.RegisterSingleton<ICheese>(new MyCheese());
Mvx.RegisterType<IBeer, Beer>();
Mvx.RegisterType<IWine, Wine>();
The choice is yours.
As well as Mvx.Resolve<T>
, the Mvx
static class provides a reflection-based mechanism to automatically resolve parameters during object construction.
For example, if we add a class like:
public class Bar
{
public Bar(IFoo foo)
{
// do stuff
}
}
Then you can create this object using:
Mvx.IocConstruct<Bar>();
What happens during this call is:
Bar
IFoo
Mvx.Resolve<IFoo>()
to get hold of the registered implementation for IFoo
IFoo
parameterThis "Constructor Injection" mechanism is used internally within MvvmCross when creating ViewModels.
If you declare a ViewModel like:
public class MyViewModel : MvxViewModel
{
public MyViewModel(IMvxJsonConverter jsonConverter, IMvxGeoLocationWatcher locationWatcher)
{
// ....
}
}
then MvvmCross will use the Mvx
static class to resolve objects for jsonConverter
and locationWatcher
when a MyViewModel
is created.
This is important because:
locationWatcher
classes on different platforms (on iPhone you can use a watcher that talk to CoreLocation
, on Windows Phone you can use a watcher that talks to System.Device.Location
Json.Net
implementation for Json, you can use a ServiceStack.Text
implementation instead.Internally, the Mvx.Resolve<T>
mechanism uses constructor injection when new objects are needed.
This enables you to register implementations which depend on other interfaces like:
public interface ITaxCalculator
{
double TaxDueFor(int customerId)
}
public class TaxCalculator
{
public TaxCalculator(ICustomerRepository customerRepository, IForeignExchange foreignExchange, ITaxRuleList taxRuleList)
{
// code...
}
// code...
}
If you then register this calculator as:
Mvx.RegisterType<ITaxCalculator, TaxCalculator>();
Then when a client calls Mvx.Resolve<ITaxCalculator>()
then MvvmCross will create a new TaxCalculator
instance, resolving all of ICustomerRepository
, IForeignExchange
and ITaxRuleList
during the operation.
Further, this process is recursive - so if any of these returned objects requires another object - e.g. if your IForeignExchange
implementation requires a IChargeCommission
object - then MvvmCross will provide Resolve for you as well.
Sometimes you need to use some platform specific functionality in your ViewModels. For example, you might want to get the current screen dimensions in your ViewModel - but there's no existing portable .Net call to do this.
When you want to include functionality like this, then there are two main choices:
In your core project, you can declare an interface and you can use that interface in your classes there - e.g.:
public interface IScreenSize
{
double Height { get; }
double Width { get; }
}
public class MyViewModel : MvxViewModel
{
private readonly IScreenSize _screenSize;
public MyViewModel(IScreenSize screenSize)
{
_screenSize = screenSize;
}
public double Ratio
{
get { return (_screenSize.Width / _screenSize.Height); }
}
}
In each UI project, you can then declare the platform-specific implementation for IScreenSize
. A trivial example is:
public class WindowsPhoneScreenSize : IScreenSize
{
public double Height { get { return 800.0; } }
public double Width { get { return 480.0; } }
}
You can then register these implementations in each of the platform-specific Setup files - e.g. you could override MvxSetup.InitializeFirstChance
with
protected override void InitializeFirstChance()
{
Mvx.RegisterSingleton<IScreenSize>(new WindowsPhoneScreenSize());
base.InitializeFirstChance();
}
With this done, then MyViewModel
will get provided with the correct platform-specific implementation of IScreenSize
on each platform.
A Plugin is an MvvmCross pattern for combining a PCL assembly, plus optionally some platform-specific assemblies in order to package up some functionality.
This plugin layer is simply a pattern - some simple conventions - for naming related Assemblies, for including small PluginLoader
and Plugin
helper classes, and for using IoC. Through this pattern it allows functionality to be easily included, reused and tested across platforms and across applications.
For example, existing plugins include:
System.IO
type methods for manipulating filesSQLite-net
on all platforms.If you want to see how these plugins can be used in your applications, then:
Writing plugins is easy to do, but can feel a bit daunting at first.
The key steps are:
Create the main PCL Assembly for the plugin - this should include:
PluginLoader
class which MvvmCross will use to start the pluginOptionally create platform-specific assemblies which:
Plugin
class which MvvmCross will use to start this platform-specific extensionOptionally provide extras like documentation and NuGet packaging which will make the plugin easier to reuse.
I'm not going to go into any more detail on writing plugins here.
If you'd like to see more about writing your own plugin, then:
Vibrate
plugin at https://github.com/slodge/MvvmCross-Tutorials/tree/master/GoodVibrations
If you don't want to use this in your code, then don't.
Simply remove the CreatableTypes()...
code from App.cs and then use 'normal code' in your ViewModels - e.g.:
public class MyViewModel : MvxViewModel
{
private readonly ITaxService _taxService;
public MyViewModel()
{
_taxService = new TaxService();
}
}
There are lots of excellent libraries out there including AutoFac, Funq, MEF, OpenNetCF, TinyIoC and many, many more!
If you want to replace the MvvmCross implementation, then you'll need to:
Adapter
layer to provide their service location code as an IMvxIoCProvider
CreateIocProvider
in your Setup
class to provide this alternative IMvxIoCProvider
implementation.Alternatively, you may be able to organise a hybrid situation - where two IoC/ServiceLocation systems exist side-by-side.
There is an example Property Injection implementation for IoC provided.
This can be initialised using a Setup override of:
protected override IMvxIoCProvider CreateIocProvider()
{
return MvxPropertyInjectingIoCContainer.Initialise();
}
The IoC container in MvvmCross is designed to be quite lightweight and is targeted at a level of functionality required in the mobile applications I have built.
If you need more advanced/complex functionality, then you may need to use a different provider or a different approach - some suggestions for this are discussed in: Child containers in MvvmCross IoC
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With