Chapters

Hide chapters

Dagger by Tutorials

First Edition · Android 11 · Kotlin 1.4 · AS 4.1

16. Dagger & Android
Written by Massimo Carli

In the previous chapters of this book, you became a Dagger guru. You learned many new concepts, in particular:

  • How Dagger works and how it helps you implement the main principles of object-oriented programming in your app.
  • The different types of injection, how to know which one to use for a specific use case and how to implement them with Dagger.
  • How to use custom @Scopes to optimize the way your app uses resources.
  • When to use @Components or @Subcomponents to manage dependencies between objects with different @Scopes.
  • How to implement architecture based on the concept of plugins by using multibinding with Sets or Maps.
  • How to create a structure for your app’s code by splitting it into different modules, resulting in improved build times, reusability and extensibility.

You’ve done a great job. Dependency injection is a tough topic and Google is working to make it easier, improving the learning curve that, before this book, was very steep. The result of Google’s effort is Hilt.

You’ll learn about Hilt in the remaining chapters of this book. Before you get to that, though, take a moment to consider the legacy code you might need to maintain. To handle that, you’ll learn about Dagger Android in this chapter.

There’s still a lot of code out there that uses Dagger Android, which is a library that Google created with the goal of simplifying Dagger in Android apps. Unfortunately, the result was not very successful and the solution is sometimes more complicated than the problem it was supposed to solve.

For this reason, Google stopped developing new features for the Dagger Android library in favor of Hilt. As you’ll learn in this chapter, Dagger Android helps reduce the lines of code you need to write to configure Dagger, but it also has some important limitations. You’ll get to know about these limitations as you continue to refactor the Busso App.

In this chapter, you’ll learn:

  • Why you need a special tool to use Dagger in an Android app.
  • How Android Dagger works under the hood.
  • How to inject an Activity and a Fragment.
  • Which utility classes Android Dagger provides.
  • How to use @ContributesAndroidInjector.

Why Android is different for Dagger

In the previous chapters of this book, you converted Busso, which is an Android app, to Dagger. So if that worked, you might wonder why you’d need a specific Dagger Android library.

As you know, Android is a container that manages the lifecycle of its standard components, like Activitys, Services, ContentProviders and BroadcastReceivers. Because of that, Dagger can’t create an instance of a standard component like, for example, an Activity. That falls under the Android environment’s responsibilities.

You cannot use constructor injection in Android. Instead, you must write code like what you implemented in BusStopFragment.kt in the ui.view.busstop package of the app module:

class BusStopFragment : Fragment() {
  // ...
  override fun onAttach(context: Context) {
    context.activityComp
        .fragmentComponent()
        .inject(this)
    super.onAttach(context)
  }
  // ...
}

You had to write this code in all the Activitys and Fragments of the app. You did this exactly four times in Busso, and a larger project could need it even more often.

Sometimes, the code you need to write becomes very verbose, like what you have in SplashActivity.kt in the ui.view.splash package in app.

class SplashActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    makeFullScreen()
    setContentView(R.layout.activity_splash)
    this.application.appComp
      .activityComponentBuilder()
      .activity(this)
      .build()
      .inject(this)
    splashViewBinder.init(this)
  }
  // ...
}

In a perfect world, injection should happen from the outside. In the previous example, SplashActivity should know nothing about the ApplicationComponent you use for the actual injection and it shouldn’t have any code with inject().

But, can you completely remove all that code? The answer is: no. What you can do is to make that code easier to write or, even better, ask Dagger to write it for you. That’s exactly what Dagger Android does.

Dagger Android gives you a way to generate the code that, in the first part of this book, you put in Injector implementations. To do this, you need some libraries and, of course, an annotation processor. That’s Dagger Android!

Installing Dagger Android dependencies

The first step toward using Dagger Android is to install its dependencies in the app module’s build.gradle by adding the following to it:

  // Dagger Android
  implementation "com.google.dagger:dagger-android:$dagger_version" // 1
  implementation "com.google.dagger:dagger-android-support:$dagger_version" // 2
  kapt "com.google.dagger:dagger-android-processor:$dagger_version" // 3

These definitions tell you some interesting things:

  1. The Dagger Android library differs from the Dagger library you’ve used so far, meaning you need to add new dependencies to the app.
  2. If your app uses the Android support library, Dagger Android needs some more classes, which you find in dagger-android-support.
  3. You also need an annotation processor. This tells you that Dagger Android will generate some code for you.

