Introduction to Dagger 2, Using Dependency Injection in Android: Part 2

This is the part 2 of the article series. In part 1 we understood the need and advantages of dependency injection. We also got an overview of Dagger2. In this part, we will focus on implementing the DI using Dagger2 in an android app.

Check Part 1 here.

For the sake of this tutorial, we will break the process in steps and analyze each step one by one. Remember Dagger2 requires a concentrated approach. So actively follow the below tutorial, asking a lot of questions. For the project structure, see the project repo mentioned below.

let’s get started.

The GitHub repo for the project:

https://github.com/MindorksOpenSource/android-dagger2-example

First, we have to define the structure of the Android App. The core classes are as follows:

  1. DataManager class will provide access to the data of the application.
  2. DbHelper class will be used by DataManager to access the SQLite database.
  3. SharedPrefsHelper will be used by DataManager to access the SharedPreferences.
  4. Model classes for retrieving the DB table.

Step 1:

Create a project in android studio with an empty Activity and add the following dependencies in app’s build.gradle

Notes: We are using the annotation processor provided by gradle for android. dagger-compiler is the annotation processing repo for generating the dependency graph classes during build time. Other gradle dependencies are provided for the sake of Dagger2.

Step 2:

The data part we will be building first. So, create a model class User.

Notes: This class will bind the DB table data.

Step 3:

Create few custom annotations: ActivityContext, ApplicationContext , DatabaseInfo , PerActivity

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityContext {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationContext {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface DatabaseInfo {
}
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerActivity {
}

Notes:

Why are we creating these annotations and what is @Qualifier and @Scope ?

@Qualifier annotation is provided by javax inject package and is used to qualify the dependency. For example, a class can ask both, an Application Context and an Activity Context. But both these Objects will be of type Context. So, for Dagger2 to figure out which variable is to be provided with what, we have to explicitly specify the identifier for it.

Thus @Qualifier is used to distinguish between objects of the same type but with different instances. In the above code, we have ActivityContext and ApplicationContext so that the Context object being injected can refer to the respectiveContext type. DatabaseInfo is used to provide the database name in the class dependency. Since a String class in being provided as a dependency, it always a good idea to qualify it so that the Dagger can explicitly resolve it.

An alternative to this is @Named annotation provided by Dagger2.@Named itself is annotated with @Qualifier. With @Named we have to provide string identifier for similar class objects and this identifier is used to map the dependency of a class. We will explore the @Named at the end of this example.

@Scope is used to specify the scope in which a dependency object persists. If a class getting dependencies, have members injected with classes annotated with a scope, then each instance of that class asking for dependencies will get its own set of member variables.

Step 4:

Create a DbHelper class that extends the SQLiteOpenHelper. This class will be responsible for all the DB related operations.

Notes: This class introduces few annotation.

  1. @Singleton ensure a single instance of a class globally. So, there will be only one DbHelper class instance for the app and whenever a class asks for DbHelper as a dependency, it will be provided with the same instance that is maintained in the Dagger’s dependency graph.
  2. @Inject on the constructor instructs the Dagger to accumulate all the parameter dependencies when the class is being constructed.
  3. @ApplicationContext Qualifier facilitates DbHelper to get the context object of the application from dagger’s dependency graph
  4. @DatabaseInfo qualifier helps the dagger to distinguish between String and Integer Dependencies from existing same types in the dependency graph.

We will again come back to this discussion when we deal with the module.

Rest of the contents of this class is standard SQLiteOpenHelper . This class creates a user table and inserts/reads it.

Step 5:

Create a SharedPrefsHelper to deal with the shared preferences.

Notes: This class is annotated with @Singleton to make this a singleton class in the dependency graph of Dagger.

This class also gets SharedPreferences dependency through Dagger which is expressed by the @Inject on the constructor.

How is this dependency provided? It is explained later in this example.

Step 6:

Create DataManager class

Notes: This class expresses the dependencies of Application Context, DbHelper and SharedPrefsHelper in the contructor. It provides all the apis to access the data of the application.

Step 7:

Create DemoApplication class that extends android.app.Application

Add this class in AndroidManifest.xml

<application
    ...
    android:name=".DemoApplication"
    ...
</application>

Notes: This class gets DataManager through DI. The interesting part in this class is the ApplicationComponent . Let’s go on with the steps before we cover this point.

Step 8:

Create class MainActivity

Also create activity_main.xml

Now let’s hold on for some time and review the Dagger2.

  1. To provide the dependency for a class we have to create a Module class. This class defines the methods that provide the dependency. A Module class is identified by @Module and the dependency provider method in identified by @Provides .
  2. We then have to create an interface that serves as a connection between a class that expresses the dependency through @Inject and a class that provides the dependency i.e. annotated with @Module .
  3. In order to figure out the dependencies that a Module has to provide, we have to scan all the classes in the graph that needs to be provided with dependencies and then figure out the least number of objects that has to be provided.

Let’s move back to the example steps to understand the above statements.

Step 9:

We have to provide the dependencies expressed in the DemoApplication class. This class needs DataManager and to provide this class we have to provide the dependencies expressed by DataManager , which as mentioned in the constructor are Context, DbHelper, and SharedPrefsHelper . We then move further in the graph.

  1. Context has to be ApplicationContext
  2. DbHelper needs Context, dbName, and version. This does not have any further branching.
  3. SharedPrefsHelper needs SharedPreferences

We now accumulate the superset of all these dependencies, which turn out to be: Context, dbName, version, and SharedPreferences

Now to provide these dependencies we create ApplicationModule

Note: We have annotated this class with @Module and all the methods with @Provides .

Let’s explore this class:

  1. In the constructor, we have passed the Application instance. This instance is used to provide other dependencies.
  2. This class provides all the dependencies that we listed in the above step.

Step 10:

We create a Component which links the DemoApplication dependency and the ApplicationModule

Notes: ApplicationComponent is an interface that is implemented by Dagger2. Using @Component we specify the class to be a Component.

Here we have written a method inject where we pass the DemoApplication instance. Why do we do it?

When the dependencies are provided through field injection i.e. @inject on the member variables, we have to tell the Dagger to scan this class through the implementation of this interface.

This class also provides methods that are used to access the dependencies that exist in the dependency graph.

Step 11:

Similarly, we have to create the module for MainActivity and it’s component. Which follow the same procedures as in the above step.

Note: ActivityComponent specify ApplicationComponent and ActivityModule . ApplicationComponent is added to use the graph that has already been generated in the previous step and do exists because the DemoApplication class persists till the application is running.

@PerActivity is a scope and is used to tell the Dagger that the Context and Activity provided by the ActivityModule will be instantiated each time an Activity is created. So, these objects persist till that activity lives and each activity has its own set of these.

We may ask that the DataManager will then be created with each Activity. But that is not true because we have annotated the DataManager class with @Singleton. Which brings the class in the global scope and thus is accessed whenever a dependency is expressed.

Now let’s revisit the DemoApplication class and MainActivity class. These classes don’t have a constructor and Android System is responsible for instantiating these. To get the dependency we use the OnCreate method as it is called once when they are instantiated.

applicationComponent = DaggerApplicationComponent
                            .builder()
                            .applicationModule(new ApplicationModule(this))
                            .build();
applicationComponent.inject(this);

DaggerApplicationComponent is the generated class by the Dagger, implementing the ApplicationComponent. We provide the ApplicationModule class that is used to construct the dependencies.

We have also called the inject method of applicationComponent and passed the instance of the DemoApplication class. This is done to use it for providing the DataManager.ApplicationComponent instance is retained so as to access all the classes that are available in the dependency graph and is express for access.

public ActivityComponent getActivityComponent() {
if (activityComponent == null) {
activityComponent = DaggerActivityComponent.builder()
                .activityModule(new ActivityModule(this))
                .applicationComponent(DemoApplication.get(this).getComponent())
                .build();
    }
return activityComponent;
}

Similar process is applied in the MainActivity as well. The only difference is that, we also provide ApplicationComponent that is required for dependencies resolution mentioned in ActivityModule.

This completes the Example project.

Hope you have got a working knowledge of Dagger2.

The GitHub repo for this example project:

https://github.com/MindorksOpenSource/android-dagger2-example

As we mentioned about @Named("string")annotation, we just have to replace @ApplicationContext and @ActivityContext with something like @Named("application_context") and @Named("activity_context") every where. But personally I don’t like this implementation. It requires to maintain a String tag.

Note: If due to some reason we have to inject dependencies through field injection for classes other that android constructed classes then define a component interface for that class and call it’s inject method in class’s constructor. I would suggest to figure out a way not to do this but try to use the constructor with @Inject for the injection.


 

Let’s become friends on Twitter, Linkedin, Github, and Facebook.

Learning is a journey, let’s learn together!

 

Janishar Ali

Janishar Ali

Co-Founder at Mindorks | Learning is a journey, let’s learn together