Freigeben über


Dependency Injection With Xamarin Forms

Editor's note: The following post was written by Visual Studios and Development Technologies MVP Houssem Dellai  as part of our Technical Tuesday series.

Inversion of Control (IOC) and Dependency Injection (DI) are common patterns in software development, which help to create loosely coupled dependencies. This article will show you how to setup IOC and DI in Xamarin Forms. The goal is to create a low decoupled application that is easy to maintain, unit test and extend. This architecture also enables one to access platform specific code for Android or iOS from a shared PCL project.

It’s common to use DI container frameworks to simplify object creation, especially for hierarchical object structures and dependencies. These frameworks are available as nuget packages like Ninject, Autofac, TinyIoc, StructureMap. Here we’ll be using Unity Application Block, which is an open source lightweight DI container developed by Microsoft.

Setup Xamarin Forms MVVM project

We’ll start by creating an Xamarin Forms app that uses a PCL project for sharing code.

Create the Product model

Then we’ll add folders for the MVVM structure: Models, ViewModels and Views. In the Models folder, we’ll create a Product class as follows:

 public class Product
{
    public string Name { get; set; }
    public double Price { get; set; }
    public override string ToString()
    {
        return $"{Name} : {Price} USD";
    }
}

Create the ProductsService

The IProductsService interface defines the method for getting the list of products. It is implemented by the ProductsService class. Typically, products are retrieved from a web service call. To keep it simple, we’re returning a static list.

 public interface IProductsService
{
    IEnumerable Getproducts();
}
public class ProductsService : IProductsService
{
    public IEnumerable Getproducts()
    {
        return new List
        {
            new Product { Name = "Surface Laptop", Price = 1500 },
            new Product { Name = "XBox One", Price = 400 },
        };
    }
}

Create the ViewModel

The ProductsViewModel is the intermediate between the user interface and the business logic. It retrieves the data from the service and exposes it to the Xaml page through Products property.

 public class ProductsViewModel
{
    private readonly ProductsService _productsService;
    public IEnumerable Products { get; set; }
    public ProductsViewModel()
    {
        _productsService = new ProductsService();
        DownloadProducts();
    }
    public void DownloadProducts()
    {
        Products = _productsService.Getproducts();
    }
}

Create the View

The ProductsView will be bound to the ProductsViewModel in order to access its properties and commands. It will take the Products and show it inside a ListView.

 <?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="https://xamarin.com/schemas/2014/forms"

xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
           x:Class="IocAndDiXamarinForms.Views.ProductsPage"
           xmlns:viewModels=
              "clr-namespace:IocAndDiXamarinForms.ViewModels">
    ContentPage.BindingContext>
       <viewModels:ProductsViewModel />
    </ContentPage.BindingContext>
    <ContentPage.Content>
       <ListView ItemsSource="{Binding Products}" />
    </ContentPage.Content>
</ContentPage>

Note : The ListView will display both the Name and Price for each property. This is because we’ve overriddenToString() in the Product class. The ListView calls to the ToString() method by default, if there’s no explicit binding to its properties.

Tip: The above code uses the MVVM design pattern to isolate the view’s code from the business logic code. This pattern is described in more detail here.

Setup IOC/DI

ProductsViewModel creates a new instance of the ProductsService object. This violates the OOP principle of single responsibility - however dependency injection solves this problem. Instead of creating the object here, someone else will do it and pass it to whoever asks for it. Then ProductsViewModel won’t create an instance, but it will ask for it and get it through its constructor.

We want to go a step further here and think about unit tests, and where we need to mock the ProductsService object. That is why we created an interface. With it, we can pass any type that implements that interface, like the actual ProductService type or a MockProductService type with a different implementation dedicated for unit tests. This means we need to change our code to deal with interfaces instead of concrete object types.

 public class ProductsViewModel
{
    private readonly IProductsService _productsService;
    public ProductsViewModel(IProductsService productsService)
    {
        _productsService = productsService;
    }
    // code removed for brievety
}

Setup Unity

Install Unity nuget package into PCL project by running this command:

 PM> Install-Package Unity -Version 4.0.1

Register dependencies

Create the container and register the service dependency in the App class constructor:

 var unityContainer = new UnityContainer();
unityContainer.RegisterType();
unityContainer.RegisterInstance(typeof(ProductsViewModel));//optional

Configure ServiceLocator

After that, we need to configure the app to look for its dependencies from within this container. This is what the ServiceLocator does:  

 var unityServiceLocator = new UnityServiceLocator(unityContainer);
ServiceLocator.SetLocatorProvider(() => unityServiceLocator);

Resolve dependencies

Now we are ready to resolve the dependencies. The app will get an instance of ProductsService when it asks for the type of IProductsService in the ProductsViewModel constructor.

Until now, the view was binded to the view-model. It creates a new instance of the view-model. But, it should get it from the container. Let’s change that. Remove the BindingContext element in XAML and apply one of these two solutions:

From code-behind (C-Sharp), direct access to ServiceLocator:

 public ProductsPage()
{
    InitializeComponent();
    BindingContext = ServiceLocator.Current.GetInstance(typeof(ProductsViewModel));
}

From XAML code, using ViewModelLocator. Because XAML cannot call method GetInstance(…) directly to get the view-model from ServiceLocator, we’ll pass through a property. Let’s create a ViewModelLocator class which will define a ProductsViewModel property.

 public class ViewModelLocator
{
    public ProductsViewModel ProductsViewModel
    {
        get { return ServiceLocator.Current.GetInstance(); }
    }
}

