Dependency Injection for beginners
About dependency injection.
If a class A uses some other classes B,C then B,C are the dependencies of A. Here B,C can either be created by A or be passed onto A from higher classes. The programming pattern where instances of B,C are passed to A and the responsibility for creating instance of B,C is taken by the caller itself is called dependency injection.
Basic dependency injection mechanism:
Let’s build a simple dependency injection mechanism. Which will help us know the basic steps involving in dependency resolution.
Dogs are love, dogs are life. So let’s build application that lists all dog breeds.
This application uses libraries:
- Gson 2.6.2 (to parse api json)
- OkHttp 3.0.0-RC1 (as http client)
- Okio 1.13.0 (as okhttp dependency)
- Retrofit 2.0.2 (as helper library for network calls)
- Gson Converter for retrofit 2.3.0 (as retrofit response body converter)
In this application we will use DogApi: https://dog.ceo/api/
And get list for all dog breeds making query at path: breeds/list
Where response format is:
In order to convert it to a java object, we build a data class:
Where message type T will be List<String>
Now we will create retrofit interface which will return call for breeds list.
DogApi’s instance is created from retrofit like:
Now let’s build a class which wraps the DogApi’s Call and returns list of dog breeds.
As we can notice, DogApi depends on instances of Gson and OkHttpClient.
Now we have made all necessary settings for the application. Let’s make a system which resolves these dependencies, i.e our dependency injection mechanism.
Dependency Injection framework have dependency builder for each type which are able to create instance for that type.
Lets create a similar interface, where for any dependency of type T, it returns the instance of that type and It may query dependency provider if it depends on other types.
Here Dependency provider is a container of builders.
With these two classes we have made a simple dependency injection mechanism.
To use this in our app,
Lets create a dependency provider’s instance:
DependencyProvider dependencyProvider = new DependencyProvider();
Now, lets add all the dependency builders required in our app i.e builders for DogDataProvider, DogApi, OkHttpClient and Gson :
Now this dependency injection mechanism can be used to resolve the dependencies, like:
DogDataProvider dogDataProvider = dependencyProvider.get(DogDataProvider.class);
This is a naive but foundational mechanism for dependency injection libraries.
It’s a lot of tedious boilerplate code. Applications can be large, it will be hard to maintain and pass the dependencies. For an example:
https://www.youtube.com/watch?v=oK_XtfXPkqw in this talk at around 5:50, it is stated that google had an android app with around 3k lines of code for creating builders for required dependencies. It will be hard to develop and maintain applications like this.
Dependency injection framework like Dagger2 will help remove all these boilerplates and provide simple cleaner api. It resolves complex dependency graphs by wiring them together.
How Dagger 2 works:
Component is the interface for consuming dependencies. It is defined by adding @Component annotation to the interface which consists of methods.
Module is the class with @Module annotation which defines how dependencies are created. It is mostly used resolve dependencies which needs some logic before creation or for those class where it’s not possible to add @Inject annotation.
Our DogDataProvider class has @Inject annotated constructor. Dagger is able to create instance of DogDataProvider once it can resolve its dependencies.
This is the basic dagger api. Here we define how instances of DogApi, Gson and OkHttpClient are created.
What dagger does is that, it generates builder classes for each dependencies and wires them together with generated component. Component is the only way to access dependency graph. Dagger generates implementation for components which is prefixed with “Dagger” on component name.
Inspecting the generated code by Dagger:
AppComponent is implemented as DaggerAppComponent:
This DaggerAppComponent exposes a builder which receives AppModule. Now client uses this builder to create an instance of AppComponent which is the only api exposed to access dependency graph.
During the initialization of DaggerAppComponent, all the instances of DependencyBuilder are created. Here DependencyBuilders are accessed as Providers and the Builders are named with pattern:
DependencyClassName + “_Factory”
In case of dependency created by Module
ModuleName + _DependencyClassName + “Factory”
Now for each declared method in component it creates the method definition which returns the dependency from that provider.
Here dependency factory for DogDataProvider is generated as DogDataProvider_Factory:
Factory for OkHttpClient is:
Similar is for Gson and DogApi dependencies.
This is the basic mechanism for dependency resolution by dagger. Dagger is much more powerful than this demonstration. With the availability of scoped modules and submodules we can create subgraphs with its own lifecycle.
Dependencies are created using dependency builders. Dependency Injection framework creates object graph which stores all the dependency builders. Dagger provides a minimal api to define dependency creation and resolution.