Introduction
When I started researching about Dependency Injection, I had the very same question "Why dependency injection", when I am happy with what I am doing.One day when I was going through best practice in coding, I happened to see a comment by ROBERT MARTIN which Was
"3 important characteristics of a bad design that should be avoided:
Rigidity - It is hard to change because every change affects too many other parts of the system.
Fragility - When you make a change, unexpected parts of the system break.
Immobility - It is hard to reuse in another application because it cannot be disentangled from the current application."
I went through my code, is my code RIGID? yes, it is.Is my code fragile? yes, it is.is my code immobile? again, yes it is. Since my code is a complete mess, I need to change the whole approach.Further research led me to the term SOLID principles and the clean architecture principles of Uncle Bob (Robert C. Martin).
This article is not about SOLID principle.I will be writing about it in my upcoming articles.In this, I will brief through Solid principle.
There exist 5 most recommended design principles, that you should keep in mind while writing your classes. These design principles are called SOLID.Why do we have to follow this principle?
SOLID are five basic principles which help to create good software architecture. SOLID is an acronym where:-
S stands for SRP (Single responsibility principle
O stands for OCP (Open closed principle)
L stands for LSP (Liskov substitution principle)
I stands for ISP ( Interface segregation principle)
D stands for DIP ( Dependency inversion principle)
In this article, we will be concentrating on "D" the Dependency inversion principle.
Dependency inversion principle
"The Dependency Inversion Principle states that high-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details. Details should depend upon abstractions" This might sound bloated, but it is really easy to understand. This principle allows for decoupling.
Implementing proper dependency inversion in our apps allows to have:
Loose coupling
Easily testable code
Code reusability
As to why it is important, in short: changes are risky, and by depending on a concept instead of on an implementation, you reduce the need for change at call sites.
Effectively, the Dependency Inversion Principle reduces coupling between different pieces of code.
Dependency Injection
Dependency injection can be considered as an implementation of Dependency Inversion Principle.
Dependency Injection (DI) is a design pattern which has been around for awhile, but recently it has become more commonly used in the development of Android applications.Dependency injection is a style of object creation in which objects are created by an external entity or technique whereby one object supplies the dependencies of another object.
We have three types of Dependency injection
1) Constructor Injection
2) Setter/Getter Injection
3) Interface Injection
Depending on the technologies, some are supported and others are not.In this article, we will be concentrating on Dependency injection implementation in Android.
When we write code, we will often find that our classes will have dependencies on other classes. So class X might need to have a reference or dependency to class Y. To make things a little clearer let’s look at the case where we have a Car class that needs to use an Engine class.
public class Car {
private Engine engine;
public Car() {
engine = new PetrolEngine();
}
}
This code works fine, but the downside is that the coupling between the Car and the Engine is high. The Car class creates the new Engine object itself and so it has to know exactly what Engine it needs, in this case, a PetrolEngine. Maybe we could do a little better and reduce the coupling, so let’s look at a different way of creating this Car class.
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
}
it’s quite straightforward to start using it in our code. We simply look at what dependencies are needed and pass them via a constructor or a method call.
If it is this simple then why we need libraries or a DI frameworks?
An application is never this simple.As the application becomes complex things can start getting a little messy.
In our example of a Car that has a dependency on an Engine, imagine that the engine also has its own set of dependencies. Let’s say it needs a crankshaft, pistons, block, and head. If we follow DI principles we will pass these dependencies into the Engine class, that’s not so bad, we just need to create these objects first and pass them into the Engine object when we create that. Finally, we pass the Engine to the Car.
Next, let’s make our example a little more complicated. If we imagine trying to create classes for each part of an engine we can see that we would soon end up with possibly hundreds of classes with a complicated tree (more accurately it is a graph) structure of dependencies.
From our example, we can see that implementing DI on our own can lead to creating a lot of boilerplate code and the more complex your dependencies the more boilerplate you will have to write. so to solve this issue we can use DI frameworks.These frameworks make it simple to configure dependencies and in some cases generate factory and builder classes for creating objects, making it very straightforward to create complex dependencies that are easily managed.
Working of Dependency injection libraries
Most dependency injectors rely on reflection to create and inject dependencies.Reflection is awesome but is very slow and time-consuming. Also, it performs dependency resolution at runtime which leads unexpected errors and crashes the application.
On the other hand, some injectors use a Pre-compiler that creates all the classes (object graph) it needs to work using Annotation Processor. An Annotation Processor is a way to read the compiled files during build time to generate source code. So it performs the dependency resolution before the application runs and avoids unexpected errors.
We have a lot of dependency injection libraries for Android, let us look into some of them
RoboGuice
Roboguice injects code at runtime using reflection. Using RoboGuice, we can inject Views, Drawable, Resources, System Service, or any other objects. Because of using reflection it is slow.
Traditional code:
TextView mTvName = (TextView) findViewById(R.id.tv_name);
Roboguice injects view code:
@InjectView(R.id.tv_name ) TextView mTvName;
ButterKnife
Butterknife uses annotation processing to generate modified Java classes based on annotations. It allows annotating the views and OnClickListeners. Butterknife also includes findById methods which simplify code that still has to find views on a View, Activity, or Dialog.
Traditional code:
TextView mTvName = (TextView) findViewById(R.id.tv_name );
Butterknife code:
@BindView(R.id.tv_name) TextView mTvName;
Traditional code:
button.setOnClickListener(new OnClickListener(){
@override
public void onClick(View view){
}
})
Butterknife code:
@OnClick(R.id.button)
Dagger
Dagger is designed for low-end devices. It is based on the Java Specification Request (JSR) 330. It uses code generation and is based on annotations. The generated code is very relatively easy to read and debug.
Dagger 2 uses the following annotations:
@Module and @Provides: define classes and methods which provide dependencies
@Inject: request dependencies. Can be used on a constructor, a field, or a method
@Component: enable selected modules and used for performing dependency injection.
Conclusion
Dependency injection helps to simplify the code and also provide an adaptive environment that’s useful for testing and other configuration changes.In the upcomming post, I will be covering implementation of dagger2.