Dependency Injection — Part 1

Devesh Shetty
AndroidPub
Published in
2 min readOct 22, 2018

We frequently encounter code that looks like this:

open class ExampleClass {

private var dependencyOne = DependencyOne()
private lateinit var utilDependency: UtilDependency
protected lateinit var dataProvider: DataProvider

protected open fun loadData() {
dataProvider = DataProvider()
utilDependency = UtilDependency(dataProvider)
}
}

In any code, we know one type depends on multiple other types, like ExampleClass depends on dependencyOne, utilDependency, and dataProvider and it resolves these dependencies using a constructor dependencyOne = DependencyOne() or maybe even help resolve dependencies for the type it depends on utilDependency = UtilDependency(dataProvider).

As developers, we often write tests but we don’t want our tests to be querying the database or performing network requests when they are not supposed to. Hence, we end up mocking them out:

class TestableExampleClass : ExampleClass() {

init {
dataProvider = Mockito.mock(DataProvider::class.java)
}

override fun loadData() {
// do nothing
}
}

However, supporting this ability to mock out these dependencies using inheritance leads to even more boilerplate code.

What if I told you there is a way to avoid creating this mess? What if I told you there is a way for us to avoid calling a constructor?

The technique, dependency injection aims to resolve this problem. Using a tool called dependency injector, we can pass dependencies to objects.

@Inject
lateinit var dataProvider: DataProvider

Just annotate the field using @Inject, an injector will take care of injecting the correct dependency. You can also annotate a constructor and perform constructor injection:

class ExampleClass @Inject constructor(
private val dependencyOne: DependencyOne,
private val dataProvider: DataProvider,
private val utilDependency: UtilDependency
)

If there exist dependencies without which the object cannot function properly, it is advisable to use constructor injection. This assures that the constructed object has fulfilled all it’s dependencies and is ready to be used.

What about testing?

@Before
fun setUp() {
dependencyOne = mock()
dataProvider = mock()
utilDependency = mock()

exampleClass = spy(
ExampleClass(
dependencyOne,
dataProvider,
utilDependency
)
)
}

We are correctly supplying mocks without any additional boilerplate code. What have we achieved with this?

  • Construction of dependencies is abstracted out of the class which depends on them.
  • Assurance that none of dependencies are unsatisfied.
  • No additional boilerplate code required for testing.

--

--