Why you should embrace Dependency Injection

Why do we need Dependency Injection in our lives? If you think you don’t need it, keep reading. It will change your life into becoming a much better programmer, I promise.

In the previous article we covered what Dependency Injection is, if you missed it check it out here. In this second part of the Dependency Injection series we will cover in-depth what are the benefits of DI.


Alright, what the hell is not working the way it should?

The truth is it could work the way it used to. Unfortunately, there are several portions of our project that usually go sideways if we don’t incorporate DI. What are the benefits of using Dependency Injection and NOT going with the traditional approach of hardcoding the dependencies inside each class?

  1. There is less boilerplate code
  2. Components are loosely coupled
  3. The code is highly testable

Alright, before going into actual examples, let’s establish a basic requirement, just so you know what to expect:

  • Basic to mid programming experience (If it’s in Android then you’re a match, but no worries if your heart is in another place, the concept is the same!)

1. There is less boilterplate code

Let’s say you are working on a huge application that requires a sophisticated client to get some weather records. Let’s actually call the client that is responsible for this WeatherRepository.

Imagine if our ViewModel would rely on this WeatherRepository which on turn would rely on other 3 other clients, RemoteWeatherRecordsClient, InMemoryWeatherRecordsClient and LocalWeatherRecordsClient. And now each of this client would have as dependency different clients. Our WeatherRecordsClient would look like this:

Now let’s instantiate this WeatherRecordsClient in 2 fragments:

Messy, isn’t it? Also, there is a lot of repetitive code taking up a lot of space. But what if we needed this WeatherRepository in 10 fragments or activities through their ViewModels? Or what if we would like the pass the same instance to all those ViewModels constructors?

Let’s do some Dependency Injection!

Awesome, this looks much better! We have reduced the boilerplate and we were also able to pass the same WeatherRepository to our ViewModels!


Alright, time for some coffee! Take a break before getting into the next section, I need you fresh!


2. Components are loosely coupled

To address this issue, Dependency Injection relies on interfaces for injecting dependencies. This way, different implementation can swapped at the injection level, therefore decoupling components and allowing them to depend on abstractions rather than concrete implementations. Let’s have a look on how Dependency Injection enforces that.

Say we need to fetch some data to two fragments from different sources using ViewModels. Therefore, one fragment should be populated with local data from a particular data source, while the other one with remote data from a different data source.

Instead of creating two separate ViewModels that each reference a different data source, how can we re-use the same ViewModel with interfaces and Dependency Injection?

We know that both data sources should get some data, but the ViewModel should not care how that data is obtained, so what we did was to create two different data sources that both implement the same interface DataSource and add this particular contract as a dependency inside the DataViewModel. At the same time, we’ve set the injector to create both the local and the remote instances to be ready for injection.

Alright, now let’s create the two fragments and let’s inject their dependencies:

As you can see, we have passed the same type of ViewModel and for all they know, both Fragment and ViewModel don’t really care what’s the concrete implementation for the data sources that they rely on.

As long as the DependencyInjector is in charge of providing the concrete implementation of the DataSource we could have an infinite number of clients to swap! And do you know what the best part is?

The code is highly testable because we can mock those interfaces! Let’s move into the final section in order to clear this up.

3. The code is highly testable

In order to see why testing is much easier after having all the dependencies externalized, let’s try unit testing without any depedency injection in place.

Let’s first define a DataViewModel that has as hardcoded concrete dependency a LocalDataSource that as well relies on getting some informations from a json file stored locally.

As you can see we haven’t used any dependency injection or any kind of interfacing. Let’ try to test this and see what goes wrong!

First of all, when we try to create the dependencies for the DataViewModel we will probably see an error telling us that the test doesn’t have access to our json file stored locally. Bollocks!

Secondly, what if LocalDataSource would have had many clients, similarly with what we described in the first section – 3 clients as dependencies and each one of those would have had 2 more clients? Then instantiating this LocalDataSource would have been a nightmare as in the same nightmare you’d already be having in the actual application-side instantiation.

Let’s say that we would somehow manage to instantiate the concrete dependencies, but testing a concrete dependency will usually result in a failed test as concrete implementation require a full running application system. Also, most frameworks will require you to pass interfaces as dependencies, this way they can help you in mocking their behavior.

Confused what mocking is?

Mocking is primarily used in unit testing. An object under test may have dependencies on other (complex) objects. To isolate the behavior of the object you want to replace the other objects by mocks that simulate the behavior of the real objects. This is useful if the real objects are impractical to incorporate into the unit test.

Alright, let’s fix this by adding some interfaces and by injecting the dependencies:

Perfect, we have used an interface which should now allow us to mock that particular dependency’s behavior. Let’s use Mockito and see how testing would look now:

Our test should not only that it ran flawlessly but it also allowed us to basically fake the behavior of the ViewModel`s dependency, therefore having complete control on our isolated testing environment.

To sum this up, by using interfaces and Dependency Injection, we were able to test easier and have full control on how certain dependencies react to our test subject.

Next up we will cover how to achieve Dependency Injection through the use of frameworks! Subscribe to stay tuned!


I want you focused so take a break, and see ya in the next article! And remember, if you enjoyed this article, you can buy me a coffee using the coffee bottom widget!

6+
%d bloggers like this: