Android Dependency Injection [Dagger2 Example]

Dependency injection might sound like a very complex programming concept but is indeed very simple. In fact most programmers regularly use dependency injection without even realizing it. Dependency injection is basically providing the dependencies that an object needs instead of forcing it to construct them on its own. To simplify it let us suppose you have a House class which internally uses Wall and Pillar objects as shown below. Here Wall and Pillar are the dependencies for the House class.

public class House {

    Wall wall;

    Pillar pillar;

    public House() {
        wall = new Wall();
        pillar = new Pillar();
    }
    
}

Now as seen we instantiate both Wall and Pillar in the constructor. This will work fine but is against standard coding practice of separation of responsibility and reduces reusabilty and testabilty of your code. So instead you can pass instances of Wall and Pillar objects over the constructor as shown below. This is simple Dependency Injection. Here you are just passing the instances from outside the House class rather than creating them inside. You can also pass it using a setter method.

Now many of you must already be using dependency injection using constructor or setter methods in your Android applications. Then why should we use Dagger2 ? Most importantly it removes a lot of Boilerplate code from your application. It generates this code at compile time using annotation processing improving your code’s readibility and testability.

In order to demonstrate Dagger2  we will develop an Android application step by step. For ease of understanding we will use the same House example shown above

Please note that in this example we are not using Android specific dagger library because the aim of this tutorial is to understand dependency injection and Dagger2 in general.

Gradle dependencies

  • As with any Android library before beginning we need to add the following dependencies in our build.gradle.
    implementation 'com.google.dagger:dagger:2.22.1'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
  • As already mentioned we will use the same House example but with Dagger2 dependency injections. Following is our House class with its two dependencies Pillar and Wall
    public class House {
    
        Pillar pillar;
    
        Wall wall;
    
        public House(){
            Log.d(TAG,"House initialized");
        }
    
        public void startBuilding(){
            Log.d(TAG, "started building...");
        }
    
    }

    Following is Pillar class

    public class Pillar {
    
        public Pillar(){
            Log.d(TAG,"Pillar Initialized...");
        }
    
    }

    Following is the Wall class

    public class Wall {
    
        public Wall(){
            Log.d(TAG,"Wall initialized...");
        }
    }

@Inject

javax.inject.Inject annotation is the most widely used Dagger annotation. It can be used along with member fields, constructors and even methods. It is used to identify both objects requesting dependency injection and also constructors which are used to fulfill that dependency.

Constructor injection
@Inject annotation on constructor of a class enables Dagger to use that constructor for creating an instance of that class if there is dependency injection requested by that particular class object. Basically adding @Inject on to a constructor helps Dagger2 to satisfy dependencies of that object. Also if constructors annotated with @Inject has any parameters then it implies that injection is requested for those parameters and Dagger needs to fulfill it.

In the context of this example we add @Inject annotation to constructors of all three of our classes to ensure Dagger2 knows how to create an instance if there is dependency injection requested for any of them. Code snippets below is after adding annotation on the constructors.

public class House {

    Pillar pillar;

    Wall wall;

    @Inject
    public House(){
        Log.d(TAG,"House initialized");
    }

    public void startBuilding(){
        Log.d(TAG, "started building...");
    }

}

Constructor annotated in Pillar.java

public class Pillar {

    @Inject
    public Pillar(){
        Log.d(TAG,"Pillar Initialized...");
    }
}

Similarly Wall.java

public class Wall {

    @Inject
    public Wall(){
        Log.d(TAG,"Wall initialized...");
    }
}


Field Injection

@Inject annotation on class member fields denotes that dependency injection is requested for those fields. It also means we need to provide Dagger2 with a way to fulfill the requested injection. This could be done either by constructor injection or using Modules(which will be discussed later).

For this example we add @Inject annotation to both the member fields Wall and Pillar of House.java. This informs Dagger2 that injection is requested for both objects. As constructors of both the objects are already annotated, Dagger can instantiate them via that constructor and satisfy the dependency

public class House {

    @Inject
    Pillar pillar;

    @Inject
    Wall wall;

    @Inject
    public House(){
        Log.d(TAG,"House initialized");
    }

    public void startBuilding(){
        Log.d(TAG, "started building...");
    }

}

Method Injection
Apart from fields and constructors @Inject annotation can also be used on a method. It denotes that dependency injection is requested for the parameters of that method and similar to Field injection we need to provide Dagger2 with a way to satisfy the requested injection. Important thing to note here is that method annotated with @Inject is invoked directly by Dagger2.

So far we are not using @Inject annotation on method in this example.

