Two key ideas that are use in MvvmCross 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
The core idea of MvvmCross Service Location is that you can write classes and interfaces like:
With this pair written you could then register a
As a variation on this, you could register a lazy singleton. This is written
One final option, is that you can register the
If you create several implementations of an interface and register them all:
This can be useful for:
The default NuGet templates for MvvmCross contain a block of code in the core
Technical Note> the lazy singleton implementation here is quite technical - it ensures that if a class implements
The choice of name ending here -
As well as
For example, if we add a class like:
This "Constructor Injection" mechanism is used internally within MvvmCross when creating ViewModels.
If you declare a ViewModel like:
then MvvmCross will use the
This is important because:
Internally, the
This enables you to register implementations which depend on other interfaces like:
Further, this process is recursive - so if any of these returned objects requires another object - e.g. if your
Sometimes you need to use some platform specific functionality in your ViewModels. e.g. 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.:
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
For example, existing plugins include:
- the Service Locator pattern
- Inversion of Control
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.Service Location - Registration and Resolution
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";
}
}
Singleton Registration
With this pair written you could then register a
Foo
instance as a singleton which implements IFoo
using:If you did this, then any code can call:// every time someone needs an IFoo they will get the same one Mvx.RegisterSingleton
<
IFoo>(new Foo());
and every single call would return the same instance of Foovar foo = Mvx.Resolve
<
IFoo>
();
Lazy Singleton Registration
As a variation on this, you could register a lazy singleton. This is written
In this case:// 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());
- no
Foo
is created initially - the first time any code calls
Mvx.Resolve
<
IFoo>
then a new() Foo
will be created and returned - all subsequent calls will get the same instance that was created the first time
'Dynamic' Registration
One final option, is that you can register the
IFoo
and Foo
pair as:In this case, every call to// every time someone needs an IFoo they will get a new one Mvx.RegisterType
IFoo, Foo>();
<
Mvx.Resolve
<
IFoo>()
will create a new Foo
- every call will return a different Foo
.Last-registered wins
If you create several implementations of an interface and register them all:
Then each call replaces the previous registration - so when a client callsMvx.RegisterType
IFoo, Foo1>
<
(); Mvx.RegisterSingleton
IFoo>(new Foo2()); Mvx.RegisterType
<
IFoo, Foo3>();
<
Mvx.Resolve
<
IFoo>()
then the most recent registration will be returned.This can be useful for:
- overwriting default implementations
- replacing implementations depending on application state - e.g.
after a user has been authenticated then you could replace an empty
IUserInfo
implementation with a real one.
Bulk Registration by Convention
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:- find all classes in the Core assembly
- which are
creatable
- i.e.:- have a public constructor
- are not
abstract
- with names ending in Service
- which are
- find their interfaces
- register them as lazy singletons according to the interfaces they support
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:The choice is your'sMvx.RegisterSingleton
(new MyMixer()); Mvx.RegisterSingleton
(new MyCheese()); Mvx.RegisterType
(); Mvx.RegisterType
();
Constructor Injection
As well as
Mvx.Resolve
, 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: What happens during this call is:Mvx.IocConstruct
();
- MvvmCross:
- uses Reflection to find the constructor of
Bar
- looks at the parameters for that constructor and sees it needs an
IFoo
- uses
Mvx.Resolve
to get hold of the registered implementation for
()IFoo
- uses Reflection to call the constructor with the
IFoo
parameter
- uses Reflection to find the constructor of
Constructor Injection and ViewModels
This "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:
- It allows you to easily provide different
locationWatcher
classes on different platforms (on iPhone you can use a watcher that talk toCoreLocation
, on WindowsPhone you can use a watcher that talks toSystem.Device.Location
- It allows you to easily provide mock implementations in your unit tests
- It allows you to override default implementations - if you don't like the
Json.Net
implementation for Json, you can use aServiceStack.Text
implementation instead.
Constructor Injection and Chaining
Internally, the
Mvx.Resolve
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:Then when a client callsMvx.RegisterType
<
ITaxCalculator, TaxCalculator>();
Mvx.Resolve
<
ITaxCalculator>()
then what will happen is that 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.How do I use IoC when I need different implementations on different platforms?
Sometimes you need to use some platform specific functionality in your ViewModels. e.g. 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:
- Declare an interface in your core library, but then provide and register an implementation in each of your UI projects.
- Use or create a plugin
1. PCL-Interface with Platform-Specific Implementation
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
- e.g. 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
withWith this done, thenprotected override void InitializeFirstChance() { Mvx.RegisterSingleton
IScreenSize>
<
(new WindowsPhoneScreenSize()); base.InitializeFirstChance(); }
MyViewModel
will get provided with the correct platform specific implementation of IScreenSize
on each platform.2. Use or create a plugin
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:
- a File plugin which provides access to
System.IO
type methods for manipulating files - a Location plugin which provides access to GeoLocation information
- a Messenger plugin which provides access to a Messenger/Event Aggregator
- a PictureChooser plugin which provides access to the camera and to the media library
- a ResourceLoader plugin which provides a way to access resource files packaged within the .apk, .app or .ipa for the application
- a SQLite plugin which provides access to
SQLite-net
on all platforms.
Plugin Use
If you want to see how these plugins can be used in your applications, then:
- the N+1 videos provide a good starting point - see http://mvvmcross.wordpress.com/ - especially :
- N=8 - Location http://slodge.blogspot.co.uk/2013/05/n8-location-location-location-n1-days.html
- N=9 - Messenger http://slodge.blogspot.co.uk/2013/05/n9-getting-message-n1-days-of-mvvmcross.html
- N=10 - SQLite http://slodge.blogspot.co.uk/2013/05/n10-sqlite-persistent-data-storage-n1.html
- N=12 -> N=17 - the Collect-A-Bull app http://slodge.blogspot.co.uk/2013/05/n12-collect-bull-full-app-part-1-n1.html
Plugin Authoring
Writing plugins is easy to do, but can feel a bit daunting at first.
The key steps are:
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:
If you don't want to use this in your code, then don't.
Simply remove the
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:
There is an example Property Injection implementation for IoC provided.
This can be initialised using a Setup override of:
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
Note: this article is part of the v3 attempt to get MvvmCross documentation produced. If you'd like to help with this work, please say hello on https://github.com/slodge/MvvmCross/issues/252 - we need you!
The key steps are:
- Create the main PCL Assembly for the plugin - this should include:
- the interfaces your plugin will register
- any shared portable code (which may include implementations of one or more of the interfaces)
- a special
PluginLoader
class which MvvmCross will use to start the plugin
- Optionally create platform specific assemblies which:
- is named the same as the main assembly but with a platform specific extension (.Droid, .WindowsPhone, etc(
- contains
- any platform specific interface implementations
- a special
Plugin
class which MvvmCross will use to start this platform-specific extension
- Optionally 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:
- there's a presentation on this at https://speakerdeck.com/cirrious/plugins-in-mvvmcross
- there's a sample which creates a
Vibrate
plugin at https://github.com/slodge/MvvmCross-Tutorials/tree/master/GoodVibrations
What if...
What if... I don't want to use Service Location or IoC
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();
}
}
What if... I want to use a different Service Location or IoC mechanism
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:
- write some kind of
Adapter
layer to provide their service location code as anIMvxIoCProvider
- override
CreateIocProvider
in yourSetup
class to provide this alternativeIMvxIoCProvider
implementation.
What if... I want to use Property Injection as an IoC mechanism
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();
}
What if... I want advanced IoC features like child containers
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
Note: this article is part of the v3 attempt to get MvvmCross documentation produced. If you'd like to help with this work, please say hello on https://github.com/slodge/MvvmCross/issues/252 - we need you!
Hi!
ReplyDeleteGreat explanation indeed! Looks pretty like Win32 API development to fit all the possible situations. :)
I've got a question related to IoC performance though... Posted it here:
http://stackoverflow.com/questions/16987652/mvvmcross-ioc-and-servicelocation-performance
Thanks. I've replied... but to be honest it feels like you were asking about some non-specific future imaginary situation and it's very hard to answer a question about some unknown problem that might one day hit one app... It's a personal thing - I just feel I've got enough real problems already, without worrying about ones which might come up in the future. If and when one of my apps has a performance problem, then hopefully I'll have just made sure that I know I have put myself in a position to optimise it as and when I need to. Stuart
DeleteThank you for your long answer on stackoverflow, but as for me you've got my question not really in a correct way (unless, I was not fully clear).
DeleteThe question was about "how do you consider mvvmcross performance?"
You could have answered just like "Not really. We don't have any deep considerations on that as the first thing we are care about is 'developers convenience'". :)
I'm afraid "how do you consider mvvmcross performance?" is a very open and ambiguous sentence.
DeleteMy answer included "we're aware that parts of the platform do add a level of overhead - but we've worked hard to make sure it isn't a large one" which tells you that we do care deeply and think upfront to make sure that we are not writing poorly-performing code.
My answer included "we've worked hard to make sure almost everything can be overridden in the platform so that you can optimise later if you need to - if your app wants to hand-tune IoC registration it can - your app doesn't have to use reflection." which tells you that developers can override any behaviour if they consider it to perform too poorly.
We do care about our library and we do write the code in a way that gives the app-developers the choice about how to develop. Our general philosophy is that we don't worry about lots of micro-optimisations up-front, but that we do provide the options so that the app-developers can write very good code to start with can can then optimise later when they need to.
This approach might mean that bad developers are more likely to make rubbish apps, but it also means that good developers are more likely to make good apps. I don't really care about the bad developers - they will make bad apps anyway.
Sure. I absolutely agree with this: "Our general philosophy is that we don't worry about lots of micro-optimisations up-front, but that we do provide the options so that the app-developers can write very good code to start with can can then optimise later when they need to". I am not trying to doubt that you are really care about good project...
DeleteHowever, if you check ServiceStack docs (I guess, you were even mentioning this framework in some of your projects explanations): https://github.com/ServiceStack/ServiceStack/wiki/The-IoC-container, then you will see the following: "ServiceStack uses a slightly modified version of Funq - which was adopted because of its excellent performance and memory characteristics." which considers that there are more "excelent" and sometimes less ones (and not just because of "good" or "bad" developers, but just because different strategies used) frameworks in performance meaning. So this is why I was asking about that.
And as the discussion continues, it shifts strictly to the idea "... whether MvvmCross performance effective?", which I was not even going to touch. I was just talking about IoC on app startup/initialization, as there is still no any "standard" in its implementation, and different developers write different code (see above the link with Funq).
If you want to use the Funq style of registration - you can - it's available as `void RegisterSingleton(Func theConstructor)`
DeleteSorry. I don't have time to continue this conversation about a non-problem.
Good luck with your apps.
Stuart
That was not a talk about Funq. But seems that there are some "tricks" in our understanding each other.
DeleteAnyway, I've got an answer for myself.
Thanks again!
Boleslav
Big applause for Stuart Lodge!
ReplyDeleteYou're building something that will make the future of app development environment for crossplatform .Net...chapeau!
Just a suggestion on IoC implementation. If application is rather complex, doing constructor injection could be expensive, especially if there are many constructor parameters to be resolved by Ioc and some of there are interdependent. I'd recommend you to implement a typed factory pattern, so once its resolved, only needed members are delivered and exactly when needed. (LinkL http://docs.castleproject.org/Default.aspx?Page=Typed-Factory-Facility-interface-based-factories&NS=Windsor&AspxAutoDetectCookieSupport=1 )
ReplyDelete