
Why Dagger 2?
Problem 1:
Lets imagine we are making a new project so start importing our base classes, retrofit singleton, database singleton classes in our project and then start on the modules we are given. Same even when working on existing project with new dependencies.
Now basic singleton pattern looks like,
RetrofitClient.instance(ApiConstants.BASE_URL);
and we are happy with our network client and we keep making calls with it, we make our module and release the build and then to the next module,
Again, we take a new module and reuse the same wherever we need to use the client and it is spread across the project, repeating the same initialization code.
Now suppose we have to introduce a interceptors in our network client so the signature becomes different like,
RetrofitClient.instance(ApiConstants.BASE_URL,interceptors);
Here the problem arises that we have to change it at each and every place refactoring it, even worse if some dependencies needs context so we start passing context everywhere in presenter, interactor, repository everywhere and we end up with a untestable code, thereby making architecture useless.
Problem 2:
We are great developers so we use Interfaces and rely on interfaces than concrete class reference like, RetrofitClient implements INetworkClient, passing the S.O.L.I.D principles,
Liskov substitution principle, and in all places where we need network client we keep a member variable, and then initalize it with Retrofit instance;
private val networkClient = RetrofitInstance.instance();
and we are happy that all the functionalities are accessed through interface definitions and we can switch the implementation in future if network client changes by just changing the assignment.
Now when we have to change this, we also have to go to each individual files and change the implementation their to new class like
private val networkClient = FuelInstance.instance(ApiConstants.BASE_URL);
Problem 3:
From the above examples, there lies one more common issue that the person who created the module knows the dependency(url, interceptors) and enforces that the other people working in the project should also know how to create before they can use it which needs another message board in basecamp.
Solution:
So how dagger solves this problem.
By Providing dependencies directly from a module, implemetation code is written only once at only one place
public class RetrofitModule(){
@Provides
@Singleton
fun provideNetworkClient(String baseUrl):INetworkClient
{
return RetrofitInstance(baseUrl)
} }
Setting our dependency module to Component
@Component(modules = arrayOf(RetrofitModule::class))
And injecting it in places where you need them
@Inject lateinit var networkClient:INetworkClient
By Changing module, all the Interfaces will be switched to our new implementation without touching any feature.
public class FuelModule(){
@Provides
@Singleton
fun provideNetworkClient(String baseUrl):INetworkClient
{
return FuelInstance(baseUrl)
} }...@Component(modules = arrayOf(FuelModule::class))
And the next person who wants to use your model doesnt need to know how its made, they can directly use it by asking the name of the interface and dagger will inject it for them.
@Inject lateinit var networkClient:INetworkClient
Thereby, less redundant, decoupled and testable code, acheiving S.O.L.I.D Dependency Injection too.
Bonus:
If a presenter or a helper has 3 dependencies and you have to mock a dependency, so instead of making mocks everywhere, we can make a mock of that interface or class and provide it through DI.
If you have a class SnakesLadders with dependencies Snake, Board and Ladder, then if your modules in the component already provide these 3 dependencies, you can ask for a instance of it and dagger will get the dependencies, build the class and inject it for you.
class SnakesLadders @Inject constructor(val snake: Snake, val board: Board, val ladder:Ladder)...@Inject lateinit var game:SnakesLadders
Happy injecting!