Note that dependency injection in a class happens in a sequence i.e. First the constructor annotated with @Inject is invoked and its parameters are injected then field dependencies of the class are injected followed of dependency injection of parameters of methods annotated with Inject. But for this sequence to start constructor of the class needs to be annotated.

@Component

Component is used to initiate dependency injection. They start the dependency graph by informing Dagger2 which class is first requesting its dependencies to be injected via Dagger2. Apart from this they also help Dagger in addressing the dependencies by bundling all the dependency providers available for this instance. Essentially Component is required to connect dependency providers with the classes requesting dependency injection. Suprisingly, considering all the work they do they have a very simple implementation. You just need to define a interface with @Component annotation. Dagger2 generates a compile time implementation for this interface thereby removing all the boiler-plate code that you could have written.

If all of this sounds very complicated to you don’t panic. Just continue with the tutorial. Dagger2 is very simple once you get a hang of it

In this example we define a component MainComponent.java with a single method inject(). This kind of methods are known as member-injection method which take in a single parameter representing the class which needs its dependencies to be injected. In this case that class is MainActivity.java.

@Component
public interface MainComponent {

    /*
    * Member injection method to inject dependencies in MainActivity
    * */
    void inject(MainActivity mainActivity);

}

Now rebuild your Android application, this will generate the implementation for your interface by appending “Dagger” to your interface name. In this example generated file name will be DaggerMainComponent.java

  • Now in your MainActivity.java you can get instance of your interface MainComponent using the auto-generated DaggerMainComponent as shown below. Then you use this instance and call inject method passing in your current MainActivity instance as parameter. This, as already explained, will initiate dependency injection in your activity.
    public class MainActivity extends AppCompatActivity {
    
        /*
        * Instance variable with @Inject annotation denoting this dependency needs 
        * to be injected via Dagger
        * */
        @Inject
        House house;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            /*
            * Creating instance of interface MainComponent using auto-generated  
            * implementation DaggerMainComponent
            * */
            MainComponent mainComponent = DaggerMainComponent.builder().build();
            
            /*
            * This call initiates dependency injection in your class MainActivity*/
            mainComponent.inject(this);
    
        }
    }
    
  • Dagger2 now searches if any member fields or methods in your activity are requesting dependency injection. As noted House object is the only instance variable requesting for dependency injection and its constructor is already annotated with @Inject. Therefore Dagger instantiates House object immediately.
  • But House.java itself has two dependencies Wall and Pillar requesting injection and both theses objects also have constructors annotated with @Inject. Therefore soon after instantiating House object Dagger instantiates Wall and Pillar objects.
  • This creates a Dependency graph wherein your MainActivity had dependency on House object and internally your House object has dependency on Wall and Pillar objects. As of now Wall and Pillar objects don’t have any dependency but if that would have been the case our graph would have grown. Following is the visual representation of dependency graph
    Wall           Pillar
      |              |
      |______________|
              |
            House
  • If you would have noted we have added logs in all our constructors therefore lets quickly run our application and check if things are working as intended. We have pasted the screenshot of the logs below.dependency_inject
  • As seen in the screenshot first you House class is instantiated followed Pillar and Wall objects. All three dependencies we injected via Dagger2 with the help of @Inject annotation. I hope now you have a better understanding of how Dagger2 helps us in injecting dependencies without manually instantiating a single object.

@Module

So far we learnt that to inject a object dependency via Dagger2 we need to annotate the object’s constructor with @Inject. But that is not always possible like in the cases listed below

  1.  Interface dependency
  2.  Dependency from Third-party libraries
  3.  Object with constructors parameters which need to be configured at runtime .