Note that the Dagger Android library’s version is the same as Dagger’s version. That’s because, when Google releases a new version of Dagger, it also releases a new version of the Android library as part of the same codebase.

As an example of how to use the Dagger Android library, you’ll refactor the Busso App. In particular, you’ll migrate:

  1. SplashActivity
  2. MainActivity
  3. BusStopFragment
  4. BusArrivalFragment

Throughout this chapter, you’ll refer to the specific standard components or Fragments as the injection target.

During the refactoring process, you’ll realize that Dagger Android has some advantages — but at the cost of less freedom in how you structure the code.

How Dagger Android works

To illustrate how Dagger Android works, you’ll migrate Busso’s MainActivity and SplashActivity injection targets following these steps:

  1. Define an abstraction for the injectors.
  2. Generate an injector for each injection target.
  3. Simplify the code you use in the injection targets for the actual injection.
  4. Set up your Application for Dagger Android.
  5. Bind the injector to the specific injection target type.
  6. If necessary, work around some Android Dagger limitations.
  7. Build the app and enjoy. :]

Don’t worry if these tasks look complicated — everything will be clearer when you code along. Executing each of these steps in order will help you understand how Dagger Android works.

In this chapter, you’ll need to be very patient — the app won’t build successfully until you’ve finished refactoring. As you learned in the previous chapters, it’s good to try to build the app after each step anyway, to let Dagger generate whatever code it can.

Define an abstraction for the injectors

In Chapter 4, “Dependency Injection & Scopes”, you created the Injector<A> interface as the abstraction for any component with the responsibility of injecting objects into an injection target of type A. The interface looked like this:

interface Injector<A> {

  fun inject(target: A)

}

Dagger Android does a similar thing with the definition of this interface in dagger.android:

interface AndroidInjector<T> { // 1

  fun inject(T instance) // 2

  // 3
  interface Factory<T> {
    // 4
    fun create(@BindsInstance instance: T): AndroidInjector<T>
  }
}

Note: The Dagger Android library is in Java, but you see the Kotlin equivalent here.

There are some important things to note here, including:

  1. The name of the interface is different because Google preferred to use AndroidInjector<T>.
  2. inject()’s signature is the same as the one you had in Injector<A>.
  3. The library also provides an inner AndroidInjector.Factory<T> interface for AndroidInjector<T> itself.
  4. AndroidInjector.Factory<T> defines create() with a single parameter that must be of type T.

The last point is the reason for one of Dagger Android’s limitations. If you want to create an AndroidInjector<T> through AndroidInjector.Factory<T>, you can only pass a single object that must be of the same type as the injection target.

Note: Actually, AndroidInjector<T> also provides an AndroidInjector.Builder<T> interface that would solve the previous limitation. Unfortunately, this is now deprecated.

OK, but what’s responsible for creating the AndroidInjector<T> for your injection target? In Busso, for instance, what generates the AndroidInjector<MainActivity> and AndroidInjector<Splashctivity> implementations?

Dagger Android, of course! But you need to tell it how to do so.

Generating an injector for each injection target

In the previous step, you learned that Dagger Android provides an AndroidInjector<T> interface that all injectors need to implement to resolve the dependencies of an object of type T, which you called the injection target.

Dagger Android also provides the AndroidInjector.Factory<T> interface with the factory method create(), which accepts an object of type T. What you need to do now is to tell Dagger which specific AndroidInjector<T> to create — and, so, which specific injection target to address.

In your case, you need to do this for MainActivity and SplashActivity. But how? Well, you just need to remember what you already did without the Android Dagger library — specifically, where you put the inject() functions earlier.

Refactoring MainActivity

Open ActivityComponent.kt in the di package of app and you’ll see the following:

@Subcomponent(
  modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {

  fun inject(activity: SplashActivity) // HERE

  fun inject(activity: MainActivity) // HERE

  fun fragmentComponent(): FragmentComponent

  @Subcomponent.Builder
  interface Builder {
    fun activity(
      @BindsInstance activity: Activity // HERE
    ): Builder

    fun build(): ActivityComponent
  }
}

This tells you that AndroidInjector<T> must be either a @Component or a @Subcomponent. In the same code, you can also see how the existing Builder is similar to AndroidInjector.Factory<T>.

Of course, one is a Builder<T> and the other a Factory<T>, but the difference is insignificant. They’re both creational patterns. Creational patterns provide various object creation mechanisms, which increase flexibility and reuse of existing code.

This also tells you that the AndroidInjector<T> you’re asking Android Dagger to generate for you will replace the existing ActivityComponent.

Now that you understand the background, it’s time to write some code.

Create a new di.activities.main package in the app module and create a new file in it named MainActivitySubcomponent.kt, then ad the following code:

@Subcomponent(
  modules = [
    ActivityModule::class, // 1
  ]
)
@ActivityScope // 2
interface MainActivitySubcomponent : AndroidInjector<MainActivity> { // 3

  @Subcomponent.Factory // 4
  interface Factory : AndroidInjector.Factory<MainActivity> // 5
}

This code contains some interesting things:

  1. MainActivitySubcomponent is a @Subcomponent that uses ActivityModule because it gives Dagger information about how to create the objects MainActivity needs.
  2. MainActivitySubcomponent is a @Subcomponent for the bindings with @ActivityScope.
  3. You extend AndroidInjector<MainActivity>, which means you implicitly define inject() with a parameter of type MainActivity.
  4. The MainActivity object is something you provide when you need an instance of the MainActivitySubcomponent implementation. This is why you define @Subcomponent.Factory.
  5. @Subcomponent.Factory extends AndroidInjector.Factory<MainActivity>. In this way, you implicitly inherit the definition of the create() operation with a parameter of type MainActivity.

With this code, you tell Android Dagger that you need an injector for MainActivity.

Refactoring SplashActivity

Next, you’ll do the same for SplashActivity. Just create a new package named splash in the existing di.activities and create SplashActivitySubcomponent.kt inside. Then, add the following code:

@Subcomponent(
  modules = [
    ActivityModule::class,
  ]
)
@ActivityScope
interface SplashActivitySubcomponent : AndroidInjector<SplashActivity> {

  @Subcomponent.Factory
  interface Factory : AndroidInjector.Factory<SplashActivity>
}

As you see, the code is basically the same as what you wrote for MainActivity, but with a different generic type parameter value.

Cleaning up your code

Before continuing, you need to do some cleanup and refactoring. Specifically, you need to:

  1. Move the existing ActivityModule.kt from the di package into di.activities. This puts all the code related to Activitys in the same place.
  2. Delete the existing ActivityComponent.kt from the di package.

Now, you told Dagger which injectors you need for your Activitys. Before going further, take a moment to see how to use them.

Simplifying the code

Supposing that Dagger already generated AndroidInjector<SplashActivity> and AndroidInjector<MainActivity> for you, how would you use them? This is the main advantage of Android Dagger — it allows you to replace all the boilerplate related to the inject() invocation you saw earlier with a simple instruction.

To see this in action, open MainActivity.kt in the ui.view.main package and replace the existing code with:

class MainActivity : AppCompatActivity() {

  @Inject
  lateinit var mainPresenter: MainPresenter

  override fun onCreate(savedInstanceState: Bundle?) {
    AndroidInjection.inject(this) // HERE
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }
}

Compare this code with what you had before and you’ll see:

  • The injection code is now a simple invocation of the static method, inject(), on an AndroidInjection, which passes the reference to MainActivity.
  • You don’t need a reference to ActivityComponent anymore. As you’ll see soon, Dagger will manage @Subcomponent inheritance for you.
  • Because of the previous point, you also removed the activityComp extension property

Of course, you can do the same thing for SplashActivity.kt in ui.view.splash, getting something like this:

class SplashActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    AndroidInjection.inject(this) // HERE
    super.onCreate(savedInstanceState)
    makeFullScreen()
    setContentView(R.layout.activity_splash)
    splashViewBinder.init(this)
  }

  // ...
}

Think about all the code you had to write before — and now, there’s just a single instruction for every injection target.

How AndroidInjection works

But how does AndroidInjection’s inject() work?

You can summarize its source code like this:

  fun inject(activity: Activity) { // 1
    val application = activity.application
    if (application is HasAndroidInjector) { // 2
      application.androidInjector().inject(activity) // 3
    } else {
      throw RuntimeException("Something wrong") // 2
    }
  }

There are many important things to note:

  1. inject() has a parameter of type Activity that is a common abstraction for all the Activitys. Therefore, it includes both MainActivity and SplashActivity.
  2. You use the Application object that must implement the HasAndroidInjector interface, which defines a single androidInjector() operation. In short, a HasAndroidInjector is any object that can provide an AndroidInjector<Any>. This tells you that, to make everything work, your Application needs to be a HasAndroidInjector and must then provide an implementation of AndroidInjector<T>. Otherwise, you’ll get a RuntimeException.
  3. If your Application is a HasAndroidInjector, it will provide the AndroidInjector<Any> you’ll delegate the inject() invocation to.

The last point tells you what the next two steps you need to do are:

  1. Make your Application a HasAndroidInjector.
  2. When the Application returns AndroidInjector<Any>, you need to give the information about the specific AndroidInjector<T> to invoke for the injection target of type T.

Next, you’ll start with Busso’s Application, which you extended in Main.kt.

Setting up Application for Dagger Android

As you just learned, to make Android Dagger work, you need to make your Application implement HasAndroidInjector so it can provide an implementation of AndroidInjector<Any> to use in your Activitys.

To do this, open Main.kt in the main package for the app and change it like this:

class Main : Application(), HasAndroidInjector { // 1

  @Inject
  lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any> // 2

  override fun onCreate() {
    super.onCreate()
    DaggerApplicationComponent
      .factory()
      .create(this, BussoConfiguration)
      .inject(this)
  }
  // 2
  override fun androidInjector(): AndroidInjector<Any> {
    return dispatchingAndroidInjector
  }
}

In this code:

  1. Main now implements HasAndroidInjector and must define androidInjector(), returning the implementation of AndroidInjector<Any> to use for the actual injection.
  2. The AndroidInjector<Any> you return is the same object Android Dagger injects as DispatchingAndroidInjector<Any>.

You also removed the reference to the ApplicationComponent and the appComp extension property, which you don’t need anymore.

But where does DispatchingAndroidInjector<Any> come from? Dagger Android provides it when you add the following @Module to the dependency graph for ApplicationScope. Open ApplicationModule.kt in di for the app module and add the following definition:

@Module(
  includes = [
    LocationModule::class,
    NetworkModule::class,
    AndroidSupportInjectionModule::class // HERE
  ]
)
object ApplicationModule

AndroidSupportInjectionModule is the module you need to add if you’re using the support library — which Busso does.

Main now implements HasAndroidInjector and provides the AndroidInjector<Any> you need to inject in Busso’s Activitys.

This is an object of type DispatchingAndroidInjector<Any>, which you get from Android Dagger through injection. Look at the source code and you’ll find that it contains a Map between the type of the injection target of type T and the AndroidInjector.Factory<T> to use for the actual injection. Does that ring a bell? :]

You already told Dagger which AndroidInjector<T> implementation you need by providing an AndroidInjector.Factory<T> for each injection target. What you’re still missing is a way to bind the injection type T to it — which is multibinding!

Before implementing the multibinding, you need to do some cleanup. Open ApplicationComponent.kt in the di package and remove the following definition:

@ApplicationScope
interface ApplicationComponent {

  fun activityComponentBuilder(): ActivityComponent.Builder // REMOVE!!
  // ...
}

You don’t need this anymore because the ActivityComponent is now Dagger Android’s responsibility.

As a quick status update, clean up all the imports to the definitions you deleted then build the app. You’ll only have some errors in the Fragments, which you’ll fix later.

Binding the injector to the specific injection target type

Dagger Android now knows which AndroidInjector<T> to create but it doesn’t know how to bind the result to a specific type. In Chapter 14, “Multibinding With Map”, you solved a similar problem using @ClassKey. You’ll do the same when you work with Dagger Android.

Create a new file named MainActivityModule.kt in di.activities.main and add the following code:

@Module(
  subcomponents = [MainActivitySubcomponent::class] // 1
)
interface MainActivityModule {

  @Binds
  @IntoMap
  @ClassKey(MainActivity::class) // 2
  fun bindMainActivitySubcomponentFactory(
    factory: MainActivitySubcomponent.Factory // 3
  ): AndroidInjector.Factory<*>
}

In this @Module. you configure two important things:

  1. Using the subcomponent attribute of @Module, you create an inheritance relationship between MainActivitySubcomponent and the @Component or @Subcomponent you add this @Module to, using the modules attribute.
  2. Using multibinding, you add a new entry to a Map that has Class<T> as its key and AndroidInjector.Factory<*> as a value. In this specific case, you assign MainActivitySubcomponent.Factory as a value to the MainActivity::class key.

Adding the @Modules to ApplicationComponent

Now, according to what you did in step one above, you need to add MainActivityModule to the modules attribute of the @Component or @Subcomponent that you want to be MainActivitySubcomponent’s parent. This is ApplicationComponent.

Before that, create a new file named SplashActivityModule.kt in di.activities.splash and add the following code:

@Module(
  subcomponents = [SplashActivitySubcomponent::class]
)
interface SplashActivityModule {

  @Binds
  @IntoMap
  @ClassKey(SplashActivity::class)
  fun bindSplashActivitySubcomponentFactory(
    factory: SplashActivitySubcomponent.Factory
  ): AndroidInjector.Factory<*>
}

Now, you’re ready to add both @Modules to ApplicationComponent. Open ApplicationComponent.kt in di and add the following definitions:

@Component(
  dependencies = [NetworkingConfiguration::class],
  modules = [
    ApplicationModule::class,
    InformationPluginEngineModule::class,
    InformationSpecsModule::class,
    MainActivityModule::class, // HERE
    SplashActivityModule::class // HERE
  ]
)
@ApplicationScope
interface ApplicationComponent {
  // ...
}

Build the app now and you’ll get an unexpected error with the following message:

error: [Dagger/MissingBinding] android.app.Activity cannot be provided without an @Inject constructor or an @Provides-annotated method.
public abstract interface ApplicationComponent {
                ^
      android.app.Activity is injected at
          com.raywenderlich.android.ui.navigation.di.NavigationModule.provideNavigator(activity)

What’s happening? Why is the compiler complaining about the Navigator interface? This is related to the limitation you read about earlier. You’ll fix it next.

Working around Dagger Android limitations

You’ve almost finished configuring Dagger Android for Busso’s Activitys, but you got an annoying error on the Navigator interface. This is a consequence of the limitation you read about earlier: Navigator needs an Activity but you have a MainActivity and a SplashActivity, instead.

Dagger is picky with types, and you learned that even if MainActivity IS-A Activity, Dagger treats them as different types unless you use @Binds to make them equivalent.

However, you can’t just connect the MainActivity to Activity with @Binds because sometimes, you need SplashActivity instead. One possible solution is to use qualifiers to indicate when you should use which activity.

Using qualifiers

Start by creating a new file named NavigatorModule.kt in the new di.navigator and add the following to it:

@Module
object NavigatorModule {

  @Provides
  @ActivityScope
  @Named("Main") // HERE
  fun providesMainActivityNavigator(owner: MainActivity): Navigator =
    NavigatorImpl(owner)

  @Provides
  @ActivityScope
  @Named("Splash") // HERE
  fun providesSplashActivityNavigator(owner: SplashActivity): Navigator =
    NavigatorImpl(owner)
}

Here, you’re saying that the Activity the Navigator needs can be MainActivity or SplashActivity, depending on the qualifier you use when you declare the dependency.

This should be straightforward, but there’s still a problem: NavigatorImpl isn’t visible because of the work you did in the previous chapter with encapsulation and modularization. To make it visible, you need to break encapsulation.

Do this by opening NavigatorImpl.kt in libs.ui.navigation and removing the internal modifier, like this:

class NavigatorImpl(private val activity: Activity) : Navigator { // HERE
  // ...
}

After this change, you can compile NavigatorModule — but you still need to apply some changes.

Adding Qualifiers

First, open ActivityModule.kt in di.activities in app and replace the existing NavigationModule with NavigatorModule, like this:

@Module(
  includes = [
    NavigatorModule::class // HERE
  ]
)
interface ActivityModule {
  // ...
}

Then, you need to use the right qualifier in the right place. Open SplashViewBinderImpl.kt in ui.view.splash and add @Named("Splash").

class SplashViewBinderImpl @Inject constructor(
  @Named("Splash") private val navigator: Navigator // HERE
) : SplashViewBinder {
  // ...
}

This means that the Navigator you use in SplashViewBinderImpl is the one using the SplashActivity.

Now, open MainPresenterImpl.kt in ui.view.main and add @Named("Main"), like this:

class MainPresenterImpl @Inject constructor(
  @Named("Main") private val navigator: Navigator // HERE
) : MainPresenter {
  // ...
}

There’s one final place where you need to apply a fix. Open BusStopListPresenterImpl.kt in ui.view.busstop and make the following change:

@FragmentScope
class BusStopListPresenterImpl @Inject constructor(
  @Named("Main") private val navigator: Navigator, // HERE
  private val locationObservable: Observable<LocationEvent>,
  private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusStopListViewBinder>(),
  BusStopListPresenter {
  // ...
}

Build and run now and you’ll only get the errors about Fragments — which you’re about to address.

Reviewing what you achieved

You’ve just migrated Busso’s Activitys to Dagger Android. It’s hard to believe, because the app doesn’t build successfully yet, but that’s only because you still need to migrate the Fragments. You’ll do that right after you summarize what you’ve done so far. You:

  1. Told Dagger which AndroidInjector<T> to create for Busso’s injection target. You did this by creating a @Module and a @Subcomponent for each target.
  2. Used multibinding to map the class of the injection target, T, to the AndroidInjector.Factory<T>.
  3. Prepared your Application to be a HasAndroidInjector making it able to provide the AndroidInjector<Any> to use in your Activitys through AndroidInjection.

That’s a lot. The good news is, you don’t have to learn anything new for Fragments. You just need to follow the same process.

Injecting Fragments

Busso doesn’t build successfully yet because you still need to fix the Fragments for Dagger Android. The process is basically the same as you followed for the Activitys, but with some small but important differences.

For instance, in the case of Activitys, the HasAndroidInjector needs to be an Application. In the case of Fragments, the HasAndroidInjector is an Activity. Just code along and everything will be fine.

Creating @Subcomponents and @Modules for Fragments

Create a new package named ui.fragments and move the existing FragmentModule.kt into it. Now, create a new package called di.fragments.busstop and create a new file named BusStopFragmentSubcomponent.kt in it. Finally, add the following content:

@Subcomponent(
  modules = [
    FragmentModule::class
  ]
)
@FragmentScope
interface BusStopFragmentSubcomponent : AndroidInjector<BusStopFragment> {

  @Subcomponent.Factory
  interface Factory : AndroidInjector.Factory<BusStopFragment> {

    override fun create(@BindsInstance instance: BusStopFragment): BusStopFragmentSubcomponent // HERE
  }
}

The only difference is that you override create() to make the return type, BusStopFragmentSubcomponent, explicit.

In the same package, create a new file named BusStopFragmentModule.kt with the following code:

@Module(
  subcomponents = [BusStopFragmentSubcomponent::class]
)
interface BusStopFragmentModule {

  @Binds
  @IntoMap
  @ClassKey(BusStopFragment::class)
  fun bindBusStopFragmentSubcomponentFactory(
    factory: BusStopFragmentSubcomponent.Factory
  ): AndroidInjector.Factory<*>
}

This is the code that you’re already familiar with. :]

Create a new di.fragments.busarrival and create the BusArrivalFragmentSubcomponent.kt inside, adding this code to it:

@Subcomponent(
  modules = [
    FragmentModule::class
  ]
)
@FragmentScope
interface BusArrivalFragmentSubcomponent : AndroidInjector<BusArrivalFragment> {

  @Subcomponent.Factory
  interface Factory : AndroidInjector.Factory<BusArrivalFragment> {

    override fun create(@BindsInstance instance: BusArrivalFragment): BusArrivalFragmentSubcomponent
  }
}

In the same package, also create BusArrivalFragmentModule.kt and add this code:

@Module(
  subcomponents = [BusArrivalFragmentSubcomponent::class]
)
interface BusArrivalFragmentModule {

  @Binds
  @IntoMap
  @ClassKey(BusArrivalFragment::class)
  fun bindBusArrivalFragmentSubcomponentFactory(
    factory: BusArrivalFragmentSubcomponent.Factory
  ): AndroidInjector.Factory<*>
}

These @Subcomponents will replace the existing FragmentComponent.kt. You don’t need it anymore, so delete it. This also deletes the include for InformationPluginEngineModule.FragmentBindings, which you need to move into FragmentModule.kt.

The code should now look like this:

@Module(includes = [InformationPluginEngineModule.FragmentBindings::class]) // HERE
interface FragmentModule {
  // ...
}

Now that you’ve told Dagger which AndroidInjector<T> to create for Busso’s Fragments, the next step is to simply the injection code.

Simplifying the Fragments’ injection

Fragments need code that’s similar to what you used for Activitys.

Open BusStopFragment.kt in ui.view.busstop and apply the following change:

class BusStopFragment : Fragment() {
  // ...
  override fun onAttach(context: Context) {
    AndroidSupportInjection.inject(this) // HERE
    super.onAttach(context)
  }
  // ...
}

Again, this is the code that you’re already familiar with.

Next, open BusArrivalFragment.kt in ui.view.busarrival and do the same thing, like this:

class BusArrivalFragment : Fragment() {
  // ...
  override fun onAttach(context: Context) {
    AndroidSupportInjection.inject(this) // HERE
    super.onAttach(context)
  }
  // ...
}

Note here how you used inject() for AndroidSupportInjection. That’s because you’re using the support library for Fragments and the parameter for inject() must be of the right type.

inject() works the same way as its counterpart for Activitys does, so the next step is to make MainActivity an object of type HasAndroidInjector — just as you did for Main.

Setting up HasAndroidInjector for Fragments

inject() for AndroidSupportInjection follows the same logic you saw for the AndroidInjection and Activitys. To implement it, open MainActivity.kt in ui.view.main and apply the following changes:

class MainActivity : AppCompatActivity(), HasAndroidInjector { // 1

