A good application should have a good framework on which the developers will rely.
In my opinion, that means that the application should have a good UI composition framework and a solid IoC container.
In this post I will give an example of two of such libraries: Caliburn Micro for the UI composition and AutoFac for IoC.
It will contain only the Android platform, but it can be easily migrated to iOS and UWP.
TL;DR version
If all you want is the code and you can handle it yourself,
Feel free to check the example on my GitHub.
Introduction
This article is basically a step-by-step tutorial on creating a Xamarin Forms application which is composing the UI with Caliburn Micro, while the IoC is handled by AutoFac.
The final result will be an application that is based on Caliburn Micro for UI composition and on AutoFac for all the DI needs.
I will discuss some of the frameworks capabilities, but to get the full picture, you’ll have to keep exploring yourself.
So, let’s get started!
Caliburn Micro
UI composition can be tricky sometimes. You want to decouple everything as much as possible in your application, but still be able to combine view models and views. You want to add child view models with their corresponding views, you want to handle event from the view in your view model and more.
Caliburn Micro solves all of those issues, while maintaining relatively easy syntax. For a very quick start and understanding of the capabilities of the framework, check their Cheat Sheet.
Starting with Caliburn Micro in Xamarin Forms is pretty easy and the official documentation on their website gives a great example and a step-by-step explanation.
First things First – NuGet package
To be able to use Caliburn Micro, we need to add the NuGet packages.
Add the Caliburn.Micro.Xamarin.Forms package to all the projects. (In our case, the PCL project and the Android project)
Initial PCL project setup
We will need to handle both, the PCL and the platform specific projects for Caliburn Micro to work.
Without any particular reason, let’s begin our implementation from the PCL project.
Solution Tree Changes
Caliburn Micro uses a naming convention to connect the views and view models. The convention is very straight forward and easy to understand:
- All Views should be under the Views folder and end with the suffix View.
- All ViewModels should be under the ViewModels folder and end with the suffix ViewModel.
- A View will be connected to the corresponding ViewModel when they both have the same name if you omit the suffix.
Example of a pair: HomeView will be connected to HomeViewModel.
So for starters, let’s do the following:
- Remove the MainPage.xaml.
- Create the aforementioned folders.
- Add HomeView and HomeViewModel to the corresponding folders.
Your initial solution tree should look like this:
App.cs
In addition to setting up the solution tree, we need to make sure that our first View/ViewModel pair is initialized and shown via the Caliburn Micro mechanism.
This requires us to make some changes in the App.xaml.cs file.
For simplicity purposes, I’m going to step aside from the official demo.
To wire up everything correctly, we need only two things:
- Make the App class derive from FormsApplication. (Don’t forget the xaml part if you have it)
- Call the DisplayRootFor method in the constructor.
Your final App class should look like this:
public partial class App : FormsApplication { public App() { Initialize(); DisplayRootViewFor<HomeViewModel>(); } }
For now, this is all we are going to do in the PCL project. We will come back to it later.
Platform Specific Initialization (Android)
Now we are going to initialize the platform specific project. This step is very similar in every platform, so for simplicity, I’m going to use just the Android platform.
But, if you are interested in other platforms, I’ll refer you again to the official documentation of Caliburn Micro.
Following the explanation in the documentation, we need to do 2 things only:
- Add new class Application.
- Replace LoadApplication in MainActivity.
It doesn’t really explain past it, so we will have to go to the demo code and see for ourselves.
MainActivity.cs
There are almost no changes in this class, the only change is to initialize the App from the IoC, which is the container that is exposed by Caliburn Micro.
So the MainActivity should look like this:
[Activity(Label = "CaliburnAutoFac", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity { protected override void OnCreate(Bundle bundle) { TabLayoutResource = Resource.Layout.Tabbar; ToolbarResource = Resource.Layout.Toolbar; base.OnCreate(bundle); global::Xamarin.Forms.Forms.Init(this, bundle); LoadApplication(IoC.Get<App>()); } }
You can alter it in anyway you see fit, but the last line (the highlighted one) should stay the same if you want the IoC capabilities in your app.
Application.cs
This class will be the Bootstrapper in our application. I’ve copied the class from the demo code:
[Application] public class Application : CaliburnApplication { private SimpleContainer _container; public Application(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { } public override void OnCreate() { base.OnCreate(); Initialize(); } protected override void Configure() { _container = new SimpleContainer(); _container.Instance(_container); _container.Singleton<App>(); } protected override IEnumerable<Assembly> SelectAssemblies() { return new[] { GetType().Assembly, typeof (HomeViewModel).Assembly }; } protected override void BuildUp(object instance) { _container.BuildUp(instance); } protected override IEnumerable<object> GetAllInstances(Type service) { return _container.GetAllInstances(service); } protected override object GetInstance(Type service, string key) { return _container.GetInstance(service, key); } }
And this will conclude our initialization of the Caliburn Micro part.
Basically saying, we are now able to use all the Caliburn Micro capabilities in our app.
Now, all we need to do is to integrate it with AutoFac.
AutoFac
An app with a good foundation is an app with a good IoC container. Of course, if your app is very simple app, you could simply use the built in IoC container that Caliburn Micro provides.
However, if you are planning a bit more complex scenarios, I suggest you kick it up a notch and use something more flexible with more features.
There are lots of options out there, and each IoC container has its own pros and cons, so if you feel like AutoFac is not suitable for you, you could simply initialize any other IoC container of your choice.
First things First – NuGet package
As in Caliburn Micro, we will need a NuGet package here.
So now will be a good time to add the AutoFac Nuget package to both of our projects.
Initialize PCL module
AufoFac supports modules initialization. Basically, this means that you can give each logical piece in your app to register its own implementations in the container.
There are lots of advantages to this approach, so I’m usually happy to use it.
So, to create our module in the PCL project, we will create a new class there and give it a very creative name of… well, Module:
public class Module : Autofac.Module { protected override void Load(ContainerBuilder builder) { base.Load(builder); builder.RegisterType<App>().AsSelf().SingleInstance(); // Registers only classes that match the ViewModel pattern. // Registers them as self and as implemented interfaces. GetType().Assembly.GetTypes() .Where(type => type.IsClass) .Where(type => type.Name.EndsWith("ViewModel")) .ForEach(viewModelType => builder.RegisterType(viewModelType).AsSelf().AsImplementedInterfaces()); } }
To create a module in AutoFac, we will have to derive from the class Module that is declared in the AutoFac dll.
To use the module, all we need to do is to override the Load method and register our types to the container builder.
As you can see, in our PCL project, we register the App class and all the view models.
That is all we are going to need for our little demo. In case you have more things to register, this is the place. If you have several logical parts, you can even create several modules. It is really up to you.
Platform Specific Initialization (Android)
After we initialized our module, we need to integrate AutoFac in our app. Luckily for us, we already have an IoC container initialized in our Application class. All we need to do now is to replace it with our own.
Namespace
So for starters, let’s add the autofac namespace:
using Autofac;
This will allow us to use all the AutoFac classes we will need here.
The Container Member
Next, let’s change the type of the container private member:
private IContainer _container;
Configure
Now, we need to change the way we configure our container, so let’s change the Configure method.
What we want to do here is to create a container builder, for:
- Passing it inside the modules
- Building the container itself
protected override void Configure() { base.Configure(); ContainerBuilder builder = new ContainerBuilder(); builder.RegisterModule<CaliburnAutoFac.Module>(); _container = builder.Build(); }
Note, that since we are registering the App in the PCL module, we don’t need to register it here anymore.
Select Assemblies
The next change is actually optional, but I think it is important.
IMHO, the bootstrapper shouldn’t know about any implementation details, therefore loading an assembly by a specific view model type (HomeViewModel in this case) is not right.
My suggestion, since we are initializing an AutoFac project, we may use the Module in the assembly we want to load.
The method with the change should look something like this:
protected override IEnumerable<Assembly> SelectAssemblies() { return new[] { GetType().Assembly, typeof(CaliburnAutoFac.Module).Assembly }; }
Build Up
The BuildUp method is used to inject properties to an isntance, based on our IoC container.
The method in AutoFac container is different, so we will have to change this method as well:
protected override void BuildUp(object instance) { _container.InjectProperties(instance); }
Although we are not using this capability in our demo, it is important we initialize the container well for any other scenario that might come up in the future.
Get all Instances & Get Instance
Now comes the money time. Here is the actual request from the container to get an implementation of a type.
As in the BuildUp method, the methods are a bit different, so we will have to change them:
protected override IEnumerable<object> GetAllInstances(Type service) { var enumerableOfServiceType = typeof(IEnumerable<>).MakeGenericType(service); return (IEnumerable<object>)_container.Resolve(enumerableOfServiceType); } protected override object GetInstance(Type service, string key) { return key == null ? _container.Resolve(service) : _container.ResolveKeyed(key, service); }
We are done
If you’ve done everything right, your Application class should look something like this:
[Application] public class Application : CaliburnApplication { private IContainer _container; public Application(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { } public override void OnCreate() { base.OnCreate(); Initialize(); } protected override void Configure() { base.Configure(); ContainerBuilder builder = new ContainerBuilder(); builder.RegisterModule<CaliburnAutoFac.Module>(); _container = builder.Build(); } protected override IEnumerable<Assembly> SelectAssemblies() { return new[] { GetType().Assembly, typeof(CaliburnAutoFac.Module).Assembly }; } protected override void BuildUp(object instance) { _container.InjectProperties(instance); } protected override IEnumerable<object> GetAllInstances(Type service) { var enumerableOfServiceType = typeof(IEnumerable<>).MakeGenericType(service); return (IEnumerable<object>)_container.Resolve(enumerableOfServiceType); } protected override object GetInstance(Type service, string key) { return key == null ? _container.Resolve(service) : _container.ResolveKeyed(key, service); } }
Hooray! We now have a fully functional mobile app that is based on Caliburn Micro and AutoFac!
Even More
If you want a concrete example of how to use Caliburn Micro and AutoFac,
You can follow to my GitHub and download the example.
It contains:
- Initialization of the HomeViewModel as a conductor.
- Dependency Injection of all view models that implement a specific interface.
- TabbedPage that is auto generated based on the injected view models.
It gives an error: System.ArgumentException: An item with the same key has already been added
Hmm… Very odd.
Can you please share more details?
Did you make changes to the code?
At which line you experience the exception?