Dagger 2 has been around for quite a while now and still holds up as one of the most valuable libraries for the ecosystem, and for good reason.
Android is one of those coding environments where tons of classes interact with each other endlessly. If one class requires an instance of another class for it to function, we call that class dependency. In an ideal world however, we would rather have these classes independent of each other for better reusability and testability. You can’t test a dependent class without also having to test the class it’s dependent on.
With Dependency Injection, these dependencies can be identified at creation time and given directly to the previously dependent classes thus, eliminating any hard dependencies. In other words, classes get their dependencies from an external object. It also means mocks can be used to test these classes independently of the classes they depend on.
In other OTHER words, it turns this:
val car = Car() val electricity = Electricity() val tesla = Tesla(car, electricity) tesla.run()
Into this:
val tesla = DaggerTeslaFactory.getTesla() tesla.run()
Where’d Car and Electricity go, you may ask? Here’s the beginning of the Tesla class
class Tesla @Inject constructor() { @Inject lateinit var car: Car @Inject lateinit var electricity: Electricity
Dagger code is centered around annotations so everything is quick and simple. We’ll explore each one we can use a little bit further down.
Why use Dependency Injection?
Dependency Injection has quite a few great benefits:
- Better code reusability and testability
- Reduces boilerplate code
- Promotes logical abstraction of code
- Loose coupling
Add the Dependencies
Stick this in your app/build.gradle file.
kapt { generateStubs = true } dependencies { kapt 'com.google.dagger:dagger-compiler:2.16' implementation 'com.google.dagger:dagger:2.16' implementation 'com.google.dagger:dagger-android:2.16' implementation 'com.google.dagger:dagger-android-support:2.16' annotationProcessor 'com.google.dagger:dagger-compiler:2.16' annotationProcessor 'com.google.dagger:dagger-android-processor:2.16' }
Dagger Annotations
Annotations play the main part in telling where dependencies are injected.
@Inject
class Tesla @Inject constructor() { @Inject lateinit var car: Car @Inject lateinit var electricity: Electricity
This is the basic annotation that tells us we want a dependency in another class. You can guess injecting Car and Electricity is the dagger-equivalent of having instances of those classes but what’s with injecting the constructor? That just allows the class with that constructor to be constructed by Dagger.
@Singleton
@Singleton class Car @Inject constructor()
The @Singleton annotation just tells dagger to create and maintain a single instance of that class. That means if Car is needed anytime, the same instance will be passed on.
@Module and @Provide
@Module class TeslaModule { @Provides fun provideCar(): Car { return Car() } @Provides fun provideElectricity(): Electricity { return Electricity() } }
Use these annotations together to make dependencies available in our main code.
@Component
@Singleton @Component(modules = [TeslaModule::class]) public interface TeslaComponent { fun buildTesla(): Tesla }
A component makes use of Modules we create to generate classes which have their dependencies injected. In this case, our TeslaFactory component will generate Teslas and handle the injection of Car and Electricity objects.
And the final step, to access the component in your main code:
val component = DaggerTeslaComponent.builder() .teslaModule(TeslaModule()) .build()
And there you have it! The fundamental steps into learning Dagger 2.