In App.xaml, we create an instance of the ViewModelLocator to be accessed by XAML views. Note it is a global resource.

 <Application xmlns="https://xamarin.com/schemas/2014/forms"
             xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:viewModels="clr-namespace:IocAndDiXamarinForms.ViewModels"
             x:Class="IocAndDiXamarinForms.App">
        <Application.Resources>
         <ResourceDictionary>
             <viewModels:ViewModelLocator x:Key="Locator"/>
         </ResourceDictionary>
        </Application.Resources>
</Application>

Then, from the XAML view, we have access to the view-model resolved from the container:

 <ContentPage xmlns="https://xamarin.com/schemas/2014/forms"
             xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="IocAndDiXamarinForms.Views.ProductsPage"
             xmlns:viewModels="clr-namespace:IocAndDiXamarinForms.ViewModels"
          BindingContext="{Binding ProductsViewModel, Source={StaticResource Locator}}">
     <!--<ContentPage.BindingContext>
         <viewModels:ProductsViewModel />
     </ContentPage.BindingContext>-->
     <ContentPage.Content>
        <ListView ItemsSource="{Binding Products}" />
     </ContentPage.Content>
</ContentPage>

Finally, we have transformed the way the app gets its dependencies. All the object creation is done from the outside. We don’t even care how they are created, as long as we’ll always get the one we ask for (I thought about saying ‘Dependency as a Service’!).

In the following section, we’ll take a look at advantage of IoC and DI in two scenarios: accessing platform specific code from PCL and writing unit tests.

Accessing platform specific code from PCL

We cannot access Android, iOS or UWP specific platform code directly from a PCL project. In order to do so, we need to abstract the implementation through an interface. Here’s a use case: to implement the text to speech feature, we create the native implementation for each platform, then we pass that abstracted implementation to PCL.

The interface should be created in PCL project, as we have done with IProductsService.

 public interface ITextToSpeech
{
    void Speak(string text);
}

We can implement this interface in each platform. Here’s a sample implantation for iOS:

 public class TextToSpeechIosImpl : ITextToSpeech
{
    public void Speak(string text)  
    {
        var speechSynthesizer = new AVSpeechSynthesizer();
        var speechUtterance = new AVSpeechUtterance(text)
        {
            Rate = AVSpeechUtterance.MaximumSpeechRate / 4,
            Voice = AVSpeechSynthesisVoice.FromLanguage("en-US"),
            Volume = 0.5f,
            PitchMultiplier = 1.0f
        };
        speechSynthesizer.SpeakUtterance(speechUtterance);
    }
}

We can register and resolve it in either of two ways: DependencyService or Dependency Injection.

DependencyService

Xamarin Forms defines a DependencyService class, which can register and resolve dependencies. Here’s how to register the platform implementation in native project code:

 [assembly: Xamarin.Forms.Dependency(typeof(TextToSpeechIosImpl))]

Here’s how to resolve the implementation from PCL:

 var textToSpeach = DependencyService.Get<ITextToSpeech>();

This link provides more detail on the platform implementation.

Dependency Injection

To register the implementation object to the container, we’ll pass it as a parameter in the App constructor:

 // AppDelegate.cs (iOS native project)
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    Forms.Init();
    LoadApplication(new App(new TextToSpeechIosImpl()));
    return base.FinishedLaunching(app, options);
}
// App.xaml.cs
public App(ITextToSpeech textToSpeech)
{
    //... code removed for brievety
    unityContainer.RegisterType();
    unityContainer.RegisterInstance(typeof(ITextToSpeech), textToSpeech);
}

After that, we can retrieve it from the ProductsViewModel. At this point, we can invoke the Speak() method, which will run the native implementation from PCL.

 public ProductsViewModel(IProductsService productsService, ITextToSpeech textToSpeech)
{
    //... code removed for brievety
    textToSpeech.Speak("IoC and DI");
}

Unit tests & mocks

To create a unit test for the ProductsViewModel, we need to pass an implementation for ITextToSpeech. The implementations we created are platform dependent. But fortunately, we can create our own implementation to mock the real object.

 public class MockTextToSpeach : ITextToSpeech
{
    public void Speak(string text)
    {
        // implementation...
    }
}

Tip: In this sample, we choose to create a Mock object by implementing the interface. But there are some frameworks for creating mocks like Moq, as well, seen here.  

We can also create a mock for IProductsService, and we can  use the implementation provided in PCL as it is not platform specific, and doesn’t make a call to web service or database. The result is the following unit test:

 [TestClass]
public class ProductsUnitTest
{
    [TestMethod]
    public void GetProductsTest()
    {
        // Arrange
        IProductsService productsService = new ProductsService();
        ITextToSpeech mockTextToSpeech = new MockTextToSpeach();
        var vm = new ProductsViewModel(productsService, mockTextToSpeech);
        // Act
        vm.DownloadProducts();
        // Assert
        var expected = productsService.Getproducts();
        var actual = vm.Products;
        Assert.AreEqual(expected, actual);
    }
}

Conclusion

We saw how useful it can be to apply IoC and DI in an Xamarin Forms app, in order to ensure maintainability and extensibility. The steps are almost the same for other platforms (like WPF, ASP.NET and Windows UWP). And the IoC containers are almost the same as they are becoming cross platform. This makes it easy to learn it once, and apply it everywhere using the same tools.

For more information, check out the source code on Github.


Houssem Dellai is a Tunisian Microsoft MVP who shares his professional experience as Xamarin Consultant through articles and videos on channel9. Follow him on Twitter @ HoussemDellai.