Design a site like this with WordPress.com
Dagger 2 – Dependency Injection on Android : Part 1
Date: 4th Jul 2021
Author: karandeepmalik
In this tutorial I will try my best to simplify the mechanics of using Dagger 2. If you are working on Android there is a high possibility that you have heard about Dagger 2. There are other libraries that do this job like Juice, Dagger 1 from Square , Coin and now Hilt.
Dependency Injection
The discussion about Dagger 2 is incomplete without first discussing Dependency Injection and the need for it. So, what is it exactly?
From Wikipedia
In software engineering, dependency injection is a technique in which an object receives other objects that it depends on.
These other objects are called dependencies. In the typical “using” relationship the receiving object is called client and the passed (that is, “injected”) object is called a service. The code that passes the service to the client is called the injector. Instead of the client specifying which service it will use, the injector tells the client what service to use. The “injection” refers to the passing of a dependency (a service) into the object (a client) that would use it.
Advertisement
Privacy Settings
The service is made a part of the client’s state. Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.
The intent behind dependency injection is to achieve separation of concerns of construction and use of objects. This can increase readability and code reuse.
Dependency injection is one form of the broader technique of inversion of control.A client who wants to call some services should not have to know how to construct those services. Instead, the client delegates the responsibility of providing its services to external code (the injector). The client is not allowed to call the injector code; it is the injector that constructs the services. The injector then injects (passes) the services into the client which might already exist or may also be constructed by the injector. The client then uses the services. This means the client does not need to know about the injector, how to construct the services, or even which actual services it is using. The client only needs to know about the intrinsic interfaces of the services because these define how the client may use the services. This separates the responsibility of “use” from the responsibility of “construction”
Let’s say for eg you have written a piece of code (service) in your android app which is responsible for network communication. Now, in your app wherever you want to interact with the outside world you would use this piece of code(service) and, hence, this piece of code (service) is a dependency for all such areas (clients). Now, while writing this piece of code (service) you would have typically written a network communication class and assigned the class the responsibility to perform the communication ( Single Responsibility pattern – one class one responsibility ). The class would typically implement some abstract methods that would be exposed so that other objects can use them (to enable loose coupling, the external objects will depend on an interface containing abstract methods and not the concrete implementation ie class itself ) to avail this functionality. The external objects (clients) would either create an object of this class and use it or they will be passed the network comm class object by some code (dependency injection) through object’s constructor, or assigned to object’s field or passed through object’s method.
interface CommunicationInterface { fun sendData(payload :String) } class NetworkCommImpl : CommunicationInterface { override fun sendData (payload :String) { ………………. } }
Without Dependency Injection,
class ExternalObject { private var networkingInterface = NetworkCommImpl () }
Dependency Injection through Constructor
class ExternalObject (private val netInterface: CommunicationInterface) { }
Dependency Injection through Method
class ExternalObject { private lateinit var networkingInterface : CommunicationInterface fun injectNetInterface(netInterface: CommunicationInterface) { networkingInterface = netInterface } }
Dependency Injection through Field
class ExternalObject { public lateinit var networkingInterface : CommunicationInterface } var networkingImpl = NetworkCommImpl()
Now, let’s talk about the advantages of Dependency Injection.
The benefits of this DI-based approach are:
In the previous example, we created, provided, and managed the dependencies of the different classes ourselves, without relying on a library. This is called dependency injection by hand, or manual dependency injection. In the CommunicationInteface example, there was only one dependency, but more dependencies and classes can make manual injection of dependencies more tedious.
Advertisement
Privacy Settings
Manual dependency injection also presents several problems:
For automated DI, before Dagger 2, Dagger 1 was used and Dagger 1 used java reflection which resulted in dependency resolution at runtime. It had two disadvantages first runtime resolution resulted in dependency graph creation at run time through reflection which slowed the process. Secondly, if there was an error in injection you would only know about it at runtime.
To get rid of these two disadvantages of Dagger 1, Google forked off the original square Dagger 1 and made modification to make Dependency graphs at compile time. This helped in resolving dependencies at compile time and any injection error to be flagged of at compile time itself. In order to make the Dependency Acyclic Graph (DAG) at compile time Dagger 2 uses annotations. Through annotations the developer can give hints about Dependency objects, client methods expecting dependency and also about how to wire up the Dependency objects with Clients.
An example of DAG
Here, Car depends upon Engine and Wheels. The engine depends upon Blocks, Cylinders and Spark Plugs and the wheels depend upon Tyres and Rims for their working.
Dagger 2 Environment Setup
Add the following line to the plugins section of yout module build.gradle file
Add the following to the dependencies section of build.gradle file of your module
implementation 'com.google.dagger:dagger-android:2.15'
implementation 'com.google.dagger:dagger-android-support:2.15' // if you use the support libraries
kapt 'com.google.dagger:dagger-android-processor:2.15'
kapt 'com.google.dagger:dagger-compiler:2.15'
Advertisement
Privacy Settings
I am using Dagger 2.15. Replace 2.15 with the version that you would like to use
Car Example
As we can see the Car example in the above DAG, the Car is made up of Engine and Wheels. Engine is made up of Blocks, Cylinder and Spark Plugs and Wheels are made up of Rim and Tyres
Let’s look at the sample code without DI
Car Class
package com.gtd.daggerapp import android.util.Log class Car (var engine: Engine, var wheels: Wheels){ fun drive() { Log.d(TAG, "driving...") } companion object { private const val TAG = "Car" } }
Engine Class
package com.gtd.daggerapp class Engine (var cylinders:Cylinders, var sparkPlugs: SparkPlugs, var blocks: Blocks )
Blocks Class
package com.gtd.daggerapp class Blocks {}
Cylinders Class
package com.gtd.daggerapp class Cylinders {}
SparkPlugs Class
package com.gtd.daggerapp class SparkPlugs {}
Wheels class
package com.gtd.daggerapp class Wheels (var rims:Rims, var tyres:Tyres)
Rims class
package com.gtd.daggerapp class Rims {}
Tyres Class
package com.gtd.daggerapp class Tyres {}
Dagger 2 Annotations
The annotations used by Dagger to generate the DAG are standardized JSR 330 annotations. They are defined in either package javax.inject.Inject provided by Standard Java or provided by Dagger to get information from user regarding DAG construction.
Advertisement
Privacy Settings
@Inject
The inject annotation is used to tell Dagger that following places like Constructor, member variable or class methods require some dependencies to be injected.
@Inject – Constructor Injection
For constructor injection we modify our existing above Car code as
package com.gtd.daggerapp import android.util.Log import javax.inject.Inject class Car @Inject constructor(var engine: Engine, var wheels: Wheels) { fun drive() { Log.d(TAG, "driving...") } companion object { private const val TAG = "Car" } }
This means that Car object will require dependencies to be injected by Dagger.
Compile this code and you will see a new class generated in the java (generated) folder called Car Factory. If you open it in Android Studio
Now Dagger knows that to create Car the dependencies Engine and Wheels need to be injected Now, add @Inject for both Engine and Wheels to tell Dagger that the dependencies for Engine and Wheels also require to be injected
Engine
Wheels
Now build your code again
In the java (generated) you will now find there are two more files,
EngineFactory and WheelsFactory
Engine Factory
Wheels Factory
Now Engine comprises of Blocks, Cylinders and SparkPlugs and Wheels comprises of Rims and Tyres. But, Blocks, Cylinders SparkPlugs, Rims and Tyres are not composite objects and donot have any dependency. So, ideally we should not be required to specify the @Inject for them (If you read documentation of @Inject it has clearly mentioned that @Inject is optional for public no-argument constructors when no other constructors are present. This enables injectors to invoke default constructors.)
But, Dagger 2 require you to mark even no argument public default constructors with @Inject for code generation. So, after doing the necessary changes our classes become:-
Now build once again, and you will find factories created for Blocks, Cylinders, SparkPlugs, Rims and Tyres.
Now, the Dagger2 knows where to inject dependencies for creating Car.
Constructor injection is the preferred way for injection. The advantage that we had here was that whichever class we wanted to inject dependencies we had the constructor available and we added the @Inject to all these class constructors, But it may be the case that the construction of objects is not done by us and we donot have access to the constructor because the initialization of the object is done by Framework and we are only allowed to have control over some methods ( Eg Activities in Android Framework). In such cases, the Constructor Injection does not work as it is not accessible to add the @Inject notation.
Advertisement
Privacy Settings
@Inject – Field Injection
In cases where you are not allowed to tell Dagger through constructors that the dependency is required to be injected in objects, we can use field injection. This is a common case when you want to inject an object in an Activity.
So, let’s add an EmptyActivity in out project called MainActivity.
Now, the task at hand is to have a member variable Car and tell Dagger to inject its dependencies while activity is created. The challenge in injecting the dependency object Car in Activity is that we don’t have the Activity constructor available to us. Activity is something that the Android framework creates, so we cannot use constructor injection here to tell the Dagger that the Car Object needs to be provided.
So, to our rescue comes field injection. So, instead of using the annotation Inject with constructor we use Inject with the member variable or field.
Now re-build the code again.
The Android Studio now complains “Dagger does not complain injection into private fields”.
So, make car a public field. This is a problem with field injection in Dagger and I will show the reason why the field needs to be made public.
After making Car a public field :-
Rebuild the project again. The errors are gone.
And now when we look under the generated folder again we see that a new file has been generated by Dagger “MainActivity_MembersInjector.java”
Advertisement
Privacy Settings
If you see carefully you will find a function at the last line
public static void injectCar(MainActivity instance, Car car) { instance.car = car; }
In this generated function code, we are assigning instance.car to car. Now instance.car is accessible only when the car is a public field. That is why, Dagger complained that @Inject is not possible with private fields.
@Inject (Method Injection)
In rare usages you may want to inject your dependencies through member functions. By annotating a non-private method with @Inject, we tell Dagger to provide its arguments as dependencies. In combination with constructor injection, this happens automatically after the constructor finished. Without constructor injection, it happens when the members-injection method is called on the component.
Let’s say we have a Remote object and we want to inject a full fledged ready car object in it using method injection
So, we define a new method in car which takes remote and takes as parameter a full fledged ready car object in it. We annotate this method with @Inject
Now, if we go to the generated code, we will find that a new class Car_MembersInjector class in generated code.
It has a function injectEnableRemote is generated.
The code that we have written so far tells the Dagger where to inject dependencies, but we also need to create the dependency objects and then to inject them
Advertisement
Privacy Settings
@Component
Dagger can create a graph of the dependencies in your project that it can use to find out how to get those dependencies when they are needed. To make Dagger do this, you need to create an interface and annotate it with @Component. Dagger creates a container as you would have done with manual dependency injection.
Inside the @Component interface, you can define functions that return instances of the classes you need (for eg. Car). @Component tells Dagger to generate a container with all the dependencies required to satisfy the types it exposes. This is called a Dagger component; it contains a graph that consists of the objects that Dagger knows how to provide and their respective dependencies.
So we add a Component Interface CarComponent first, and expose the type car’s instance by creating a method getCar() . Such a method is called a provision method.
Now lets build the project again.
If you now go and check in the java(generated) folder, you will find a new class generated DaggerCarComponent which is an implementation of interface CarComponent
The interface CarComponent contains a function getCar which returns a Car. This tells Dagger that it needs to create a DAG for Car and based on the DAG it needs to generate all the code required for the construction of the Car and all the objects that it depends upon.
Now, in order to use getCar, create the DaggerAppComponent instance and call getCar() to get the car object
For the field injection code above, in order for Dagger to inject car into MainActivity, it needs to know that MainActivity has to access the graph in order to provide the Car it requires. We use the @Component interface to get objects from the graph by exposing functions with the return type of what we want to get from the graph. In this case, you need to tell Dagger about an object y (MainActivity in this case) that requires a dependency to be injected – Car. For that, you expose a function (fun inject) that takes as a parameter the object that requests injection(MainActivity).
Build project again.
And now you will see that the CarComponent has been generated with injectMainActivity function.
So, now in order to inject the Car instance into MainActivity’s car field, we will use the function inject from CarComponent that we defined above.
Now run and see the car Driving log being shown in logcat.
@Modules
Sometimes, it happens that we have to create objects of an external third party library by injecting third party library objects in it, but we donot have the source code to apply constructor injection or method injection or field injection to inject its dependencies.
Advertisement
Privacy Settings
In such cases, we use Module classes and ourselves write functions to tell Dagger how to create the dependencies of these objects. The functions that we write are annotated with @Provides and the Module class with annotation @Module. The @Module just like @Component adds to the DAG.
Let’s say we have a Wheels object that comes form 3rd party library and Wheels is made up of Rims and Tires that also belong to third party lib.
Now we cannot do any dependency injection technique on the classes to let Dagger automatically construct the wheels object
So, now we will define a class WheelsModule which tells Dagger how to create dependencies of the third party object Wheels. We will create a method provideRims() to tell how to create Rims, a method provideTyres() to tell how to create Tyres , a method provideWheels() to tell how to create Wheels and annotate provideRims() , provideTyres() and provideWheels() with @Provides.
Now when we build project we get three new classes in generated java
WheelsModule_ProvideRimsFactory, WheelsModule_ProvideTyresFactory and WheelsModule_ProvideWheelsFactory
If the provides methods donot depend on any internal variables of the class we can declare them static. It results in better performance because the Dagger doesnot have to create instance of Wheels Module to provide rims, tyres or wheels. So, this is how we make them static in Kotlin
Share this:
Related