Skip to content

Inject Xamarin Forms view-models via IOC container

I started a new little project in my spare time which is a native mobile app for iOS and Android. Because the goal is to have as much shared code between iOS and Android as possible and still have native apps experience I choose the Xamarin Forms stack. Its also an excellent opportunity to refresh my XAML know-how.

The problem

I quickly got a screen working but missed a full fledged IOC container with proper constructor injection. There are libraries and article but most of them are outdated or even discontinued.

The Dependency Injection documentation got me half way started. It contains mostly general information about dependency injection, inversion of control and so on. But it also has a sample implementation in this GitHub repo which was very helpful.

For Xamarin Forms the recommended pattern for non-trivial apps is the MVVM pattern. Means we do a view using XAML (or C#) and connect it with a view-model using data-binding. The view-model contains all the UI logic and connect to other service classes – for example to retrieve data from a server.

What I like to do is having view-models instantiated automatically using a IOC container so all the constructor injection takes place. As the view-models are the central part of MVVM apps we get IoC for wast majority of the app just with this alone.

How to integrate an IOC container so the views get the view models injected and all dependencies are resolved as part of the constructor injection?

Solution

As I like SimpleInjector I choose this one but you can do basically the same with every other container that runs on .net Core and the Xamarin stack.

The basic concept as described in Microsoft’s documentation goes like this (took me a while to get this):

  • Create a ViewModelLocator which creates an attached property for the views called AutoWireViewModel (boolean).

  • When the AutoWireViewModel is set the ViewModelLocator looks for a matching view model class using a naming convention.

  • The view model gets resolved using SimpleInjector IoC container.

  • The view model instance then gets set as the views BindingContext.

  • The normal binding kicks in because BindingContext was set.

The View Model Locator

Here is my ViewModelLocator similar to the one from the Microsoft documentation. You see that the ViewModelLocator is a static class and hold a singleton reference to the SimpleInjector container. It also defines the AutoWireViewModel property. In the change handler OnAutoWireViewModelChanged you find the resolving of the view model and its assignment to BindingContext of the view. Change this logic to your needs.

In the static constructor I instantiate the SimpleInjector container called Container. I then call my helper IoCRegistrations.RegisterDependencies to do all the registrations.

    public static class ViewModelLocator
    {
        private static readonly Container Container;

        static ViewModelLocator()
        {
            Container = new Container();
            IoCRegistrations.RegisterDependencies(Container);
        }

        public static readonly BindableProperty AutoWireViewModelProperty =
            BindableProperty.CreateAttached(
                "AutoWireViewModel",
                typeof(bool),
                typeof(ViewModelLocator),
                default(bool),
                propertyChanged: OnAutoWireViewModelChanged);

        public static bool GetAutoWireViewModel(BindableObject bindable)
            => (bool)bindable.GetValue(AutoWireViewModelProperty);

        public static void SetAutoWireViewModel(BindableObject bindable, bool value)
            => bindable.SetValue(AutoWireViewModelProperty, value);

        public static void RegisterSingleton<TInterface, T>() where TInterface : class where T : class, TInterface
            => Container.RegisterSingleton<TInterface, T>();

        public static T Resolve<T>() where T : class
            => Container.GetInstance<T>();

        private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var view = bindable as Element;

            var viewType = view?.GetType();
            if (viewType?.FullName == null)
            {
                return;
            }

            var viewName = viewType.FullName.Replace(".Views.", ".ViewModels.");
            var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
            var viewModelName = string.Format(CultureInfo.InvariantCulture, "{0}ViewModel, {1}", viewName, viewAssemblyName);

            var viewModelType = Type.GetType(viewModelName);
            if (viewModelType == null)
            {
                return;
            }

            var viewModel = Container.GetInstance(viewModelType);
            view.BindingContext = viewModel;
        }
    }

This was the most tricky part.

Just for the record here the helper for the IoC registrations:

    public static class IoCRegistrations
    {
        public static void RegisterDependencies(Container container)
        {
            container.RegisterSingleton<IDataStore<Item>, MockDataStore>();
            container.RegisterSingleton<ICountryDataStore, CountryDataStoreMock>();
            ...
        }
    }

Do whatever you need to do to get your stuff registered with the container.

Usage of the View Model Locator in the View

All that is needed now is that the view sets the AutoWireViewModel to true. Check out the line base:ViewModelLocator.AutoWireViewModel="true".

<?xml version="1.0" encoding="utf-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:views="clr-namespace:DiveSpots.Views"
    xmlns:base="clr-namespace:DiveSpots.ViewModels.Base;assembly=DiveSpots"
    base:ViewModelLocator.AutoWireViewModel="true"
    x:Class="DiveSpots.Views.WatersOverviewPage"
    Visual="Material"
    Title="{Binding Title}">

    <StackLayout>
        ...

Naming convention

I use the naming convention that the views are in the namespace DiveSpots.Core.Views and their name ends with ‘View’ while the view models are in DiveSpots.Core.ViewModels and there name is equal to the view but end with ‘ViewModel’. This can be changed or enhanced in OnAutoWireViewModelChanged.

SimpleInjector

To use simple injector with Xamarin Forms the only thing I had to do was to add the SimpleInjector NuGet package to the project.

Hope this helps.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: