Rachit
Rachit
Nov 5, 2017 · 6 min read

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.

Setup

We will use IntelliJ CE with a gradle based project.

Add following lines to build.gradle

  1. The plugin kotlin-kapt (Kotlin Annotation Processor) is required for processing the kotlin annotations.
  2. The second dependency is the dagger library required for annotations and other internal functionalities.
  3. 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.

Providing dependencies

@Module

  • 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 @Provides.
  • A Kotlin class annotated with @Module may contain a companion object for static functions. Don’t forget to annotate the companion object with @Module too.
  • Thus a module can be written as following —
ComputerModule.kt
  • 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.

@Provides

  • 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

static @Provides

  • 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!
  • Using static provider 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 object of the class to make them static and annotate the function with @JvmStatic for instance-less access.

@Component

  • It is applied to interfaces
  • It specifies all the modules available, and thus also acts as a central provider/factory for dependencies
Creating a component
  • The above code block contains the implementation of our Computer component, which depends on Computer module, and contains a provision function getComputer()

@Named

  • 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 provider functions return same type object.
  • Ex. In ComputerModule.kt above, we have two functions which return VideoRam so we annotate one function with @Named("large") which can be requested using the same named annotation as you can see in providesMotherBoardWithLargeVideoRam function arguments.
  • We may also create custom qualified annotations —
Example of Custom qualified annotation

@Binds

  • It is applied to abstract functions.
  • It is used to create/provide concrete implementations of interfaces(contracts/presenters).
  • Functions annotated with @Binds and @Provides can’t be part of same class(read module), unless it is static @Provides functions.
  • In case of static functions with @Provides, function annotated with @Binds can be part of the same module.
Example of `@Binds`

Injecting/Requesting Dependencies

@Inject

  • We can mark fields or constructors with @Inject, so dagger can magically create and inject the field or type.
  • When marking a field with @Inject dagger will create an instance of the type of field and initialize our field.
  • In Kotlin, the @Inject field has to be public and late initialized with lateinit var.
  • 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 Dagger
  • The DaggerComputerComponent is generated by a dagger and contains builder functions to initialize modules which can’t be magically created by dagger. Ex. In code above ComputerModule can’t be instantiated as it requires memorySize and vMemorySize as inputs. So it can be set using a builder function of DaggerComputerComponent
  • Since our component class had a provision function getComputer, using it we can directly get an instance of the computer class.
  • Also, The ComputerApp class requests field injection using @Inject which will also be magically provided by dagger and will be available at runtime.

Scopes

A scope defines how long our dependency instances will live.

@Singleton

  • 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.

@Reusable

  • 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 @Reusable annotation live as long as the outer component lives.
  • The subcomponents may cache the reusable dependencies from parent components.

Custom scopes

  • 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.

Reusing dependencies

Module dependencies

  • 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 includes element. Don’t forget to annotate the other class too with @Module.

@SubComponent

  • 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 @Component we declare the subcomponents as
A subcomponent declaration
  • 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 subcomponent element of any module in our parent component.
    It is good to keep/specify the subcomponent in relatable modules.
Binding the subcomponent to parent component
  • 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.

Component dependencies

  • Another way to reuse our dependencies is using component dependency.
  • Our component can depend on other components by using the dependency element of @Component annotation.
Component with Dependencies
  • 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 MiningRigComponent has a dependency on ComputerComponent, and it will have to only Computer dependency.
  • In comparison, The Subcomponent inherits all the dependencies from the parent.

More to explore/study, Map Multibinding, Async/Lazy provider functions.

Sample project


That’s all folks. We continue with Dagger in Android in Part 2 of the series

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade