Android and Dagger 2.10 AndroidInjector
Dagger 2.10 introduced dagger-android, a new module specifically for Android that comes in addition to the main dagger module and compiler. In this tutorial we will go through the steps needed to get started with the new module — assuming you’re already familiar with Dagger.
This tutorial is focused on Activity injection, but might as well serve as a reference for other Android components.
The code in this article is written in Java, but is also available in Kotlin on Github under branch named kotlin.
Nimrodda/dagger-androidinjectordagger-androidinjector - This sample is part of a tutorial on how to use the new dagger-android module, which was…github.com
Common Dagger Setup
A common Dagger setup on Android normally involves an Application Component and an Application Module where the former is used to inject Android components, such as Activity, Fragment, etc.
@Component(modules = { AppModule.class })
public interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance Builder application(App application);
AppComponent build();
}
void inject(FeatureActivity featureActivity);
} @Module
public class AppModule {
@Provides Context provideContext(App application) {
return application.getApplicationContext();
} @Singleton @Provides SomeClientApi provideSomeClientApi() {
return new SomeClientApiImpl();
}
} public class App extends Application {
private AppComponent appComponent; @Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent
.builder()
.application(this)
.build();
} public AppComponent getAppComponent() {
return appComponent;
}
}
Since the Android framework instantiates these components for us, we have to perform members injection, i.e. annotating package visible class fields with @Inject-annotation like so:
public class FeatureActivity extends AppCompatActivity {
@Inject SomeClientApi mSomeClientApi; @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((App)getApplication())
.getAppComponent()
.inject(this);
}
}
This pattern however breaks a core principle of dependency injection: a class shouldn’t know anything about how it is injected. The newly introduced dagger-android module specifically comes to address this problem — decoupling the injected class from its injector.
Using the new dagger-android module
First thing, add the following Gradle dependencies to your build.gradle:
// Dagger core dependencies
annotationProcessor 'com.google.dagger:dagger-compiler:2.10'
compile 'com.google.dagger:dagger:2.10' // Dagger Android dependencies
annotationProcessor 'com.google.dagger:dagger-android-processor:2.10'
compile 'com.google.dagger:dagger-android:2.10' // Use this instead if you're using support library
compile 'com.google.dagger:dagger-android-support:2.10'
If you’re upgrading from an older version of Dagger, make sure that the version (2.10) is set to all dagger related dependencies. It’s also worth mentioning that I’m using Android Gradle plugin 2.3.0 in this tutorial. If you’re using version older than 2.2.0, you’d want to replaceannotationProcessorwithapt.
The setup of the new module requires more than just one component and one module in our project. My recommendation is to have the following class structure:
/
| App (extending Application)
| AppComponent
| AppModule
| BuildersModule
+ feature/
| FeatureModule
| FeatureSubComponent
As we can see we have 3 more classes (marked in bold) added on top of the typical setup discussed in the previous section. Each feature has its own sub component and module.
I’m using the term “Feature” to describe one screen (or Activity) in the app.
- The subcomponent must extend
AndroidInjector<TheActivity>and have an inner abstract builder class that extendsAndroidInjector.Builder<TheActivity>:
@Subcomponent
public interface FeatureSubComponent extends AndroidInjector<FeatureActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<FeatureActivity> {
}
}
2. We then need to add a binding to the sub component’s builder in BuildersModule so that Dagger would be able to inject FeatureActivity. Future sub components builders’ bindings need to be added to this class as
well.
@Module
public abstract class BuildersModule {
@Binds
@IntoMap
@ActivityKey(FeatureActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindFeatureActivityInjectorFactory(FeatureActivitySubComponent.Builder builder); // Add more bindings here for other sub components
}
3. Next, we need to wire the FeatureSubComponent to AppModule by specifying it in the subcomponents parameter of @Module annotation:
@Module(subcomponents = { FeatureSubComponent.class })
public class AppModule {
@Provides Context provideContext(App application) {
return application.getApplicationContext();
} @Singleton @Provides SomeClientApi provideSomeClientApi() {
return new SomeClientApiImpl();
}
}
4. And wire BuildersModule to AppComponent:
@Component(modules = {
/* Use AndroidInjectionModule.class if you're not using support library */
AndroidSupportInjectionModule.class,
AppModule.class,
BuildersModule.class })
public interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance Builder application(App application);
AppComponent build();
}
void inject(App app);
}
Notice that there are a few changes in the AppComponent:
- In addition to adding
BuildersModuleto the@Component(modules= {…}), we also addedAndroidSupportInjectionModule, which is a built-in module in dagger-android that must be installed in theAppComponentas per the official documentation that states that it is necessary in order to ensure that all bindings necessary for these base types are available. - We removed the
inject()method forFeatureActivityand replaced it with aninject()method for ourAppclass.
5. We then modify our App class so that it implements HasDispatchingActivityInjector and @Inject a DispatchingAndroidInjector<Activity> to return from the activityInjector() method. In onCreate(), we build AppComponent and call inject(this).
public class App extends Application implements HasDispatchingActivityInjector {
@Inject DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
@Override
public void onCreate() {
super.onCreate();
DaggerAppComponent
.builder()
.application(this)
.build()
.inject(this);
}
@Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return dispatchingAndroidInjector;
}
}
Note that in Dagger version 2.11 allHasDispatching*Injectors interfaces have been renamed toHas*Injectors
6. And finally, in FeatureActivity, we remove the code we had there for injecting the activity and instead we call AndroidInjection.inject(this) before calling super.onCreate(),
since the call to super attaches Fragments from the previous activity instance during configuration change, which in turn injects the Fragments. In order for the Fragment injection to succeed, the Activity must already be injected:
public class FeatureActivity extends AppCompatActivity implements FeatureView {
@Inject SomeClientApi mSomeClientApi; @Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
Injecting custom arguments to Activity’s own component
In some cases you want to inject arguments that are provided by the activity itself. The common way this was done previously was to pass the arguments to the module’s constructor before calling inject() on the activity’s component.
For example, if we follow the MVP design pattern as described here,
our Presenter would take the View as part of its constructor arguments. This means that we’d need to pass the activity as an argument to the module constructor. Prior to dagger-android, this was done like so:
@Module
class FeatureModule {
private FeatureView view;
public FeatureModule(FeatureView view) {
this.view = view;
}
@Provides FeatureView provideView() {
return view;
}
}
And in the presenter we would use constructor injection:
class FeaturePresenter {
private final FeatureView view;
@Inject
Presenter(FeatureView view) {
this.view = view;
} public void doSomething() { }
}
And lastly, build the component, pass a new instance of the module and inject the activity:
public class FeatureActivity extends AppCompatActivity implements FeatureView {
@Inject FeaturePresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerFeatureComponent.builder()
.featureModule(FeatureModule(this)).build()
.inject(this)
// presenter ready to be used
presenter.doNothing();
}
}
But how do we do it with the new dagger-android module? After all, we have only one call in the activity — AndroidInjection.inject(this) and there’s no way to pass in a new instance of the module as it was done
previously.
The answer is, we don’t need to do that anymore. With dagger-android module, the activity is already part of the graph. So what does it actually mean? It means that all we have creating a binding that will inject the activity wherever
FeatureView is requested. The way to do that is using Dagger’s @Binds annotation. For that, we’ll create a new module class called FeatureModule, which will contain all feature specific bindings.
@Module
public abstract class FeatureModule {
@Binds
abstract FeatureView provideFeatureView(FeatureActivity featureActivity);
}
}
This is great, but you’re probably wondering, what if I want to pass other arguments other than activity? Let’s say you have a unique ID that is passed to the activity via the activity’s extras and the presenter needs it. For example,
imagine that the presenter needs this ID to make an HTTP request. The way to do this is to use qualifiers with the @Named annotation. So our presenter would look like so:
class FeaturePresenter {
private FeatureView featureView;
private String someId;
@Inject
public FeaturePresenter(FeatureView featureView, @Named("someId") String someId) {
this.featureView = featureView;
this.someId = someId;
}
public void doNothing() {
featureView.doNothing();
}
}
Now we already saw that it’s not possible to pass arguments when injecting the activity via AndroidInjection.inject(this) call. So how do we make the graph aware of this ID? This is a bit tricky and might actually be a hack,
but currently one way to do it is to save the ID in a field in the activity and then have FeatureModule provide it.
- First we save the ID in a field in
FeatureActivity
public class FeatureActivity extends AppCompatActivity implements FeatureView {
@Inject FeaturePresenter presenter;
String someId;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
someId = getIntent().getStringExtra(EXTRA_SOME_ID);
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter.doNothing();
}
@Override
public void doNothing() {
}
}
2. Then we add a provide method to FeatureModule
@Module
public abstract class FeatureModule {
@Binds
abstract FeatureView provideFeatureView(FeatureActivity featureActivity);
@Provides @Named("someId") static String provideSomeId(FeatureActivity featureActivity) {
return featureActivity.someId;
}
}
As mentioned before, this is possible since FeatureActivity is already in the graph.
Injecting other than activity
As mentioned in the beginning of this tutorial, Fragment injection as well as other Android components are out of the scope of this tutorial. The official documentation covers fragments under Injecting Fragment objects and I highly encourage you to read it. There’s also more info on Services and Receivers.
Conclusion
The new dagger-android module is getting us one step closer to proper dependency injection, though in my opinion it feels more complex than before. The Dagger team is actively working on simplifying this further and I have no doubts that the API would go through additional changes pretty soon. There’s still no official samples provided as of writing this tutorial. You’d probably want to check out