Dependency Injection in iPlayer Android

Our journey to write cleaner code

Matthew Williams
BBC Product & Technology
8 min readSep 19, 2017

--

The last couple of years have been a time of transition for the iPlayer Mobile team. We’ve gone from 2 small feature crews working on 2 apps (Android & iOS) to 3 large crews working on 4 (iPlayer & iPlayer Kids, Android & iOS).

We’ve learnt a huge amount in that time; about how to organise our team, how best to work on features and also how to write better code. With the team growing, it’s become more important than ever to practice good software design techniques which allow us to write maintainable and extensible code.

With the S.O.L.I.D principles in mind, we’ve been trying to gradually transform our codebase through incremental changes and the refactoring of problematic areas of code we’re exposed to in our day to day work.

For more reading on the S.O.L.I.D principles, with a particular focus on Android, check out Donn Felker’s blog series over at the Realm Academy:

The two, similarly named, principles which I’d like to focus on here are Dependency Injection & Dependency Inversion.

Dependency Injection, tl;dr

The practice of providing a module with its dependencies rather than it creating it’s own.

Dependency Inversion, tl;dr

The practice of providing a module with it’s dependencies via abstractions, rather than concretions

Put together, and very simply, it’s going from something like this:

Example 1: Creating a class instance of the dependency in the constructor

to this:

Example 2: Injecting in the Dependency interface

In example 1, Subject is forever bound to ConcreteDependency. We cannot create an instance of Subject without also creating an instance of ConcreteDependency.

In example 2, Subject is injected with an abstraction, Dependency. Depending on an abstraction in this way has decoupled Subject from ConcreteDependency. We can now create an instance of Subject with any implementation of the Dependency interface.

Writing code like example 2 allows us to write decoupled code. In the second example, Subject knows nothing about the implementation of Dependency. All it knows that is has a single method, act(). If the implementation of the Dependency interface were to change, Subject would not.

Following on from this, a system like example 2 is far easier to test. Production code can inject one implementation of Dependency whereas tests can inject another.

Also consider the readability of the second example. From the call-site you can see exactly what the Subject class does and what its concerns are (assuming you have well named interfaces!). This will greatly help reduce the cognitive load of understanding a large codebase as well as avoiding possible unknown side effects of instantiating a class.

In iPlayer we’re trying to avoid writing code like example 1. The DI principle seems simple, and it is, but often there are hurdles to writing clean, decoupled code.

iPlayer Kids

Back in 2015 we were tasked with taking the monolithic iPlayer codebase and using it to create a brand new app, iPlayer Kids. The approach would be fairly straightforward, take some code from iPlayer, move it to a common library module and then use it in both iPlayer and the iPlayer Kids.

For more information about this project, and the way our apps are structured, check out Ross Harper’s recent blog: “Mobile Apps — iPlayer tech on the move”

If we were injecting all of our dependencies via abstractions, this would have been trivial. However we weren’t, so it wasn’t.

Consider the following example.

Here we have a piece of UI, iPlayerHomeFragment, which represents the homepage of the iPlayer app. This instantiates a class, HomeController, which handles triggering data fetching and notifying the view layer of updates. On the surface, HomeController appears to be a great candidate for reuse. All the visible dependencies are abstractions so that we can inject kids specific behaviours from the hypothetical KidsHomeFragment.

So we move HomeController and its dependencies into our new, shared library module and we immediately discover a problem. The HomeController instantiates a HomeDataProvider . This then grabs an instance of a Singleton and uses it to get the url endpoint for the home feed. The problem here is that the home feed endpoint differs between iPlayer and iPlayer Kids.

For some context, when talking about Config I’m referring to the Remote Configuration on which the iPlayer apps depend. This remote configuration contains things such as remote endpoints, feature toggles etc.

The dependency graph of the HomeController wasn’t clear and has caught us out. Whilst the data provider itself is reusable, Config is an iPlayer domain specific object.

Consider the other problems with this code. The Config interface is large, concerning multiple iPlayer specific concepts. Should a data provider for the home screen know about the endpoints for every other screen?

How would you go about testing a system like the above? As Config isn’t injected you would have to set up the Singleton with a mock instance. Maybe add a setInstance() method to the interface just for testing? Should you need to add methods just for use in tests?

Also, what if you wanted to test a discrete piece of logic in the HomeController? It can’t be instantiated without it also instantiating a HomeDataProvider. These side effects make writing reliable tests incredibly difficult

And what about the multiple concerns mentioned above? Should you need to mock out methods relating to unrelated features in a unit test?

This is where Dependency Inversion (and interface segregation) helps us. By moving the use of Config.getInstance() up to the iPlayer domain layer, we can then inject the required configuration, via an abstraction, down into the shared layer.

Now, we’ve inverted the dependency graph. Rather than the HomeDataProvider having a hidden dependency on the whole of Config, it now has an injected dependency on the HomeEndpointProvider abstraction.

Our implementations of the HomeEndpointProvider interface could look something like:

As mentioned earlier, this enables us to inject different abstractions depending on app product or for unit tests.

An example implementation which could be used for testing

Single Instance vs Singleton

The problem faced in the above example was solved by pulling that Config dependency up to the top level. I’d like to spend a bit of time talking about why this happened. The Singleton.

The use of a Singleton meant that the Config class became globally accessible making it incredibly easy to grab the instance from deep within a data fetcher.

In this case, it’s necessary that we only have one instance of Config per lifecycle of the app and creating a Singleton is a well known pattern for enforcing this. But what’s the alternative? How can we have one, long-lived instance of an object without exposing the global state?

The approach we are advocating for is to just create one instance in a high level object and inject that instance where needed.

Uncle Bob has a great blog post describing this idea: “The Little Singleton”

Where is a suitable high level place for this single instance to live? On Android there is the Application class. There is only ever one instance of this top level object and so it is a perfect place for us to build and hold onto our long lived dependencies. These dependencies can then be injected down into the app components which need them.

Platform complications

In an ideal world this would be simple. We’d build our dependencies in the application class and inject them down. However on Android things are rarely this simple. There are hard boundaries, dictated by the framework, scattered across the app which make passing dependencies very difficult.

In Android each app component (Activity/Fragment/Service) is decoupled and they communicate via a messaging system (Intents). The constructors of these components are not exposed and we do not get a reference back on which to inject our dependencies.

If you want to read more about this, check out the Android documentation https://developer.android.com/guide/components/intents-filters.html

In iPlayer, for instance, when you tap on an episode on the home screen, we send an intent to the framework that tells it how to launch the Episode page. This page displays detailed information about the episode along with call-to-actions for playback, downloads etc.

This hard boundary makes dependency injection very difficult and has resulted in the overuse of Singletons in our codebase. Singletons make it easy right? Just pluck the instance out of the ether where needed.

There is one object, however, which persists across these app boundaries. The ‘Context’. If you’re not familiar with Android, think of the Context object as our gateway into the Android framework. The docs refer to it as:

…an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations…

What the context exposes to us is our Application class. We can get a reference to our Application class from our context instance, and use it to inject our dependencies. See below for an example.

The iPlayer Application object
An accessor class
An Activity. This could be any Android UI component

MainActivity itself is a Context (Activity extends Context). This gives us access to the Application class. The Application class then conforms to an interface which provides us with out DownloadManager.

What this means is that we can inject our dependencies into our high level Android classes, via the Application, and then inject them down into our collaborators. Our collaborators then depend on these abstractions and we’re on our way to writing decoupled code!

Summary

If you’ve read this far you’re probably wondering why we don’t use one of the millions of Dependency Injection frameworks available to us. The most fashionable at the moment appears to be Google’s fork of Dagger, Dagger 2.

Whilst these frameworks are great and can reduce cognitive load*, I think it’s important to understand what’s going on and what problems you’re trying to solve.

By understanding the concepts and the reasons for implementing certain patterns, we can gain knowledge which will help us write better code going forward.

*Once you’ve understood the convoluted example, accepted that you’re coupling your code to a third party dependency and assessed the impact on your method count

Join us

If you’re interested and excited by the technical challenges we’re facing, we’re always looking for driven engineers to join us. We’re not prescriptive about what languages you know since we think a good engineer can learn quickly. That said, experience with iOS, Android, JavaScript or Java would definitely give you a head start.

In the Mobile iPlayer team we’re currently hiring for Software Engineers and Senior Software Engineers.

You can see all our job listings for BBC iPlayer here by searching for iPlayer.

--

--

Matthew Williams
BBC Product & Technology

Software Engineering Team Lead on iPlayer Mobile at the BBC