This is where Modules come to our rescue. Modules are classes that help Dagger fulfill the dependencies requested, which cannot be fulfilled by constructor injections. A Module class is defined with a @Module annotation and has methods which help Dagger in addressing object dependencies matching their return type.

  • To further understand modules, let us add two more objects to our application- SteelRod.java and Cement.java. As you will note we don’t add @Inject annotation on the constructors for these classes. Module will help us in addressing dependencies of these objects
    public class SteelRod {
    
        public SteelRod(){
             Log.d(TAG, "SteelRod initliazed...");
        }
    }
    
    public class Cement {
    
        public Cement(){
            Log.d(TAG, "Cement initialized");
        }
    }
    
  • These two objects will be dependencies(fields) of our Pillar object. We add @Inject annotation on them to request for dependency injection.

    public class Pillar {
    
        @Inject
        Cement cement;
    
        @Inject
        SteelRod steelRod;
    
        public Pillar(){
            Log.d(TAG,"Pillar Initialized...");
        }
    }
    
  • Instead of adding annotation to the constructor of these two classes we define a new Module class as shown below which will provide these two dependencies. Note the @Provides annotation on both the methods . It informs Dagger that the respective methods can be used to address dependencies of objects matching its return type
    /*
    * Adding @Module annotation is mandatory while
    * defining your module
    * */
    @Module
    public class MainModule {
    
        /*
        * This method enables Dagger to fulfill dependency
        * of the Cement object
        * */
    
        @Provides
        public static Cement provideCement(){
            return new Cement();
        }
    
        /*
        * This method enables Dagger to fulfill dependency
        * of the SteelRod object
         * */
        @Provides
        public static SteelRod provideSteelRod(){
            return new SteelRod();
        }
    
    }
    
  • While defining Modules you need to note that Dagger matches return type of your method with requested dependency not the name of the method. Which means provideSteelRod() and provideCement() would satisfy the dependency of their respective objects even if they had different name as long as the return type matches.
  • Now this module needs to be attached to the Component. Remember how we mentioned that component helps Dagger in addressing dependencies. This is done with the help of Module. Once attached to a Component all the objects provided by the particular module will be available for Component’s instance.
  • Modules are attached to components by simply adding the module class name along with the @Component annotation as shown below. You can add multiple Modules to a single component.
    /*
    * Modules are added to the component by listing them
    * with the @Component annotation
    * */
    @Component (modules = MainModule.class)
    public interface MainComponent {
    
        /*
        * Member injection method to inject dependencies in MainActivity
        * */
        void inject(MainActivity mainActivity);
    
    }
  • On a side note, our dependency graph has now grown as Pillar object has two more dependencies SteelRod and Cement.
             SteelRod     Cement
                |           |
                |___________|
                      |
Wall               Pillar
  |                   |
  |___________________|
            |
          House


  • Now run the application and as seen in the screenshot below constructors of Cement and SteelRod classes are also invoked. Also note the sequence in which the classes are instantiated. As Cement and SteelRod are field dependencies of Pillar.java they are instantiated soon after Pillar.java and before Wall.java

Passing Runtime parameters with Modules

  • Many constructors require parameters at runtime for instantiation. With Dagger you can even pass these values with help of Modules.
  • In this example let us suppose that our SteelRod object needs integer parameter at runtime in constructor . With the help of Module you have to pass this value from Activity. Following is how the new constructor of SteelRod looks like
    public class SteelRod {
    
        public SteelRod(int rodDia) {
            Log.d(TAG, "SteelRod initliazed with " + rodDia);
        }
    }
  • First you need to add Constructor to your Module and convert its methods from static to non static. Previously our methods were not dependent on a instance of Module hence we had declared them static.
  • Declare a instance variable rodDia in the Module and initialize it with the value passed in constructor as shown below. We pass the same instance variable while calling the constructor of SteelRod object
    /*
    * Adding @Module annotation is mandatory while
    * defining your module
    * */
    @Module
    public class MainModule {
    
        private int rodDia;
    
        /*
        * Constructor for the module which takes in a integer parameter to
        * configure the local instance variable
        * */
    
        public MainModule(int rodDia) {
            this.rodDia = rodDia;
        }
    
        /*
        * This method enables Dagger to provide dependency
        * of the Cement object
        * */
    
        @Provides
        public Cement provideCement(){
            return new Cement();
        }
    
        /*
        * This method enables Dagger to provide dependency
        * of the SteelRod object
         * */
        @Provides
        public SteelRod provideSteelRod(){
            return new SteelRod(rodDia);
        }
    
    }
    
  • Now the question arises how to pass value to the Module constructor because till now Module instantiation was taken care by Dagger itself. But Dagger also provides a way to pass the module instance while building the component object as shown in the code snippet below.
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        int rodDia = 10;
        /*
        * Creating instance of interface MainComponent using auto-generated
        * implementation DaggerMainComponent
        * */
        MainComponent mainComponent = DaggerMainComponent.builder()
                .mainModule(new MainModule(rodDia))
                .build();
    
        /*
        * This call initiates dependency injection in your class MainActivity*/
        mainComponent.inject(this);
    
    }
  • Now let us run the application and check the logs. As seen in the screenshot below we are a able to pass runtime parameters to the constructor using module.

Lets wrap up for now

This finishes us with the basics of Dagger2. There is still a lot more we can do with Dagger but we tried to cover all the essentials in this tutorial. If you have any questions feel free to ask them in the comments below. Also checkout more amazing Android tutorials here. We will soon be back with more amazing Dagger tutorials

via GIPHY