Dependency injection means giving an object its instance variables — James Shore
To sum up what it is
- Providing dependencies to classes instead of class itself creating dependencies
- A central/structured way of providing dependent objects/services.
- Helps in Unit testing
- Helps in writing reusable-modular-interchangeable code.
If you want to read more about DI, Follow the link
Dagger 2: Is the library which helps us in managing and providing dependencies for our app in a very efficient way. It builds an object graph of dependencies and creates/provides dependencies wherever they are required in the Object graph.
We will use IntelliJ CE with a gradle based project.
Add following lines to build.gradle
- The plugin kotlin-kapt (Kotlin Annotation Processor) is required for processing the kotlin annotations.
- The second dependency is the dagger library required for annotations and other internal functionalities.
- The third dependency is our dagger compiler, which is required to process our dagger based annotations and generate the code.
Dagger(2) — The Annotations
Dagger can magically create instances of dependencies and use them in the object graph where required. Still, there are scenarios, where we need to instruct/help the library in creating the dependencies, and this is where the annotations come helpful. Let’s read more about them.
- It is applied to classes/abstract classes.
- It supplies/contributes objects to the dagger object graph.
- It can have dependencies on other modules.
- It contains functions annotated with
- A Kotlin class annotated with
@Modulemay contain a
staticfunctions. Don’t forget to annotate the
- Thus a module can be written as following —
- In the above code-block we have declared a computer module, which provides the additional dependencies for creating a computer. We also have
@Namedfunction for providing graphics ram as the module provides two different types of graphics ram.
- Also, We declare our power supply provider function in a companion object for direct access.
- This way of declaring static functions using companion objects has a small drawback. An extra class for the companion object will be generated by the dagger.
- It is applied to functions
- It creates/specifies how objects will be provided by the module.
- It should be used to provide complex or external dependencies.
- For further reference let’s call these functions provider functions
- Everytime to provide a dependency, an instance of our module has to be created. A lot of modules = A lot of instances = A lot more time and performance hit!
staticprovider functions to provide dependencies, we get better performance, as an instance of module is not required to access this function.
- In Kotlin, we declare functions inside
companion objectof the class to make them static and annotate the function with
@JvmStaticfor instance-less access.
- It is applied to interfaces
- It specifies all the modules available, and thus also acts as a central provider/factory for dependencies
- The above code block contains the implementation of our Computer component, which depends on Computer module, and contains a provision function
- It is a qualifier annotation provided by dagger.
- It is applied to functions.
- It is helpful when we are creating dependencies of the same type, or when our
providerfunctions return same type object.
- Ex. In
ComputerModule.ktabove, we have two functions which return
VideoRamso we annotate one function with
@Named("large")which can be requested using the same named annotation as you can see in
- We may also create custom qualified annotations —
- It is applied to abstract functions.
- It is used to create/provide concrete implementations of interfaces(contracts/presenters).
- Functions annotated with
@Providescan’t be part of same class(read module), unless it is
- In case of
static functions with @Provides, function annotated with
@Bindscan be part of the same module.
- We can mark fields or constructors with
@Inject, so dagger can magically create and inject the field or type.
- When marking a field with
@Injectdagger will create an instance of the type of field and initialize our field.
- In Kotlin, the
@Injectfield has to be
publicand late initialized with
- Classes/Types with no-arg constructors will be created by dagger, but in case the class/type depends on other class we need to mark our constructor with
@Inject, and dagger will figure out the dependencies, and provide the instance of our type wherever required.
Wiring it all together —
So, How do we provide all these dependencies to our objects?
- In our main function, we call
DaggerComputerComponent.builder().build()to get an instance of our component class. Note we named our component class
ComputerComponent, Dagger generate a new class after building the object graph, by prefixing our class with
DaggerComputerComponentis generated by a dagger and contains builder functions to initialize modules which can’t be magically created by dagger. Ex. In code above
ComputerModulecan’t be instantiated as it requires
vMemorySizeas inputs. So it can be set using a builder function of
- Since our component class had a provision function getComputer, using it we can directly get an instance of the computer class.
- Also, The
ComputerAppclass requests field injection using
@Injectwhich will also be magically provided by dagger and will be available at runtime.
A scope defines how long our dependency instances will live.
- Singleton as the name implies, the instance of dependency remains same for the lifecycle of the application, and everyone who requests the dependency will get the same instance.
- It is used if we want an object to live for the application lifecycle. Ex. some auth helper containing auth token, or networking library instance.
- We shouldn’t have a lot of application singletons, as creating higher number dependency instances might slow our application, as well as the longer the dependency instance lives, the higher memory footprint it puts on our application.
- It is applied to provider function where we want our dependency instances to be reused between components, but at the same time they should be easily available for garbage collection.
- Or, when you wish to tell dagger that it may create a new instance of dependency, but at the same time, it is allowed to use an already existing one.
- The dependencies with
@Reusableannotation live as long as the outer component lives.
- The subcomponents may cache the reusable dependencies from parent components.
- Custom scopes are a great way to manage our dependencies instance.
- By default, If we don’t specify any scopes, everyone who requests an instance of our dependency, will get a new instance. That’s not so good for mobile platforms.
- Custom scopes are to be bound with component lifecycles, and all provided dependencies for that particular scope, live as long as component scope lives.
- If we scope our dependencies, we can limit the number of instances. Ex. for each feature component in our application we may have a different custom scope.
- A module may depend on other modules. Ex. App module depending on WebService module or Database module.
- It’s good to split dependencies into smaller modules and use them with custom scopes. This leads to significant performance improvements.
@Module(includes=arrayOf(SomeOtherModule::class))A module annotation may specify dependency on any other module using
includeselement. Don’t forget to annotate the other class too with
- Subcomponents are the easiest way of accessing dependencies from one component(parent component) to another.
- Subcomponents help us in creating small subgraphs of our application object graph. This helps in limiting the scope of dependencies as well as performance when proving the dependencies.
- Similar to
@Componentwe declare the subcomponents as
- Subcomponents are tightly coupled with their parent components and they automatically inherit all the dependencies from the parent components.
- We add a subcomponent to parent component by specifying the
subcomponentelement of any module in our parent component.
It is good to keep/specify the subcomponent in relatable modules.
- Subcomponents help us in the better encapsulation of our codebase/between various modules or features of our app.
- Subcomponents aren’t allowed to have the same scope as their parent components.
- Another way to reuse our dependencies is using component dependency.
- Our component can depend on other components by using the dependency element of
- In case of component dependencies only public dependencies which are provided by the BaseComponent provision functions are accessible to the dependent component. Ex. in above code block
MiningRigComponenthas a dependency on
ComputerComponent, and it will have to only
- In comparison, The
Subcomponentinherits all the dependencies from the parent.
More to explore/study, Map Multibinding, Async/Lazy provider functions.
That’s all folks. We continue with Dagger in Android in Part 2 of the series