AndroidPub
Published in

AndroidPub

Dagger Android Migration Guide

This tutorial has been updated for Dagger 2.21

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.

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 })
interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance Builder application(App application);
AppComponent build();
}
void inject(FeatureActivity featureActivity);
}
@Module
abstract class AppModule {
@Provides
static Context provideContext(App application) {
return application.getApplicationContext();
}
@Singleton
@Provides
static 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 member 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.21'
implementation 'com.google.dagger:dagger:2.21'
// Dagger Android dependencies
annotationProcessor 'com.google.dagger:dagger-android-processor:2.21'
implementation 'com.google.dagger:dagger-android:2.21'
implementation 'com.google.dagger:dagger-android-support:2.21'

The setup of the new module requires more than just one component and one module in our project. Each Activity will require its own sub-component which is then hooked up to the Application component. My recommendation is to have the following class structure:

/
| App (extending Application)
| AppComponent
| AppModule
| ContributeAndroidInjectorsModule // 3
+ feature/
| FeatureModule // 2
| FeatureActivityModule // 1
| FeatureActivity

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.

  1. FeatureActivityModule

As mentioned earlier, each Activity now requires its own sub-component. Dagger includes a convenience annotation that will signal Dagger to generate the sub-component for us. This is done like so:

@Module
public abstract class FeatureActivityModule {
@ActivityScope
@ContributesAndroidInjector(modules = { FeatureModule.class })
abstract FeatureActivity contributeFeatureActivityInjector();
}

2. FeatureModule

In this module we set up all the bindings we need in the Activity itself. Unless this module is not used in other components, the bindings in this module will be valid only for this Activity and its sub-components. Assuming we’re implementing the MVP pattern, this is how you’d normally bind the View:

@Module
abstract class FeatureModule {

@ActivityScope
@Binds
abstract FeatureView provideFeatureView(FeatureActivity featureActivity);
}

3. ContributeActivityModule

So far we told Dagger to generate a sub-component for our Activity but we haven’t hooked it up to Application Component. To do that, we need another module that will just hold all the Activity modules. This module will then be included in the Application component.

@Module(includes = {
FeatureActivityModule.class
// Other Activity modules will be added here
})
abstract class ContributeActivityModule {
}

4. AppComponent

The application component set up (in Kotlin):

I prefer to write Dagger modules in Java due to Kotlin’s lack of static modifier and the use of companion object requires additional @Module annotation nested in the same class, which I’m not a big fan of.

@Singleton
@Component(modules = [
AndroidInjectionModule::class, // 1
ContributeActivityModule::class, // 2
AppModule::class
])
interface AppComponent : AndroidInjector<App> { // 3
@Component.Builder
abstract class Builder : AndroidInjector.Builder<App>() // 4
}

Notice that there are a few changes in the AppComponent:

  • We have to include AndroidSupportInjectionModule in the modules of the component for Dagger Android to work.
  • We include ContributeActivityModule which will hook up our Activity sub-component to AppComponent
  • AppComponent must inherit from AndroidInjector and define the Application class.
  • The Builder of AppComponent can optionally extend AndroidInjector.Builder and provide the Application class. This will simply save us a few lines of code since this base class already implements the common component builder with a method to set Application instance and another to build the component.

5. We then modify our App class it extends DaggerApplication and implements the base methods like so:

class App : DaggerApplication() {
private val appComponent: AndroidInjector<App> by lazy {
DaggerAppComponent
.builder()
.create(this)
}

override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return appComponent
}
}

6. And finally, in FeatureActivity, we remove the code we had there for injecting the activity and inherit from DaggerAppCompatActivity instead of AppCompatActivity.

class FeatureActivity : DaggerAppCompatActivity(), FeatureView {
@Inject
internal lateinit var presenter: FeaturePresenter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.feature_activity)
}
}

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 don’t need to manually inject the Activity anymore and therefore we don’t have access to the module creation as before.

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? You may recall that we have FeatureModule with the following set up to bind FeatureActivity everywhere a FeatureView is asked:

@Module
public abstract class FeatureModule {
@Binds
abstract FeatureView provideFeatureView(FeatureActivity featureActivity);
}
}

But what if we want to pass arguments that are received via an Intent to the Activity to the Presenter? Let’s say you have a unique ID that is passed to the activity via the activity’s Intent’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 foo() {
featureView.showFoo();
}
}

The answer is simple, we add a Provides method to FeatureModule that will take a FeatureActivity instance in and get the value we need from the intent extras:

@Module
abstract class FeatureModule {

@ActivityScope
@Binds
abstract FeatureView provideFeatureView(FeatureActivity featureActivity);

@ActivityScope
@Provides
@Named("someId")
static String provideSomeId(FeatureActivity featureActivity) {
return featureActivity.getIntent().getStringExtra(FeatureActivity.EXTRA_SOME_ID);
}
}

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, Receivers and ContentProviders.

Conclusion

Dagger-android module is close as it gets to proper dependency injection on Android. If you’re starting a new project and are planning to use Dagger, you should definitely use Dagger-Android setup.

The official sample is a bit too simple to my taste so you’d probably want to check out my own sample instead.

If you liked this article please don’t forget to give some claps and follow me.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store