Using Inversion of Control in MonoDroid Applications

Just because a mobile application needs to be (or should be, in most cases) small and lightweight doesn’t mean we should set aside best practices like Inversion of Control containers when building our apps. I don’t want this to be an introduction to dependency injection, so I’d highly recommend reading up on it a bit if you aren’t familiar with the pattern. With so many options out there for .NET containers, which ones should we use? In this article I’ll go over some good options I’ve found for using dependency injection in MonoDroid applications.

When looking for containers to try out, my main goal was to find something really lightweight. I don’t typically need too much complexity from my containers, so I wanted something that give me that layer without much baggage. I have used Ninject before and quite like it (how can you not like ninjas?), but didn’t go down that route since it seemed I wouldn’t be able to leverage a lot of its power on Android. I was able to get it to compile against MonoDroid, so it should be possible. One disappointing thing I found was that Android doesn’t seem to expose any hooks into the creation process for components (something like ASP.NET MVC’s ControllerFactory), which would have made for a perfect injection point.

The two containers I decided to go with were TinyIoC and Funq. I will talk about each later in the article, but first let’s set up our sample application and pattern.

The Application

The sample application here will be simple, with a big button that picks a new quote from a particular movie and displays it. Different movie quote providers can be done by implementing IMovieQuoteProvider:

public interface IMovieQuoteProvider  
{
  string GetQuote();
}

We’ll implement two providers, for The Big Lebowski and Spaceballs. Here is one of them (with some quotes cut out), but you can see everything in the sample project at the end:

public class TheBigLebowskiQuoteProvider : IMovieQuoteProvider  
{
  private Random _randomNumberGenerator;

  public TheBigLebowskiQuoteProvider()
  {
    _randomNumberGenerator = new Random();
  }

  private string[] _quotes = { "Mark it, Dude." };

  public string GetQuote()
  {
    return _quotes[_randomNumberGenerator.Next(0, _quotes.Count() - 1)];
  }
}

Now we can move on to actual Android code. Since we want to use the container globally across the application, that means we want it to get loaded when the application first starts up, before any activities get fired up. To do that, we can subclass Android.App.Application and decorate it with ApplicationAttribute. You can only have one class with that attribute in your application or it won’t compile.

[Application]
public class DemoApplication : Application  
{
  public DemoApplication(IntPtr handle, JniHandleOwnership transfer)
    : base(handle, transfer)
  {
  }

  public override void OnCreate()
  {
    base.OnCreate();
  }
}

Next, let’s define the layout for the main (and only) activity in Resources/layouts/main.xml:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <Button
    android:id="@+id/generate"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="New Quote" />
  <TextView
    android:id="@+id/quote"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
</LinearLayout>  

Last but not least, the activity:

[Activity(Label = "IoC Demo", MainLauncher = true)]
public class DemoActivity : Activity  
{
  private IMovieQuoteProvider _quoteProvider;
  private Button _generateButton;
  private TextView _quoteText;

  protected override void OnCreate(Bundle bundle)
  {
    base.OnCreate(bundle);

    SetContentView(Resource.layout.main);
    _generateButton = FindViewById<Button>(Resource.id.generate);
    _quoteText = FindViewById<TextView>(Resource.id.quote);

    _generateButton.Click += delegate
    {
      _quoteText.Text = _quoteProvider.GetQuote();
    };
  }
}

When the button is clicked, the IMovieQuoteProvider will be used to get the next quote. All that’s left is to inject the implementation.

TinyIoC

First up is TinyIoC, which truly lives up to its name. One of the nice things about TinyIoC is that it is totally contained within a single C# file, making it trivial to include in any project without having to worry about references, and it is designed to work properly on many platforms. Despite being “small”, it still has a nice fluent API as well as some auto-registration features. I’m only going to go over a very simple use case, so I encourage reading more if you like this approach.

Let’s go back into DemoApplication and set up the container. We’ll add a function initTinyIoC(), and call it from OnCreate():

public override void OnCreate()  
{
  base.OnCreate();

  initTinyIoC();
}

private void initTinyIoC()  
{
  TinyIoCContainer.Current.Register<IMovieQuoteProvider>(new TheBigLebowskiQuoteProvider());
}

TinyIoC uses a singleton container that you can access anywhere in your application. Here we tell the container that whenever someone requests a IMovieQuoteProvider object, it should hand them the instance of TheBigLebowskiQuoteProvider we supplied. If you wanted you could supply it with a factory method via a lambda expression instead of creating the object up front.

Now DemoActivity needs to pull the object out of the container. In the OnCreate() method, anywhere before the click handler, add:

_quoteProvider = TinyIoC.TinyIoCContainer.Current.Resolve<IMovieQuoteProvider>();  

After that call, the activity’s private quote provider will be pointed at the one in the container, and we’re good to go. Fire up the app and you should be able to scroll through a handful of quotes from The Big Lebowski.

Lebowski screenshot

Funq

Never satisfied in having just one option, I turned to Funq next. At the time that this was written, Funq doesn’t officially support MonoDroid (which is still in private beta, so that’s not all that surprising). However, I found that the Windows Phone 7 version of it seems to work properly, so we’ll use that for now. If you’re compiling the source yourself, you can get it to compile correctly against the MonoDroid profile if you use the WINDOWS_PHONE build symbol. The reason is that MonoDroid lacks some of the System.Func<> delegates that are in newer versions of the full .NET Framework. Windows Phone 7 is missing the same ones, and Funq is set up to handle that properly in the build by defining the delegates itself. In the sample project I’m providing, I include and reference the WP7 version of Funq 1.0.

There isn’t much in the way of documentation for Funq, but there are plenty of unit tests provided which is just as good. Funq was originally created as part of the Mobile Application Blocks project, aiming to provide a fast and easy container with no use of reflection at all, which is a nice feature to have.

Back in DemoApplication, let’s add support for the Funq container. Funq doesn’t use a singleton container, so we’ll manage the instance ourself via a public property on the class, allowing it to be accessed by the activity.

public Container FunqContainer { get; private set; }

public override void OnCreate()  
{
  base.OnCreate();

  initFunq();
}

private void initFunq()  
{
  FunqContainer = new Container();

  FunqContainer.Register<IMovieQuoteProvider>(new SpaceballsQuoteProvider());
}

Just like we did with TinyIoC, the container will now hand out the instance of SpaceballsQuoteProvider whenever someone requests IMovieQuoteProvider. In DemoActivity, change the line that uses TinyIoC to use the Funq container:

_quoteProvider = ((DemoApplication)Application).FunqContainer.Resolve<IMovieQuoteProvider>();  

This time when you fire up the app, you should see quotes from Spaceballs.

Spaceballs screenshot

Hopefully this helped give you an idea of how to easily start using dependency injection in your MonoDroid applications. Which container do you prefer?

comments powered by Disqus
Navigation