  @Inject
  lateinit var mainPresenter: MainPresenter

  @Inject
  lateinit var androidInjector: DispatchingAndroidInjector<Any> // 2

  override fun onCreate(savedInstanceState: Bundle?) {
    AndroidInjection.inject(this)
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }

  override fun androidInjector(): AndroidInjector<Any> = androidInjector // 3
}

Just as you did for Main, you:

  1. Implement HasAndroidInjector.
  2. Inject an object of type DispatchingAndroidInjector<Any>.
  3. Return the injected object from androidInjector().

You’re almost done. The last step is to configure the relationship between the @Subcomponents for the Fragments and those of the Activitys.

Configuring the @Subcomponents’ relationships

For your last step, you need to set @Subcomponents related to Fragments as children of the one related to the MainActivity, which is the only one containing any Fragments.

To do this, open MainActivitySubcomponent.kt in di.activities.main and add BusStopFragmentModule and BusArrivalFragmentModule as values for its modules attribute, like this:

@Subcomponent(
  modules = [
    ActivityModule::class,
    BusStopFragmentModule::class, // HERE
    BusArrivalFragmentModule::class // HERE
  ]
)
@ActivityScope
interface MainActivitySubcomponent : AndroidInjector<MainActivity> {

  @Subcomponent.Factory
  interface Factory : AndroidInjector.Factory<MainActivity>
}

You did it! You can finally build and run successfully, getting what’s in Figure 16.1:

Figure 16.1 — The Busso App
Figure 16.1 — The Busso App

Using Dagger Android utility classes

Dagger Android’s goal was to help developers write less code, but frankly, at the moment, you’re not sure it did that. You wrote a lot of boilerplate in different files, to save other boilerplate in Activitys and Fragments.

To improve this, Dagger Android provides some utility classes that, in most cases, help you write less code. In particular, Dagger Android provides:

  • DaggerApplication
  • DaggerAppCompatActivity
  • @ContributesAndroidInjector

Next, you’ll see the benefits of these tools.

Using DaggerApplication

Main, which is the Application for Busso, has quite a defined structure. It has to:

  1. Implement HasAndroidInjector.
  2. Define a property of type DispatchingAndroidInjector<Any>.
  3. Provide an implementation for androidInjector().

You can do the same thing by simply extending DaggerApplication.

Try this out by opening Main.kt and applying the following changes:

class Main : DaggerApplication() { // 1

  override fun applicationInjector(): AndroidInjector<out DaggerApplication> { // 2
    return DaggerApplicationComponent
      .factory()
      .create(this, BussoConfiguration)
  }
}

In this code, you:

  1. Extend DaggerApplication in dagger.android.support.
  2. Implement the required applicationInjector(), returning the DaggerApplicationComponent implementation.

To make the second point work, you need your ApplicationComponent to be an AndroidInjector<Main>. So open ApplicationComponent.kt in di and change it like this:

// ...
@ApplicationScope
interface ApplicationComponent : AndroidInjector<Main> { // 1

  @Component.Factory
  interface Factory { // 2

    fun create(
      @BindsInstance application: Application,
      networkingConfiguration: NetworkingConfiguration
    ): ApplicationComponent
  }
}

In this case, it’s important to note how:

  1. ApplicationComponent now implements AndroidInjector<Main>.
  2. Factory doesn’t extend AndroidInjector.Factory<T> because you need an additional NetworkingConfiguration parameter.

Now, build and run and check that everything works as expected.

Using DaggerAppCompatActivity

Android Dagger provides the tools to reduce the boilerplate for Activitys as well. Open MainActivity.kt in ui.view.main and apply the following changes:

class MainActivity : DaggerAppCompatActivity() {

  @Inject
  lateinit var mainPresenter: MainPresenter

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }
}

