Dependency Injection in Android ( Part IV: The Android Story — Dagger 2 )

Santosh Dhakal
6 min readSep 27, 2016

--

This is the last blog post in dependency injection series. If you want to follow previous series post, please click on these links series 1, series 2, series 3. As mentioned in series 3, any class or library that does dependency injection on our behalf does the following

We are using Dagger 2 in this example, but you are free to explore other dependency injection libraries like proton and roboguice. Just like any dependency injection libraries, dagger provides us with the containers and methods in which we show how to instantiate objects, which classes may need those, and how to access those objects. Dagger 2 usage and working can be clearly illustrated in following diagram

Going from our series 2 and series 3, we know MainActivity, FormEditFragment and FormDisplayFragment depends upon DataService. So, we create a module,where we tell Dagger how to instantiate DataService in following manner

/**
* @Module means this is module container
* this container is looked upon on how to
* instantiate any dependent object.
* There can be many modules and
* each module can define any number of
* dependency objects as per need.
*/

@Module public class DataModule {

private Context context;
private static final String PREF_NAME = "diPref";

public DataModule(Context context) {
this.context = context;
}

/**
* @Provides means it is providing some objects
* or it is stub where we define how to
* instantiate dependency object.
* Further both our DataService
* implementations ( PrefManager
* or DbManager) needs context in
* their constructor.
* So you need to pass the context to
* this class or provide a context in
* any other way.
* Meaning it won't automatically search for
* context or create it.*/
@Provides public DataService getDataService() {
//return getPreferenceDataService();
//OR
return getDatabaseDataService();
}

private DataService getPreferenceDataService() {
return PrefManager.getInstance(context, PREF_NAME);
}

private DataService getDatabaseDataService() {
return DbManager.getInstance(context);
}
}

Here annotations @Module and @Provides are of key importance. As I have mentioned before, any dependency library simply provides containers and methods of instantiating objects and using them. Meaning it won’t automatically know how to instantiate object.

Now let us move on to our Component. As illustrated in the diagram, component acts as a link between dependent objects and dependency objects ( i.e. link between DataModule and MainActivity ,FormDisplayFragment, FormEditFragment). So, Component need to have knowledge of both and is created in following manner

/**
* This component links
* DataModule ( where we have
* defined way to instantiate
* dependent object DataService )
* and dependent classes which are
* MainActivity,FormDisplayFragment
* and FormEditFragment).
*/
@Component(modules = { DataModule.class }) public interface DataComponent {
/**
* If component only knows about module
* and not the dependent classes, then
* there is no way for dependent classes
* to use the dependency object. So
* the following method act as link
* between component and dependent classes.
* This link enables the dependent classes
* to use the dependency object or DataService
* object or defined module
*/
void inject(MainActivity target);

void inject(FormDisplayFragment target);
/**
* This is just a method definition.
* So you can name it whatever you like.
* But try to make it meaningful as well
*/
void injecto(FormEditFragment target);
}

Here @Component annotation specifies that it is a Component container. Component also can use multiple modules and define scopes for the modules to use. In short scope means the dependent object’s scope. Scope can be conceptually treated as variable scope within method and class definition. To keep it short, the dependency object can be defined in application level or class level scope. Here we are using application level scope. Further, there is concept of subcomponent as well. Scope and subcomponent, I will try to explain in another blog post.

Now as illustrated in the figure, we need to instantiate the Component and use its methods. First let us instantiate the Component in our application class as

public class MainApplication extends Application {
private DataComponent dataComponent;

@Override public void onCreate() {
super.onCreate();
/**
* This part may be confusing
* at first.
* If you at first simply write this
* line, the IDE would throw error as
* these classes won't be built until
* and unless you go to Build->RebuildProject.
* Once you do that, go to
* app/build/generated/source/apt/...
* You will see these generated class
* and it won't throw any error on IDE.
* DataComponent= name of our component.
* Dagger by default create the component as
* DaggerDataComponent.
* dataModule() method simply means your are
* trying to use DataModule class as defined
* in the @Component(modules = { DataModule.class }).
* So if there is another module named HelloModule
* and being used by the DataComponent, there
* will be method named
* helloModule().
*/
dataComponent = DaggerDataComponent.builder()
.dataModule(new DataModule(this)).build();
/**
* @Component(modules = { DataModule.class }),
* simply creates a
* setter in DaggerDataComponent class as
* dataModule(DataModule dataModule). Look
* upon the generated class. If you don't set
* the DataModule using that method,
* it will be null i.e. if you won't
* do as above statement, DataComponent will be created
* but with null dataModule. So annotating and
* setting the module is both need for component
* to function properly
*/
}

public DataComponent getDataComponent() {
return dataComponent;
}
}

After we have instantiated we need to use this component in the dependent classes as well in the following manner


public class MainActivity extends AppCompatActivity {

@Inject DataService dataService;

@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MainApplication)getApplication())
.getDataComponent().inject(this);
}
}
public class FormEditFragment extends BaseFragment { @Inject DataService dataService;

@Override public void onActivityCreated(@Nullable Bundle
savedInstanceState) {
super.onActivityCreated(savedInstanceState);
((MainApplication)getActivity()
.getApplication()).getDataComponent().injecto(this);
}
}
public class FormDisplayFragment extends BaseFragment { @Inject DataService dataService;

@Override public void onActivityCreated(@Nullable Bundle
savedInstanceState) {
super.onActivityCreated(savedInstanceState);
((MainApplication)getActivity()
.getApplication()).getDataComponent().inject(this);
}
}

After doing this, the dependent class gets it dependent object as variable. If in future, DataService is to be changed, then the only place it needs to be changed is in the DataModule class or to say module class. By doing this, the dependent class can freely use the functionality of dependency objects without having burden to create by themselves and without the need to change when dependency object instantiation method changes.

For setting up the Dagger we need to make following changes to our build.gradle

  1. Use apt gradle plugin from here. Since Dagger is using annotation, this plugin is there for facilitating annotation processing by excluding compiler part in the app and adding source set to current flavor. At project root’s build.gradle add the following
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
}
}

2. Use the apt plugin in your project’s build.gradle in following manner

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

android {
}
dependencies {
//dependency injection using dagger
compile 'com.google.dagger:dagger:2.0'
apt 'com.google.dagger:dagger-compiler:2.0'
provided 'javax.annotation:jsr250-api:1.0'
}

All the source code of this blog post is available in this github link. This is all for dependency injection in Android using Dagger 2.

Most of the developers I know are into belief that if there are objects in their classes, they should directly go into dependency injection. I don’t adhere to this belief completely. Dependency injection is awesome when you have a great or good system design. For instance, in Android, the classes which provides data/model like database, network first need to interact with your views in form of interface like dataSuccess() or dataFailure(). By doing this you are following separation of concerns i.e. views are only responsible for displaying data and data fetchers are responsible for fetching data. And they are simply connected by an interface. In this situation using dependency injection makes a great sense for modular design, future changes, mocking and testing. So, at first focus on making a good system and then only rely on dependency injection. Don’t just blindly began to use dependency injection for objects instantiation that you use in your class.

Useful links

  1. http://www.journaldev.com/2394/java-dependency-injection-design-pattern-example-tutorial
  2. Dependency Injection videos of https://caster.io/episode-list/

All the storyboard images were made using storyboardthat

--

--

Santosh Dhakal

Android app developer having a deep interest in mobile technology be it Android or iOS. Loves to learn ,share the knowledge and build things.