Dagger 2 with ContributesAndroidInjector - Is it easy?
If you are not familiar with basics of dagger 2 and dependency injection, I recommend you to read dagger 2 basics tutorials. Before start explaining let me tell you that, Current version of Dagger 2 comes with many new changes, A new package was included in dagger, dagger.android package that contains many interfaces and classes that simplifies dagger to an extent.
Before Dagger 2.7, I used to initialize my component like this:
component= DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
And I declare factory of my subcomponents in component like this:
@Singleton
@Component(
modules = {
AppModule.class
}
)
public interface ApplicationComponent {
MainActivityComponent plus(MainActivityComponent.ModuleImpl module);
}
And injecting it in an activity like this:
((MyApplication) getApplication()).getComponent().plus(new MainActivityComponent.ModuleImpl(this));
component.inject(this);
But this pattern is invalidating core principle of dependency injection which stats — “A class shouldn’t know anything about how it is injected”. So the main question arise how to remove this injection pattern so that a class never finds out about its injector and how to build our components and subcomponents?
I am going to compare previous implementation with current implementation of dagger. First I will explain the previous implementation and then I will explain how previous implemented things change in newer version. Start from setting up dagger by adding dependencies:
implementation 'com.google.dagger:dagger:2.15'
implementation 'com.google.dagger:dagger-android-support:2.15'
annotationProcessor 'com.google.dagger:dagger-compiler:2.15'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.15'
Dagger has several artifacts, so the first one is core dagger library that gives us annotations, the second one is an android artifact that gives us injector pattern for android specific and the last two are just the annotation artifacts that we require during annotation processing and they are not included in our final apk.
In previous implementation our Custom class extends Application class and the inject operation was done manually by us like this:
public class CustomApplication extends Application{
ApplicationComponent applicationComponent;
@Inject ActivityInjector activityInjector;
@Override
public void onCreate() {
super.onCreate();
applicationComponent=DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
applicationComponent.inject(this);
}
public ActivityInjector getActivityInjector() {
return activityInjector;
}
}
In new implementation we don’t have to worry about the inject operation as a new class DaggerApplication will do that for us. The first step that we have to do after adding dagger dependencies is creating a class that extends DaggerApplication class. The custom application class should be register in our manifest so that our app can recognize it and build it before running our app.
DaggerApplication class will inject our application as soon as build method of DaggerAppComponent is executed in its abstract method. Look at the below code snippet. We are overriding this abstract method to tell dagger how to make our Singleton Component, DaggerAppComponent class will be generated by dagger which has a final nested Builder class that implements our component.builder, so as soon as we call that build method from this abstract method it will automatically inject our application class. For sample project refer to https://github.com/shubham-95/LearningDagger.
public class ToDoApplication extends DaggerApplication {
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder().application(this).build();
}
}
Now what is DaggerApplication here?? From Docs
DaggerApplication is an abstract class that extends Application class and that injects its members and can be used to inject Activity, Fragments, Services, BroadcastReceivers and ContentProviders attached to it. Injection is performed in onCreate() or the first call to AndroidInjection.inject(ContentProvider), whichever happens first.
Let’s understand it, DaggerApplication is an abstract class in dagger-android package, the class extends Application class, implements HasActivityInjector, HasBroadcastReceiverInjector, HasContentProviderInjector, HasFragmentInjector, HasServiceInjector and have an abstract method whose implementation should return an AndroidInjector.
Interfaces implemented by DaggerApplication class are Providers they provides their corresponding AndroidInjectors like HasActivityInjector provides AndroidInjector of Activity.
The next question is what is AndroidInjector?
From docs — Android Injector is an interface that enclose an abstract class Builder<T> and a Factory interface, the main purpose of this interface is to perform members-injection for a concrete subtype of a core Android type example Activity. This interface is commonly implemented by Subcomponent-annotated types whose Subcomponent.Builder extends AndroidInjector.Builder.
In Previous implementations my Subcomponents usually look like this:
@ActivityScope
@Subcomponent(modules = {TasksModule.class})
public interface TasksActivitySubcomponent extends AndroidInjector<TasksActivity>{
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<TasksActivity>{
@Override
public void seedInstance(TasksActivity instance) {
}
}
}
So I have to write a whole lot of boilerplate code in previous implementation and that took a lot of effort but there was a relief that I don’t have to define all my subcomponents factory in parent component, the relief comes from a module class that saves my time. Modules.SubComponent (ActivityBindingModule) a module class through which I can provide all of my subcomponents in components without declaring any factories. Yeah!! I was waiting for such a change, Let’s see how ActivityBindingModule was defined in previous implementation that can provide all of my subcomponents in a component.
@Module(subcomponents = {TasksActivitySubcomponent.class})
public abstract class ActivityBindingModule {
@Binds
@IntoMap
@ActivityKey(TasksActivity.class)
abstract AndroidInjector.Factory<? extends Activity> providesTasksActivityInjector(TasksActivitySubcomponent.Builder builder);
}
Let me explain the above code the first annotation that we are using is @Binds which is replacement of @Provides methods which simply return the injected parameter. For more information you can refer to https://google.github.io/dagger/faq. The second annotation @IntoMap is used to put our AndroidInjector into a map where key is our class literal and value is our AndroidInjector. The third annotation is stating that, key in map is of Activity type. Yes it’s lot of boilerplate code that we write in previous implementation.
Now we will look how subcomponents and ActivityBindingModule is developed in new implementation. To remove the boilerplate code Dagger Android comes with a rescue annotation which is ContributesAndroidInjector. If your subcomponent module look like this:
@Module
public abstract class TasksModule {
@ActivityScoped
@Binds abstract TasksContract.Presenter taskPresenter(TasksPresenter presenter);
}
then dagger will handle the rest work, We just have to create ActivityBindingModule using ContributesAndroidInjector:
@Module
public abstract class ActivityBindingModule {
@ActivityScoped
@ContributesAndroidInjector(modules = TasksModule.class)
abstract TasksActivity tasksActivity();
}
Now let’s have a look at the documentation of ContributesAndroidInjector. It stats that “Generates an AndroidInjector for the return type of this method. The injector is implemented with a dagger.Subcomponent and will be a child of the dagger.Module’s component. This annotation must be applied to an abstract method in a dagger.Module that returns a concrete Android framework type. The method should have no parameters.”
This annotation will generate an AndroidInjector<TasksActivity> and it will generate following boilerplate code for us:
@Module(subcomponents = ActivityBindingModule_TasksActivity.TasksActivitySubcomponent.class)
public abstract class ActivityBindingModule_TasksActivity {
private ActivityBindingModule_TasksActivity() {}
@Binds
@IntoMap
@ActivityKey(TasksActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
TasksActivitySubcomponent.Builder builder);
@Subcomponent(modules = TasksModule.class)
@ActivityScoped
public interface TasksActivitySubcomponent extends AndroidInjector<TasksActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<TasksActivity> {}
}
}
I scoped my ContributesAndroidInjector because some of my dependencies are scoped in my module, that means we can scope our dependencies in module but don’t forget to provide scope along with ContributesAndroidInjector in ActivitybindingModule otherwise it will generate an error on compile time.
So after these changes dagger android removes my whole boilerplate code and save my effort. Finally our Component will look like this:
@Singleton
@Component(modules = {
ApplicationModule.class,
ActivityBindingModule.class,
AndroidSupportInjectionModule.class})
public interface AppComponent extends AndroidInjector<ToDoApplication> {
@Component.Builder
interface Builder {
@BindsInstance
AppComponent.Builder application(Application application);
AppComponent build();
}
}
In above component Binds instance injects our application class into component at run time OR it just binds our application with dagger component at run time.
Now what is this new module that we include in our component - AndroidSupportInjectionModule? Let’s have a look at it, In dagger.android package there are two abstract classes AndroidSupportInjectionModule and AndroidInjectionModule according to documentation — These classes Configures bindings to ensure the usability of dagger.android and dagger.android.support framework classes. This module should be installed in the root-most component which will use these types. In Simple terms we use AndroidSupportInjectionModule because it helps us with generation and location of subcomponents. Not only this by including this module it provides us ActivityInjector, FragmentInjector etc that are injected in our DaggerApplication class. If we do not include this module in our root component compiler will generate an error stating can not be injected without @Provides method.
So our dagger setup is completed. Let’s have a look how to inject these dependencies in our activities.
public class TasksActivity extends DaggerAppCompatActivity {
@Inject
TasksPresenter mTasksPresenter;
@Inject
Lazy<TasksFragment> taskFragmentProvider;
private DrawerLayout mDrawerLayout;
}
That’s it now we can use presenter in our activity. For complete project you can check my sample project on Github, It just loads data from a news api and displays it on home screen. In project I already described how to create a submodule of a module if your methods are not abstract. Before checking out project please check out the difference between binds and provides.
If you want to cache your AndroidInjectors during configuration changes, you have to create a custom Injector class and use it.
This article is written after taking references from some articles and project reference is taken from google sample. I’m attaching the references if you guys want to check out that.
Thanks to Mirek Stanek for publishing such great article on dagger 2.
https://medium.com/azimolabs/activities-subcomponents-multibinding-in-dagger-2-85d6053d6a95
Thanks for reading.