As you see, MainActivity now extends DaggerAppCompatActivity, which takes care of all the injection boilerplate including the invocation to AndroidInjection.inject().

Of course, you might need some more work, in case you already have a hierarchy for your app’s Activitys.

Using @ContributesAndroidInjector with Activity

What you did with DaggerApplication and DaggerAppCompatActivity isn’t much compared to what you can achieve with @ContributesAndroidInjector.

You’ve probably noticed how repetitive the code you wrote to create the @Subcomponent and @Module for each AndroidInjector<T> is. The question now is: Can you avoid that? In this case, the answer is yes.

Start by creating a new file named ActivityBindingModule.kt in di and add the following code:

@Module
interface ActivityBindingModule { // 1

  @ContributesAndroidInjector(  // 3
    modules = [
      ActivityModule::class,
      BusStopFragmentModule::class,
      BusArrivalFragmentModule::class
    ]
  )
  @ActivityScope // 4
  fun mainActivity(): MainActivity // 2

  @ContributesAndroidInjector(  // 3
    modules = [
      ActivityModule::class
    ]
  )
  @ActivityScope
  fun splashActivity(): SplashActivity // 2
}

In this code, you:

  1. Define a simple Dagger @Module, implemented as an interface.
  2. Create an abstract function that has the injection target type as the return type. In this case, mainActivity() returns MainActivity and splashActivity() returns SplashActivity.
  3. Use @ContributesAndroidInjector to ask Dagger Android to generate the @Subcomponent and @Module, which, until now, you had to write yourself. The @Modules you define here become the @Modules you use in the related @Subcomponents.
  4. Bind the object in the @Subcomponent to a @Scope.

You no longer need the previous definitions, so you can delete the following files:

  • MainActivityModule.kt
  • MainActivitySubcomponent.kt
  • SplashActivityModule.kt
  • SplashActivitySubcomponent.kt

As your final step, you just need to replace the previous @Modules with the new one in ApplicationComponent.kt, like this:

@Component(
  dependencies = [NetworkingConfiguration::class],
  modules = [
    ApplicationModule::class,
    InformationPluginEngineModule::class,
    InformationSpecsModule::class,
    ActivityBindingModule::class // HERE
  ]
)
@ApplicationScope
interface ApplicationComponent : AndroidInjector<Main> {
  // ...
}

Now, build and run to verify that everything works as expected.

Using @ContributesAndroidInjector with Fragments

@ContributesAndroidInjector also works for Fragments. To see how, create a new file named FragmentBindingModule.kt in di with the following code:

@Module
interface FragmentBindingModule {

  @ContributesAndroidInjector(
    modules = [
      FragmentModule::class
    ]
  )
  @FragmentScope
  fun busStopFragment(): BusStopFragment

  @ContributesAndroidInjector(
    modules = [
      FragmentModule::class
    ]
  )
  @FragmentScope
  fun busArrivalFragment(): BusArrivalFragment
}

This code has the same structure as ActivityBindingModule.kt except that it relates to Fragments instead of Activitys.

Now, just as you did for the Activitys, you can delete the following files:

  • BusStopFragmentModule.kt
  • BusStopFragmentSubcomponent.kt
  • BusArrivalFragmentModule.kt
  • BusArrivalFragmentSubcomponent.kt

Your last step is to fix how you use FragmentBindingModule in ActivityBindingModule.kt, by applying the following change:

@Module
interface ActivityBindingModule {

  @ContributesAndroidInjector(
    modules = [
      ActivityModule::class,
      FragmentBindingModule::class // HERE
    ]
  )
  @ActivityScope
  fun mainActivity(): MainActivity
  // ...
}

Congratulations! Busso is now a fully Dagger Android app.

Key points

  • Android is responsible for the lifecycle of its standard components, like Activitys, Services, BroadcastReceivers and ContentProviders. This prevents you from using constructor injection.
  • Dagger Android is Google’s first solution for reducing the boilerplate code you need to write to inject into Android standard components. The next solution is Hilt, which you’ll learn about in the rest of this book.
  • Using Dagger Android requires a deep knowledge of Dagger and, specifically, how @Subcomponents and multibinding work.
  • Dagger Android provides some utility classes to reduce the code you need to write.
  • @ContributesAndroidInjector allows you to automatically generate the code for AndroidInjector<T>’s @Subcomponent and @Module for a standard component.

Wow! This has been intense, but you managed to learn everything you need about Dagger Android. Now you’re ready for the next generation of dependency injection in Android: Hilt!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.