In this article first we will understand what exactly is dependency injection in android and after that we will learn applying the concept of dependency injection in our Android projects. A dependency injection is one of the most important Design patterns that you must understand As a Software Engineer. Dependency injection is a technique that is widely used in software engineering to eliminate coupling of code. That is why the concept is also used while building Android apps.

Ways to do dependency injection in Android:

  • Constructor Injection. By using constructor injection you can pass the dependencies of a class to its constructor.
  • Field Injection (or Setter Injection). Some Android classes such as activities and fragments are instantiated by the system, so you can not do constructor injection.  In such case you can use field injection after that dependencies are instantiated after the class is created.
  • Method Injection:-To use method injection you can use @Inject annotation with method.

Let’s understand about dependencies and dependency injection with this very simple Kotlin code example.

Class Car{
private val engineInstance:Engine
Init
{
engineInstance=Engine()
startCar()
}

private fun startCar()
engineInstance.startEngine()

}

Here, there is a class called car, It has an instance variable called engine Instance of type Engine In the init block , the car constructs a newengine object and assigns that to Engine instance variable and later it calls to the startCar function, We can see Car object uses that instance variable to call the start function of newly created Engine object

So in this example you can see the Car class depends on the Engine class .Car class cannot work without the support of Engine class. Wherever Cars are Engines needs to be there. We cannot reuse Cars without reusing Engines. So we can consider Engine class as a dependency for the Car class. Car class can also be called as the dependent of the Engine class. In this case Cars depend on engines. But Engines may not depend on Cars.

Now let’s have a look at the Engine class.

Class Engine{
private val pistonInstance:Piston
Init
{
pistonInstance=Piston()
startCar()
}

private fun startEngine()

//code to start engine
}

Looks like this Engine class have a dependency called Piston. Engine object cannot survive without the support of the Piston object for this case Engine class becomes the dependent and the Piston class become the dependency. Now, what can you say about Car object? looks like we have just found an indirect dependency called Piston for the Car object. 

I just showed you a very simple Kotlin example about dependencies , In a real world large project you will find many dependencies, We may create some of them, and we may also use third party dependencies. If you are using Retrofit to communicate with a REST API, retrofit instance becomes a dependency. If you are going to use Room, room data base instance becomes a dependency. if dependencies are tightly coupled , It’s very hard to do testing, bug fixing and expanding of the code .We should embrace loosely coupled and highly cohesive dependencies. That’s why we are going to do dependency injection. In industrial level large projects we must pay attention to follow dependency injection architectural pattern in order to avoid unnecessary problems that can arise during the later stages of the project.

how can we write codes to achieve this. If we try to do it manually we will have to deal with complex code logics. If you are a really good , well experienced developer you will be able to somehow do it, but it will take a lot of time and effort. That’s where Dependency Injection Frameworks comes to help us.

Among those dependency injection frameworks dagger 2 is the most successful and most popular framework. Dagger makes our life easier by generating all the codes for us. So let’s understand what are the problems  that we have with this approach.  

The first problem is Car and Engine are  Tightly Coupled as Car is constructing its own engine we cannot or we cannot easily use  different implementation of Engine for Car.  

For Example, we have to create two cars one is  for gas engine and another one is for electric engine then reusing the same Car here is not easy  in the given code because Car is constructing its  own Engine passing a Test Double for Car to test  different use-cases is also not possible. For testing many times we mock objects  that are called Test Double.  So I hope you understood what are  the issues with this approach.  

Now let’s move to a very simple  solution instead of Car to construct its own Engine this time we are passing  the Engine as a constructor parameter.  

Class Car(private val engine:Engine)
{
fun start()
{
engine.start()
}
}

Now, this approach will solve all  the problems that we discussed. Now Car and Engine are not Tightly Coupled as we  are getting the engine from outside the Car.  

We can easily pass different  Engine implementations to Car while constructing a Car. Passing Test Double is also easy now for testing. A simple change solved almost all the issues that we had and  this approach is called Dependency Injection.  The Engine is the dependency to the Car and now  we are injecting dependency via a constructor.  So it is also called Constructor Injection. 

There are two main types of Dependency Injection  

first one that we have already seen is called  Constructor Injection another solution is called Field Injection as you can see here in both  approaches Car is not constructing its own Engine  but the Engine is being injected into the Car. In both examples, we are constructing the Engine manually and hence it is called  Manual Dependency Injection.  

It is an ok solution but  it also has some issues. Right now we have just seen a simple  example but think about bigger projects. For example, Engine may also require  a lot of other Dependencies. So handling all dependencies manually  may end up to a lot of boilerplate code that will be hard to maintain and if you are  creating dependencies manually then you also  need to manage the life cycle of dependencies  in memory that can be a tedious task. To tackle these problems we have automated  dependency injection solutions that can be  categorized two types the first one is Reflection  based solutions that generate the dependency  and runtime and static solution that generates the  code to provide dependency and compile time.  In this course we will learn about Dagger  and Hilt both are the static solution of Dependency Injection as both libraries generate  Dependency Injection code at compile time.  In fact, Hilt is the recent library that is built  over the top of Dagger and now is the recommended  solution by the Android jetpack, and Dagger2  is also maintained by Google and it is also the  recommended DI for Android projects. So dependency Injection enhances code reusability and decouples dependencies. After Dependency Injection the Dependencies are not hidden as implementation details. So it is easy to refactor the dependencies now  and after using Dependency Injection  testing also becomes easier.  So these are the some reason to use  Dependency Injection in your project. 

You may also like...

0 Comments

No Comment.