# Ultimate Guide to Dependency Injection in Android — Part 1. DI and Its Benefits

![Photo by Manuel Chinchilla on Unsplash](https://miro.medium.com/v2/resize:fit:1400/0*TyJmDamd3U71vhuH align="left")

## Story time. Why understanding the basics of DI is important.

My introduction to [Dagger](https://dagger.dev/) was terrifying, to say the least. I was a junior developer and I was assigned to a project. I remember the very first time I saw the codebase. It was a rather complex code, filled with advanced usage of the Dagger library. In my opinion, I should have been assigned to this project in the first place. But it is what it is. The company was short on devs, the deadlines were coming and the project needed help. I remember feeling absolutely terrified and overwhelmed by it all. The development was fast-paced, and most of the time, I found myself just copying and pasting code. I knew that the code worked, but I didn’t understand it. Eventually, my lack of knowledge came back to bite me when it was time for me to develop new features. I found myself unable to make even the simplest changes and I had to use hacks instead. This experience taught me the importance of *really* understanding how Dependency Injection works.

That’s why I’ve decided to write this series of articles. I will take us through the very basics of Dependency Injection, all the way up to advanced usage of Dagger and Hilt. I want to help other developers avoid the same stressful situation I found myself in.

In this first article, we’ll take a look at what DI is and what are its benefits.

![Photo by Rhys Kentish on Unsplash](https://miro.medium.com/v2/resize:fit:1400/0*tuVi1zSO6rwy4Ked align="left")

## **What is Dependency Injection?**

Dependency Injection (DI) is a software design pattern that can improve code quality and maintainability. The pattern involves separating the creation of objects from their usage. Such approach makes it easier to modify and test code. It is used to reduce coupling and increase flexibility in software development.

Let’s look at an example to help us understand better:

```kotlin
class RigidComputer() {
    private val processor = Processor()
    fun compute() {
        processor.compute()
    }
}
 
fun main(args: Array<String>) {
    val computer = Computer()
    computer.compute()
}
```

What’s the problem with this code? Well, the most obvious thing here is that a `RigidComputer` class is dependent on a `Processor` class. In other words, the `RigidComputer` class is ***tightly coupled*** to the `Processor`. You might have heard that a lot of times when people speak about DI. Essentially, tight coupling makes code:

* Hard to reuse
    
* Tough to refactor
    
* Almost impossible to test properly
    

We’ll go into why that’s the case in a minute. Meanwhile, let’s see how we can apply the DI pattern here:

```kotlin
class FlexibleComputer(private val processor: Processor) {
    fun compute() {
        processor.compute()
    }
}
 
fun main(args: Array<String>) {
    val processor = Processor()
    val computer = Computer(processor)
    computer.compute()
}
```

As you can see, in this `FlexibleComputer` we’ve moved the creation of the `Processor` class out of our previous `RigidComputer` class. There is no tight coupling anymore. We can now pass any implementation of the `Processor` which gives us lots of benefits. More specifically, our code is now:

* Easy to reuse
    
* Effortless to refactor
    
* Painless to test
    

Now let’s look at each of these points in detail.

## Benefits of DI

![Photo by Nareeta Martin on Unsplash](https://miro.medium.com/v2/resize:fit:1400/0*amLC5YYXp5Ru6zqx align="left")

## **Reusability**

One of the main benefits of DI is that it makes code easier to reuse. When dependencies are injected into a class, the class can be easily reused in different contexts. At the same, we don’t need to modify the class’s implementation.

Let’s imagine a scenario where we have a requirement to build `MultiCoreComputer` and a `SingleCoreComputer`from our `RigidComputer` class. The difference between them is in their processor types. How can we do that with our `RigidComputer` class? The short answer is we wouldn’t be able to. Not easily at least.

We have no control over the creation of the `Processor` in our `RigidComputer` class. This forces us to create separate classes for the `MultiCoreComputer` and the `SingleCoreComputer`.

```kotlin
class MultiCoreComputer() {
    private val processor = MultiCoreProcessor()
    fun compute() {
        processor.compute()
    }
}

class SingleCoreComputer() {
    private val processor = SingleCoreProcessor()
    fun compute() {
        processor.compute()
    }
}

val multiCoreComputer = MultiCoreComputer()
val singleCoreComputer = SingleCoreComputer()
```

Now, let’s see how we would do that with our `FlexibleComputer` :

```kotlin
val multiCoreProcessor = MultiCoreProcessor()
val singleCoreProcessor = SingleCoreProcessor()
val singleCoreComputer = FlexibleComputer(singleCoreProcessor)
val multiCoreComputer = FlexibleComputer(multiCoreProcessor)
```

And that’s it. If we needed a `QuadCoreComputer` or an`OctaCoreComputer` — it would be as easy as passing a different `Processor` to our `FlexibleComputer`.

![Photo by Todd Quackenbush on Unsplash](https://miro.medium.com/v2/resize:fit:1400/0*miDMTdl2MU3f_Pcl align="left")

## Ease of Refactoring

Another benefit of DI is that it makes code easier to refactor. By injecting dependencies into a class, it becomes possible to replace them. We are able to swap out dependencies without touching the class’s implementation.

Consider the following example:

```kotlin
class RigidWashingMachine() {
    private val processor = Processor()
    fun prepareWashParams() {
        processor.compute()
    }
}

class RigidComputer() {
    private val processor = Processor()
    fun compute() {
        processor.compute()
    }
}
```

We have our good ol’ `RigidComputer` and another non-flexible `RigidWashingMachine` . Both of them are dependent on a `Processor`. However, they are completely different in their functionality. `RigidWashingMachine` washes and `RigidComputer` computes. Changing the `Processor` in such code is troublesome because we risk breaking `RigidComputer` or `RigidWashingMachine`. So what do we do? Dependency Injection to the rescue!

```kotlin
class FlexibleComputer(private val processor: IProcessor) {
    fun compute() {
        processor.compute()
    }
}

class FlexibleWashingMachine(private val processor: IProcessor) {
    fun prepareWashParams() {
        processor.compute()
    }
}

interface IProcessor {
    fun compute()
}
```

First of all — let’s introduce an interface `IProcessor`. This makes sure we’re not relying on any concrete implementation of the `Processor`. Now we can implement our DI pattern. Our new `FlexibleWashingMachine` and `FlexibleComputer` do not care about the implementation of the `IProcessor`. Whoever works with the `FlexibleWashingMachine` and `FlexibleComputer` just needs to pass in the`Processor` implementation they need. They don’t have to worry about breaking something.

The same cannot be said about our previous example. Messing with `RigidComputer` and `RigidWashingMachine` when they are relying on the same `Processor` can be dangerous.

![Photo by National Cancer Institute on Unsplash](https://miro.medium.com/v2/resize:fit:1400/0*tPHm9R7HBDHESaqa align="left")

## Ease of Testing

One of the challenges of testing code is dealing with dependencies. By using DI, it becomes easier to test code by injecting fake or mock dependencies into a class.

Let’s look at our usual `RigidComputer` :

```kotlin
class RigidComputer() {
    private val processor = Processor()
    fun compute() {
        processor.compute()
    }
}
```

How can we test this? Well, in its current implementation — it’s impossible. We could make our `Processor` a public `lateinit var` and set it during the test. But this would be field dependency injection and it’s not needed here. We’ll cover field dependency injection in the next article.

In our case, it would be best to use our `FlexibleComputer` as usual:

```kotlin
@Test
fun `testComputer_withValidParams_givesProperResult`() {
    val fakeProcessor: IProcessor  = FakeProcessor()
    val computer = FlexibleComputer(fakeProcessor)
    val result = computer.compute() //in case FlexibleComputer returns something
}
```

In this example, we’re testing the `FlexibleComputer` class by injecting a `FakeProcessor` object. Here we can configure the `FakeProcessor` how we like. By providing `FlexibleComputer` with a dependency that we can configure ourselves — we can simulate and test any behavior that we like.

## Recap

Dependency injection is a powerful technique for managing dependencies in software applications. By using DI, we can improve code reusability, as well as make refactoring and testing easier. Hopefully, this helped you understand some of the core concepts of DI which will be necessary for my next articles. In the next article, we’ll look into DI types, manual dependency injection, and best practices for DI.

In the [next article](https://devblogs.dashwave.io/ultimate-guide-to-dependency-injection-in-android-part-2-manual-di-and-practice), we’ll cover manual dependency injection, field injection, and best practices when using DI.

Resources:

* [Android training on dependency](https://developer.android.com/training/dependency-injection)
    
* [What is dependency injection](https://stackoverflow.com/questions/130794/what-is-dependency-injection)
    
* [A quick intro on dependency injection](https://www.freecodecamp.org/news/a-quick-intro-to-dependency-injection-what-it-is-and-when-to-use-it-7578c84fa88f/)
    

### Thanks for reading! If you found this post valuable, please recommend it (the little handclap) so it can reach others.
