XAM250 Patterns for Cross Platform Mobile Development

Exercise 3: Switch to a DI/IoC container and inject our dependency into our code. (XAM250)

This final exercise will replace the Factory pattern used in the first exercise with a Dependency Injection (DI) container that will supply the quote loader as part of the constructor. You'll then optionally, remove the Service Locator as well and just use the DI/IoC container injection approach.

To complete the exercise, you will need Visual Studio for Windows or macOS with the Xamarin development tools installed. You will also need either an emulator/simulator or a device to run the exercise on. Please see the setup page if you need help installing the Xamarin development environment.

Open the starter solution

Open the starter solution from the Exercise 3 > Start folder in your copy of the cloned or downloaded course materials in either Visual Studio on Windows or Visual Studio for Mac.


Adding the DI/IoC container

  1. In the Assets folder under Exercise 3 you will find a SimpleContainer class. This is a very simple implementation of an IoC container that allows you to register and create types. Add it into your PCL.

    You'll use this as an example - but keep in mind that it is deliberately simplified so you can see the concepts involved and trace through the code if you like. For non-sample applications you should consider a real IoC container.
  2. Examine the code; it has two methods (with two variations on each):

    Method Description
    Register This is used to register known abstractions with the container. You register a creator function with the abstraction type so it may be created, or just pass the implementation type if it has a default constructor. It allows the container to resolve interfaces to types similar to a Service Locator. You could also use a Service Locator for this purpose if you wanted to.
    FactoryFor<T> This method is used to create a factory method which will create the T type. It returns a Func<T> where the implementation goes through the container to generate the type. This can be used to create factory methods so consumers do not have to have a reference to the container itself.
    Create This method is used to create types. If the type is a registered type, then the associated registration method is used. Otherwise, if the type has a public, default constructor, then it will be used to create the object. If the type does not, then the code will pick the first public constructor and attempt to create each of the required parameters by recursively calling Create and then finally construct the final object passing each of the parameters. If it is unable to create the given type, a null is returned.

Modify the Quote Manager to take constructor parameters

Next, change the code in the QuoteManager class to inject the IQuoteLoader dependency as a constructor parameter.

  1. Open the QuoteManager and add a new parameter to the constructor of type IQuoteLoader. Assign the passed parameter to a field. Make sure to remove the existing code that uses the Factory approach.
  2. Change the constructor to be public so you can create it externally.
  3. Change the static Instance property to have a private setter and remove the existing Lazy field. Instead, assign the Instance property in the constructor - make sure to add a check to ensure it's not set more than once to enforce the singleton pattern being used.

    public class QuoteManager
    {
        public static QuoteManager Instance { get; private set; }
    
        readonly IQuoteLoader loader;
        public IList<GreatQuote> Quotes { get; private set; }
    
        public QuoteManager(IQuoteLoader loader)
        {
            if (Instance != null) {
                throw new Exception("Can only create a single QuoteManager.");
            }
            Instance = this;
            this.loader = loader;
            Quotes = new ObservableCollection<GreatQuote>(loader.Load());
        }
        ...
    }
    

Setup the DI container

Finally, you need to create the container in each of your platform-specific projects and register the IQuoteLoader implementation with it. Then you'll use the container to create our QuoteManager so that it properly "injects" in the constructor parameters required. You'll repeat the same basic steps in each project (iOS, Android and Windows).

  1. Open the file which sets up the factory used to locate the IQuoteLoader implementation. In iOS, this is AppDelegate.cs, Android uses App.cs and Windows has App.xaml.cs.
  2. Add a new SimpleContainer field to the application-level class and new up an instance of the container.
  3. Remove the current factory registration code - this is in FinishedLaunching in iOS, OnCreate in Android and in the App constructor in Windows.
  4. Next, register the IQuoteLoader abstraction with the proper platform-specific implementations using the container's Register<T, TImpl> method.
  5. Finally, create the QuoteManager instance with the container's Create method and assign it to the existing field you are using now.
  6. Run the application on one of the supported platforms and make sure it all still works - try putting a breakpoint into the QuoteManager constructor to see the injected parameter - look at the call stack to see how you got it.

iOS code

// AppDelegate.cs
public partial class AppDelegate : UIApplicationDelegate
{
  readonly SimpleContainer container = new SimpleContainer();

  public override UIWindow Window { get; set; }

  public override void FinishedLaunching(UIApplication application)
  {
    //QuoteLoaderFactory.Create = () => new QuoteLoader();
    container.Register<IQuoteLoader, QuoteLoader>();
    container.Create<QuoteManager>();
  }
  ...
}

Android code

// App.cs
public class App : Application
{
  readonly SimpleContainer container = new SimpleContainer();
  ...

  public override void OnCreate()
  {
    //QuoteLoaderFactory.Create = () => new QuoteLoader();
    container.Register<IQuoteLoader, QuoteLoader>();
    container.Create<QuoteManager>();

    ServiceLocator.Instance.Add<ITextToSpeech, TextToSpeechService>();

    base.OnCreate();
  }
}

Windows code

// App.xaml.cs
sealed partial class App : Application
{
    readonly SimpleContainer container = new SimpleContainer();

    public App()
    {
        //QuoteLoaderFactory.Create = () => new QuoteLoader();
        container.Register<IQuoteLoader, QuoteLoader>();
        container.Create<QuoteManager>();

        ServiceLocator.Instance.Add<ITextToSpeech, TextToSpeechService>();

        this.InitializeComponent();
        this.Suspending += OnSuspending;
    }
    ...
}

Extra challenge: Refactor

The container is being used to create the QuoteManager with a passed IQuoteLoader to load the quotes. The QuoteManager also uses the ITextToSpeech implementation which is located through the Service Locator you added in the last lab. What if you wanted to remove this dependency and instead have it injected as well? There is a final Challenge completed solution in the Exercise 3 folder of your copy of the cloned or downloaded course materials.


Exercise summary

In this exercise, you've taken an existing application and utilized a DI/IoC container to loosely couple dependencies together.

You can view the completed solution in the Exercise 3 > Completed folder of your copy of the cloned or downloaded course materials.

Go Back