Design a site like this with WordPress.com

Dagger 2 – Dependency Injection on Android : Part 1

Comments

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:

  • Reusability of ExternalObject. You can pass in different implementations of CommunicationInteface to ExternalObject.
  • Easy testing of ExternalObject. You can pass in test doubles to test your different scenarios. For example, you might create a test double of CommunicationInteface  called FakeCommunicationInteface  and configure it for different tests.

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 big apps, taking all the dependencies and connecting them correctly can require a large amount of boilerplate code. In a multi-layered architecture, in order to create an object for a top layer, you have to provide all the dependencies of the layers below it. As a concrete example, to build a real car you might need an engine, a transmission, a chassis, and other parts; and an engine in turn needs cylinders and spark plugs.
  • When you’re not able to construct dependencies before passing them in — for example when using lazy initializations or scoping objects to flows of your app — you need to write and maintain a custom container (or graph of dependencies) that manages the lifetimes of your dependencies in memory.

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:

Published by karandeepmalik

Leave a comment

Empty

Translate

Технологии

Google Переводчик

Переводчик

S

M

T

W

T

F

S

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

Post Categories

Empty