Chapters

Hide chapters

Dagger by Tutorials

First Edition · Android 11 · Kotlin 1.4 · AS 4.1

10. Understanding Components
Written by Massimo Carli

In the previous chapters, you learned how to deal with @Modules in Dagger. You learned how a @Module helps you structure the code of your app and how you can use them to control the way Dagger creates instances of the objects in the dependency graph.

You also had the opportunity to meet the most important concept in Dagger: the @Component. You learned that a @Component defines the factory methods for the objects in your app’s dependency graph. When you get a reference to an object using a @Component’s factory method, you’re confident that Dagger has resolved all its dependencies. You saw this in both the simple Server-Repository example and in the more complex RaySequence app.

In this chapter, you’ll go back to working on the Busso App. You’ll learn how to:

  • Migrate the existing ServiceLocators and Injectors to Dagger’s equivalent @Modules and @Components.
  • Provide existing objects with a customized Builder for the @Component using @Component.Builder.
  • Use @Component.Factory as a valid alternative to @Component.Builder.

These are fundamental concepts you must understand to master Dagger. They’re also a prerequisite for the next chapter, where you’ll learn all about scopes. So get ready to dive in!

Migrating Busso to Dagger

As mentioned earlier, @Components are one of the most important concepts in Dagger. As you saw in the previous examples, a @Component:

  • Is the factory for all the objects in the dependency graph.
  • Allows you to implement the object you referred to as an Injector in the first section of the book.

Don’t worry if you don’t remember everything. With Busso’s help, you’ll review all the concepts over the course of this chapter. In particular, you’ll see how to:

  • Remove the Injector implementations, delegating the actual injection of the dependent objects to the code Dagger generates for you from a @Component.
  • Replace the ServiceLocator implementations with @Modules.

To start, use Android Studio to open the Busso App from the starter folder in the downloaded materials for this chapter. As you see in the code, the app uses the model view presenter and has ServiceLocator and Injector implementations for all the Activity and Fragment definitions.

In Figure 10.1, you can see the project structure of the di package, which you’ll switch over to Dagger first.

Figure 10.1 — Starting project structure of the Busso app
Figure 10.1 — Starting project structure of the Busso app

It’s always nice to delete code in a project and make it simpler. So let the fun begin!

Installing Dagger

The first thing you need to do is to install the dependencies Dagger needs for the project. Open build.gradle from the app module and apply the following changes:

plugins {
  id 'com.android.application'
  id 'kotlin-android'
  id 'kotlin-android-extensions'
  id 'kotlin-kapt' // 1
}
apply from: '../versions.gradle'

// ...

dependencies {
  // ...
  // 2
  // Dagger dependencies
  implementation "com.google.dagger:dagger:$dagger_version"
  kapt "com.google.dagger:dagger-compiler:$dagger_version"
}

As you learned in the previous chapters, you add Dagger support to the Busso App by:

  1. Enabling the annotation processor plugin, kotlin-kapt.
  2. Adding dependencies to the Dagger library. The library’s version is already included in versions.gradle in the root folder of the project.

Select File ▸ Sync Project with Gradle Files to update the Gradle configuration in Android Studio. You can also select the icon shown in Figure 10.2, which appears as soon as you update some Gradle files:

Figure 10.2 — Sync Gradle file icon
Figure 10.2 — Sync Gradle file icon

Now, you’re ready to migrate Busso to Dagger.

Studying the dependency graph

In the previous examples, you learned that a:

  • @Component describes which objects in the dependency graph you can inject, along with all their dependencies.
  • @Module tells Dagger how to create the objects in the dependency graph.

With these definitions in mind, you know that you need to migrate the existing Injectors to @Components and ServiceLocators to @Modules. Start the migration from the SplashActivity using the dependency graph, as shown in Figure 10.3:

Figure 10.3 — SplashActivity dependency graph
Figure 10.3 — SplashActivity dependency graph

The dependency diagram in Figure 10.3 shows that:

  1. SplashActivity depends on SplashViewBinder and SplashPresenter.
  2. SplashViewBinderImpl is the class to create when you need an object of type SplashViewBinder.
  3. For the SplashPresenter type, you use SplashPresenterImpl.
  4. SplashViewBinderImpl depends on a Navigator.
  5. NavigatorImpl is the class to use for the Navigator type.
  6. SplashPresenterImpl depends on an Observable<LocationEvent> implementation.
  7. In the diagram, ObservableImpl just represents the Observable<LocationEvent> implementation you’ll get from provideRxLocationObservable().
  8. NavigatorImpl depends on the Activity.
  9. Also, the Observable<LocationEvent> implementation needs an Activity.

To migrate the Busso App to Dagger, you need to describe the dependency diagram in Figure 10.3 using Dagger annotations.

Removing the injectors

Start by opening SplashActivityInjector.kt from di.injectors and looking at the current implementation:

object SplashActivityInjector : Injector<SplashActivity> {
  // 1
  override fun inject(target: SplashActivity) {
    // 2
    val activityServiceLocator =
        target.lookUp<ServiceLocatorFactory<AppCompatActivity>>(ACTIVITY_LOCATOR_FACTORY)
            .invoke(target)
    // 3        
    target.splashPresenter = activityServiceLocator.lookUp(SPLASH_PRESENTER)
    target.splashViewBinder = activityServiceLocator.lookUp(SPLASH_VIEWBINDER)
  }
}

This code is familiar by now. Think about what it does and what the responsibilities of @Components and @Modules are. You can see that:

  1. The SplashActivityInjector defines inject() with a parameter of type SplashActivity, which is the target of the injection. Note that a Dagger @Component can do the same thing.
  2. Now, you get a reference to the ActivityServiceLocator, which is the object that knows how to get the instances of the SplashViewBinder and SplashPresenter implementations. A @Module has this responsibility in Dagger.
  3. Here, you assign SplashPresenter’s and SplashViewBinder’s references to the related properties in the SplashActivity.

This tells you that to migrate the SplashActivityInjector class to Dagger, you need to:

  1. Define a @Module that tells Dagger how to get the SplashViewBinder and SplashPresenter implementations.
  2. Define a @Component using the previous @Module, which defines inject() for SplashActivity.
  3. Use the @Component in SplashActivity.

It’s time to put this theory into code.

Creating the @Module

Create a new file named AppModule.kt in the di package and add the following code:

// 1
@Module(includes = [AppModule.Bindings::class])
class AppModule {
  // 2
  @Module
  interface Bindings {
    // 3
    @Binds
    fun bindSplashPresenter(impl: SplashPresenterImpl): SplashPresenter
    // 4
    @Binds
    fun bindSplashViewBinder(impl: SplashViewBinderImpl): SplashViewBinder
  }
}

This code should be quite familiar. In it, you:

  1. Create a new module named AppModule as a class. You’ll understand very soon why it’s a class. Here, you also include the Bindings module that’s defined in the same file. You’ve seen this pattern in previous chapters.
  2. Define the Bindings interface, because you need some abstract functions that aren’t possible in a concrete class.
  3. Use @Binds to bind SplashPresenterImpl to SplashPresenter.
  4. Do the same for SplashViewBinder and its implementation, SplashViewBinderImpl.

Creating SplashPresenter and SplashViewBinde

Now, Dagger knows which classes to use when you need an object of type SplashPresenter or SplashViewBinder. It doesn’t know how to create them, though. To start solving that problem, open SplashPresenterImpl.kt from ui.splash and look at its header:

class SplashPresenterImpl constructor( // HERE
    private val locationObservable: Observable<LocationEvent>
) : BasePresenter<SplashActivity, SplashViewBinder>(), SplashPresenter {
  // ...
}

SplashPresenterImpl uses constructor injection and needs a reference to an implementation of Observable<LocationEvent>. Here, you just need to use @Inject like this:

class SplashPresenterImpl @Inject constructor( // HERE
    private val locationObservable: Observable<LocationEvent>
) : BasePresenter<SplashActivity, SplashViewBinder>(), SplashPresenter {

Dagger now knows that when you need an object of type SplashPresenter, it has to create an instance of SplashPresenterImpl using the primary constructor, and that constructor needs an object of type Observable<LocationEvent>. How can you tell Dagger how to get what it needs? Simple, just add that information in the @Module.

Go back to AppModule.kt and apply the following changes to tell Dagger how to get a reference to an object of type Observable<LocationEvent>:

@Module(includes = [AppModule.Bindings::class])
class AppModule(
	// 1
    private val activity: Activity
) {
  // ...
  // 2
  @Provides
  fun provideLocationObservable(): Observable<LocationEvent> {
    // 3
    val locationManager = activity.getSystemService(Context.LOCATION_SERVICE) as LocationManager
    // 4
    val geoLocationPermissionChecker = GeoLocationPermissionCheckerImpl(activity)
    // 5
    return provideRxLocationObservable(locationManager, geoLocationPermissionChecker)
  }
}

In this code, you:

  1. Add a constructor parameter of type Activity. You already learned why you need that constructor in the last chapter, where you treated Context the same way.
  2. Define provideLocationObservable() , which is a @Provides method responsible for providing the Observable<LocationEvent> implementation.
  3. Use the activity you got from the AppModule primary constructor as a parameter to get the reference to LocationManager.
  4. Use activity again to create an instance of GeoLocationPermissionCheckerImpl.
  5. Use the LocationManager and GeoLocationPermissionCheckerImpl to invoke provideRxLocationObservable().

Now, Dagger has all the information it needs to create an instance of SplashPresenterImpl. But you still need to deal with SplashViewBinderImpl.

Handling SplashViewBinderImpl

Open SplashViewBinderImpl.kt from ui.splash and look at the class’ header:

class SplashViewBinderImpl( // HERE
    private val navigator: Navigator
) : SplashViewBinder {
  // ...
}

In this case, you need to do two things:

  1. Annotate the primary constructor with @Inject.
  2. Tell Dagger how to get an object of type Navigator.

Start by adding @Inject to the SplashViewBinderImpl primary constructor, like this:

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

Then, open AppModule.kt and add the following definition:

@Module(includes = [AppModule.Bindings::class])
class AppModule(
    private val activity: Activity
) {
  // ...
  @Provides
  fun provideNavigator(): Navigator = NavigatorImpl(activity) // HERE
}

Here, you create an instance of NavigatorImpl using the Activity you got from the primary constructor.

Great! It’s been a long journey, but Dagger now has all the information about the objects it needs to implement SplashActivity. It’s time to implement the @Component and get rid of SplashInjector.

Creating & using the @Component

Now that you’ve created AppModule, Dagger knows everything it needs to bind the objects for the SplashActivity implementation. Now, you need a way to access all those objects — which means it’s time to implement the @Component.

Create a new file named AppComponent.kt in the di package and add the following content:

// 1
@Component(modules = [AppModule::class])
interface AppComponent {
  // 2
  fun inject(activity: SplashActivity)
}

Again, this should look familiar. In this code, you define:

  1. An AppComponent interface annotated with @Component. It’s important to note that you’re using AppModule as a value for the modules attribute. This tells Dagger to use what’s in AppModule to create the objects it needs.
  2. inject() as the function that injects all the dependencies SplashActivity needs.

This @Component contains the same information you previously had in SplashActivityInjector.

Now, open SplashActivity.kt and apply the following changes:

class SplashActivity : AppCompatActivity() {

  @Inject // 1
  lateinit var splashViewBinder: SplashViewBinder

  @Inject // 2
  lateinit var splashPresenter: SplashPresenter

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    makeFullScreen()
    setContentView(R.layout.activity_splash)
    DaggerAppComponent.builder() // 3
        .appModule(AppModule(this)) // 4
        .build() // 5
        .inject(this)  // 6
    splashViewBinder.init(this)
  }
  // ...
}

Note: Dagger generates DaggerAppComponent when you build the app. This happens even if the configuration isn’t complete, unless some big mistake prevents it. If you don’t see DaggerAppComponent, try to build the app first.

In the previous code, you:

  1. Use @Inject to tell Dagger that SplashActivity needs a reference to an object of type SplashViewBinder in the splashViewBinder property.
  2. Do the same for the object of type SplashPresenter for the splashPresenter property.
  3. Use builder() to get the reference to the Builder Dagger has created for you, for the implementation of the AppComponent interface.
  4. Pass an instance of AppModule to the AppComponent Builder implementation. It needs this to create the Navigator and Observable<LocationEvent> implementations.
  5. Create the AppComponent implementation instance, invoking build() on the Builder.
  6. Invoke inject() on the SplashActivity for the actual injection.

Before continuing, you need to:

  • Note that you removed the existing inject() invocation on the onCreate() in SplashActivity.
  • Completely delete SplashActivityInjector.kt from the di.injectos package. You don’t need it anymore!

Congrats! You completed the first step in migrating Busso to Dagger: making SplashActivity use AppComponent to inject all its dependencies. Build and run now, and everything works as expected:

Figure 10.4 — The Busso App
Figure 10.4 — The Busso App

In a real app, you’d need to repeat the same process for MainActivity, BusStopFragment and BusArrivalFragment, getting rid of all the Injector and ServiceLocator implementations.

For this chapter, however, you have two options. You can code along and complete the migration of Busso to Dagger, giving you some valuable practice, or you can simply check the project in the final folder from the downloaded materials for this chapter. In that folder, all the work has been done for you.

If you want to take the second option, jump ahead to the Customizing @Component creation section. In that case, see you there. Otherwise, continue on.

Completing the migration

If you’re reading this, you decided to complete Busso’s migration to Dagger. Great choice! Now that SplashActivity is done, it’s time to continue the migration for the other components. You’ll notice this is really simple.

The classes you need to migrate are:

  • MainActivity
  • BusStopFragment
  • BusArrivalFragment

You’re about to delete a load of code. Buckle up!

Note: It’s important to note that the following migration is not the best. You still need to manage different @Components for different @Scopes, which isn’t ideal. Don’t worry, you’ll fix this in the next chapter.

Migrating MainActivity

Open MainActivityInjector.kt from di.injectors and look at the following code:

object MainActivityInjector : Injector<MainActivity> {
  override fun inject(target: MainActivity) {
    val activityServiceLocator =
        target.lookUp<ServiceLocatorFactory<AppCompatActivity>>(ACTIVITY_LOCATOR_FACTORY)
            .invoke(target)
    target.mainPresenter = activityServiceLocator.lookUp(MAIN_PRESENTER) // HERE
  }
}

Note: It’s curious how the MainActivity has a Presenter but no ViewBinder. All it needs to do is to display a Fragment, while the navigation responsibility is something you usually assign to the Presenter.

This code tells you that MainActivity depends on the MainPresenter abstraction. This is just one thing you can see from the complete dependency graph in Figure 10.5:

Figure 10.5 — MainActivity dependency graph
Figure 10.5 — MainActivity dependency graph

The dependency diagram above contains all the information Dagger needs to define the dependency graph. This diagram tells you that:

  1. MainActivity depends on the MainPresenter abstraction.
  2. MainPresenterImpl is the class to instantiate for the MainPresenter type.
  3. MainPresenterImpl depends on the Navigation abstraction.
  4. NavigatorImpl is the class to use as the Navigator implementation.
  5. NavigatorImpl depends on Activity.

Note that Dagger knows some of this information already, like how to bind NavigatorImpl to Navigator. What Dagger doesn’t know is how to bind MainPresenterImpl to MainPresenter.

Open AppModule.kt and add the following definition to the Bindings interface:

@Module(includes = [AppModule.Bindings::class])
class AppModule(
    private val activity: Activity
) {

  @Module
  interface Bindings {
    // ...
    @Binds
    fun bindMainPresenter(impl: MainPresenterImpl): MainPresenter // HERE
  }
  // ...
}

Here, you use @Binds to bind MainPresenterImpl to MainPresenter. Now, you need to tell Dagger how to create the instance of MainPresenterImpl. Open MainPresenterImpl.kt from ui.view.main and apply the following, now obvious, change:

class MainPresenterImpl @Inject constructor( // HERE
    private val navigator: Navigator
) : MainPresenter {
  override fun goToBusStopList() {
    navigator.navigateTo(FragmentDestination(BusStopFragment(), R.id.anchor_point))
  }
}

You use @Inject to tell Dagger that it needs to invoke the primary constructor to create an instance of MainPresenterImpl. It already knows how to provide a Navigator implementation.

Now, you need to define inject() for the MainActivity in the AppComponent. Open AppComponent.kt from di and add the following definition:

@Component(modules = [AppModule::class])
interface AppComponent {
  // ...
  fun inject(activity: MainActivity) // HERE
}

It’s very important to note that the parameter type matters. The parameter must match the target’s type for the injections. In short, you can’t use a parameter of type Activity, which would include both MainActivity and SplashActivity. Remember that you’re giving Dagger information, you’re not writing actual code; Dagger creates the code for you. Again, inject()’s name doesn’t matter, it’s just a convention.

For your last step, open MainActivity.kt from ui.view.main and apply the following changes:

class MainActivity : AppCompatActivity() {

  @Inject // 1
  lateinit var mainPresenter: MainPresenter

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    // 2
    DaggerAppComponent
        .builder()
        .appModule(AppModule(this))
        .build()
        .inject(this) // 3
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }
}

As you did for SplashActivity, you:

  1. Use @Inject to tell Dagger you want the reference to the MainPresenter implementation in the mainPresenter property.
  2. Create the AppComponent implementation using the Builder Dagger generated for you.
  3. Invoke inject() to run the actual injection on the MainActivity.

Now, build and run and check that everything works. Then, delete MainActivityInjector.kt, since you don’t it need anymore.

Migrating Busso’s fragments

Migrating BusStopFragment and BusArrivalFragment to Dagger is easy now. There’s just one small thing to consider: They both extend Fragment but they need access to the AppComponent implementation you created in MainActivity. That’s because they use classes that depend on:

  • BussoEndpoint
  • Observable<LocationEvent>

These are objects you manage at the Activity level. At this point, you need to:

  • Make the AppComponent available to the Fragments.
  • Fix the missing dependency by creating a @Module for the BussoEndpoint.
  • Inject the dependencies you need into the Fragments.

You’ll start by exposing AppComponent.

Exposing AppComponent to the fragments

Here, you need to make the AppComponent available to the app’s Fragments. You’ll learn how Dagger solves this problem in the following chapters. At the moment, the easiest way is to add a simple utility method.

Open MainActivity.kt and apply the following changes:

class MainActivity : AppCompatActivity() {
  // ...
  lateinit var comp: AppComponent // 1

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    // 2
    comp = DaggerAppComponent
        .builder()
        .appModule(AppModule(this))
        .build().apply {
          inject(this@MainActivity)
        }
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }
}
// 3
val Context.comp: AppComponent?
  get() = if (this is MainActivity) comp else null

In this code, you:

  1. Add the comp property of type AppComponent. This is the reference to the AppComponent instance you create for MainActivity.
  2. Create the AppComponent implementation using the Builder Dagger generated for you, then save its reference in the comp property.
  3. Define comp as an extension property for the Context type. If the Context receiver IS-A MainActivity, it’s the reference to the AppComponent instance. Otherwise, it’s null.

Now, you just need to tell Dagger how to create an implementation for the BussoEndpoint type and then migrate the two Fragments.

Adding the NetworkModule

You’re now going to create a @Module to tell Dagger how to get an implementation of BussoEndpoint to add to the dependency graph. Create a new file named NetworkModule.kt in the network package for the app and add this content:

private val CACHE_SIZE = 100 * 1024L // 100K

@Module
class NetworkModule(val context: Context) { // HERE

  @Provides
  fun provideBussoEndPoint(): BussoEndpoint {
    val cache = Cache(context.cacheDir, CACHE_SIZE)
    val okHttpClient = OkHttpClient.Builder()
        .cache(cache)
        .build()
    val retrofit: Retrofit = Retrofit.Builder()
        .baseUrl(BUSSO_SERVER_BASE_URL)
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(
            GsonConverterFactory.create(
                GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").create()
            )
        )
        .client(okHttpClient)
        .build()
    return retrofit.create(BussoEndpoint::class.java)
  }
}

This code is very similar to the one in BussoEndpoint.kt in the same package. In that case, the signature has Context as a parameter.

fun provideBussoEndPoint(context: Context): BussoEndpoint {
  // ...
}

In NetworkModule.kt, the same function has no parameter because it uses the Context the NetworkModule receives in its primary constructor.

@Module
class NetworkModule(val context: Context) { // HERE

  @Provides
  fun provideBussoEndPoint(): BussoEndpoint {
    // ...
  }
}

You can now remove provideBussoEndPoint() from BussoEndpoint.kt. Then add NetworkModule to the values for modules @Component attribute. Open AppComponent.kt and apply the following:

@Component(modules = [AppModule::class, NetworkModule::class]) // HERE
interface AppComponent {
  // ...
}

This last change has some consequences. You’re telling Dagger that the AppComponent implementation needs a NetworkModule. This means that Dagger will generate a function in the builder for it.

Creating the NetworkModule instance

Next, open MainActivity and add the following:

class MainActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    comp = DaggerAppComponent
        .builder()
        .appModule(AppModule(this))
        .networkModule(NetworkModule(this)) // HERE
        .build().apply {
          inject(this@MainActivity)
        }
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }
}
// ...

Note: Remember to build the app if networkModule() is not available. Dagger needs to generate it from the previous configuration.

Here, you create an instance of NetworkModule, passing a reference to the MainActivity that IS-A Context. The bad news is that you have to do the same in SplashActivity.kt, which should then look like this:

class SplashActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    makeFullScreen()
    setContentView(R.layout.activity_splash)
    DaggerAppComponent.builder()
        .appModule(AppModule(this))
        .networkModule(NetworkModule(this)) // HERE
        .build()
        .inject(this)
    splashViewBinder.init(this)
  }
  // ...
}

Note: Yeah. There’s a lot of repetition here. Don’t worry, you’ll get rid of all of this very soon.

At this point, the BussoEndpoint implementation is part of the dependency graph and you have everything you need to quickly complete the migration.

Handling deprecated @Modules

Before proceeding, it’s worth mentioning that the editor might display something like you see in Figure 10.6:

Figure 10.6 — A deprecated @Module
Figure 10.6 — A deprecated @Module

Here, Android Studio marks networkModule() as deprecated. That’s because you’re not using the bindings in the NetworkModule — because you haven’t yet completed the migration for the Fragments. You can just ignore this warning for now.

Migrating BusStopFragment

Your next step is to quickly migrate the BusStopFragment following the same process you used above. Open AppModule.kt and add the following bindings:

@Module(includes = [AppModule.Bindings::class])
class AppModule(
    private val activity: Activity
) {

  @Module
  interface Bindings {
    // ...
    @Binds
    fun bindBusStopListViewBinder(impl: BusStopListViewBinderImpl): BusStopListViewBinder

    @Binds
    fun bindBusStopListPresenter(impl: BusStopListPresenterImpl): BusStopListPresenter

    @Binds
    fun bindBusStopListViewBinderListener(impl: BusStopListPresenterImpl): BusStopListViewBinder.BusStopItemSelectedListener
  }
  // ...
}

It’s important to note that you’re not only binding BusStopListPresenterImplto the BusStopListPresenter type, but also to BusStopListViewBinder.BusStopItemSelectedListener. You have to be careful that Dagger uses the same instance for the two bindings.

Note: You had the same problem with the RaySequence app, but you solved it using @Singleton.

Now, you need to use @Inject the primary constructor for BusStopListPresenterImpl and BusStopListViewBinderImpl .

Open BusStopListPresenterImpl.kt and add the following:

@Singleton // 1
class BusStopListPresenterImpl @Inject constructor( // 2
    private val navigator: Navigator,
    private val locationObservable: Observable<LocationEvent>,
    private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusStopListViewBinder>(),
    BusStopListPresenter {
  // ...
}

Note that Dagger knows all about the primary constructor parameter types. Remember to use @Singleton to ensure you’re using the same instance of BusStopListPresenterImpl for BusStopListPresenter and for BusStopListViewBinder.BusStopItemSelectedListener.

Finally, do the same in BusStopListViewBinderImpl.kt:

class BusStopListViewBinderImpl @Inject constructor( // HERE
    private val busStopItemSelectedListener: BusStopListViewBinder.BusStopItemSelectedListener
) : BusStopListViewBinder {
  // ...
}

Using AppComponent in BusStopFragment

Your last step is to use AppComponent in BusStopFragment. First, open AppComponent.kt and add the following definitions:

@Component(modules = [AppModule::class, NetworkModule::class])
@Singleton // 1
interface AppComponent {
  // ...
  fun inject(fragment: BusStopFragment) // 2
}

Here, you’re:

  1. Using @Singleton because of BusStopListPresenterImpl. As you read earlier, you need this to bind the lifecycle of BusStopListPresenterImpl to AppComponent’s lifecycle.
  2. Adding inject() for the BusStopFragment.

Then, open BusStopFragment.kt and apply the following changes:

class BusStopFragment : Fragment() {

  @Inject // 1
  lateinit var busStopListViewBinder: BusStopListViewBinder

  @Inject // 1
  lateinit var busStopListPresenter: BusStopListPresenter

  override fun onAttach(context: Context) {
    context.comp?.inject(this) // 2
    super.onAttach(context)
  }
  // ...
}

Here, you use:

  1. @Inject for the busStopListViewBinder and busStopListPresenter properties.
  2. The extended property, comp, to access the AppComponent implementation in the MainActivity and then to invoke the inject().

Now, repeat the same process for BusArrivalFragment.

Migrating BusArrivalFragment

You’re very close to completing the migration of Busso to Dagger. Open AppModule.kt and add the following bindings:

@Module(includes = [AppModule.Bindings::class])
class AppModule(
    private val activity: Activity
) {

  @Module
  interface Bindings {
    // ...
    @Binds
    fun bindBusArrivalPresenter(impl: BusArrivalPresenterImpl): BusArrivalPresenter

    @Binds
    fun bindBusArrivalViewBinder(impl: BusArrivalViewBinderImpl): BusArrivalViewBinder
  }
  // ...
}

Now, open BusArrivalPresenterImpl.kt and add @Inject, like this:

class BusArrivalPresenterImpl @Inject constructor( // HERE
    private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusArrivalViewBinder>(),
    BusArrivalPresenter {
  // ...
}

Do the same for BusArrivalViewBinderImpl.kt, like this:

class BusArrivalViewBinderImpl @Inject constructor() : BusArrivalViewBinder { // HERE
  // ...
}

Again, open AppComponent.kt and add the following definition:

@Component(modules = [AppModule::class, NetworkModule::class])
interface AppComponent {
  // ...
  fun inject(fragment: BusArrivalFragment) // HERE
}

Finally, open BusArrivalFragment and apply the following changes:

class BusArrivalFragment : Fragment() {
  // ...
  @Inject // 1
  lateinit var busArrivalViewBinder: BusArrivalViewBinder

  @Inject // 1
  lateinit var busArrivalPresenter: BusArrivalPresenter

  override fun onAttach(context: Context) {
    context.comp?.inject(this) // 2
    super.onAttach(context)
  }
  // ...
}

Here, you follow the same approach you used for BusStopFragment. Build the project now. You’ll see some compilation errors, but only because you need some clean-up. :]

Cleaning up the code

The errors you see after building the app are due to the existing ServiceLocator and Injector implementations. To fix them, you just have to delete the:

  • injectors package
  • locators package
  • Main.kt file in the app’s main package and its reference in AndroidManifest.xml
  • ServiceLocatorImplTest in the di.locators package in the test build type

After this, the directory structure for the project will match Figure 10.7.

Figure 10.7 — File structure after Dagger migration
Figure 10.7 — File structure after Dagger migration

Now, you can finally build and run — and the app will work as expected.

Great job! You’ve completely migrated Busso to Dagger.

Customizing @Component creation

As you saw in the previous code, providing the reference to existing objects like Context or Activity isn’t uncommon. In your case, you just provided an activity as a parameter for the primary constructor of the @Module, as in AppModule.kt:

@Module(includes = [AppModule.Bindings::class])
class AppModule(
    private val activity: Activity // HERE
) {
  // ...
}

This method lets you use the existing object — activity, in this case — in any @Provides function in the same AppModule.kt. That’s because activity acts like a normal property of AppModule.

Then, you need to explicitly create the @Module instance and use it during the building process for the @Component, as you did in SplashActivity.kt:

class SplashActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    makeFullScreen()
    setContentView(R.layout.activity_splash)
    DaggerAppComponent.builder()
        .appModule(AppModule(this)) // HERE
        .networkModule(NetworkModule(this)) // HERE
        .build()
        .inject(this)
    splashViewBinder.init(this)
  }
  // ...
}

Here, you also had to create the NetworkModule for the same purpose: to provide an implementation of Context. It would be nice if you could pass the reference to an existing object to @Component by adding it directly to the dependency graph. That would make the provided object accessible to any other objects that depend on it. Fortunately, you can actually do that by using @Component.Builder and @Component.Factory.

Using @Component.Builder

Open AppComponent.kt and add the following code:

@Component(modules = [AppModule::class, NetworkModule::class])
interface AppComponent {
  // ...
  // 1
  @Component.Builder
  interface Builder {

    @BindsInstance // 2
    fun activity(activity: Activity): Builder

    fun build(): AppComponent // 3
  }
}

These few lines of code are very important. In the AppComponent interface, you:

  1. Add Builder as an internal interface annotated with @Component.Builder. That tells Dagger you want to customize the code it’ll generate as Builder of the AppComponent implementation.
  2. Define a function that accepts a unique parameter of the type you want to provide. In this case, you define activity() with a parameter of type Activity. This function must return the same Builder because it’s going to generate one of the methods you must invoke to pass the existing object to the @Component. To make that object available to the dependency graph, you must annotate the function with @BindsInstance. In short, here you tell Dagger that your AppComponent needs an Activity and that you’re providing its reference by invoking activity() on the Builder implementation Dagger generates for you.
  3. Must have a unique function with no parameters, returning a reference to the AppComponent. The name of the function doesn’t matter.

If you don’t use the @BindsInstance annotation, you’ll get the not-so-clear message: error: @Component.Builder has setters for modules or components that aren’t required.

Build the app now and you’ll get some compilation errors. This happens because you told Dagger that you want to provide a custom Builder for the AppComponent implementation that ignores AppModule and NetworkModule.

One solution is to implement the exact same Builder Dagger would. Just replace the previous Builder implementation with the following:

@Component(modules = [AppModule::class, NetworkModule::class])
interface AppComponent {
  // ...
  @Component.Builder
  interface Builder {

    fun appModule(appModule: AppModule): Builder

    fun networkModule(networkModule: NetworkModule): Builder

    fun build(): AppComponent
  }
}

Now, you can successfully build and run because the custom Builder implementation you configured is exactly the same as the one Dagger would have created without the @Component.Builder. The appModule() and networkModule functions have the same name.

Note: It’s important to know that it’s not mutually exclusive to pass the reference to both a @Module and an existing object. You could have both in your Builder. Also note that, in this case, you don’t need to use @BindsInstance.

Of course this isn’t what you want. Restore the previous version of Builder in AppComponent.kt and apply the following changes to AppModule.kt:

@Module(includes = [AppModule.Bindings::class])
class AppModule { // 1
  // ...
  @Provides // 2
  fun provideNavigator(activity: Activity): Navigator = NavigatorImpl(activity)

  @Provides // 3
  fun provideLocationObservable(activity: Activity): Observable<LocationEvent> {
    val locationManager = activity.getSystemService(Context.LOCATION_SERVICE) as LocationManager
    val geoLocationPermissionChecker = GeoLocationPermissionCheckerImpl(activity)
    return provideRxLocationObservable(locationManager, geoLocationPermissionChecker)
  }
}

In this code, you:

  1. Remove the activity constructor parameter of the AppModule class.
  2. Pass the Activity as the parameter for provideNavigator().
  3. Do the same for provideLocationObservable().

You basically treat the Activity type as something that’s already part of the dependency graph for the @Component.

Now, open NetworkModule.kt and make the same change, like this:

@Module
class NetworkModule { // 1

  @Provides // 2
  fun provideBussoEndPoint(activity: Activity): BussoEndpoint {
    val cache = Cache(activity.cacheDir, CACHE_SIZE)
    // ...
  }
}

In this code, you:

  1. Remove the constructor parameter of type Context.
  2. Use a parameter of type Activity in provideBussoEndPoint().

Build the app now, and you’ll still get some compilation errors because the Builder implementation for the AppComponent has changed. To fix this, open MainActivity.kt and apply the following changes:

class MainActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    comp = DaggerAppComponent
        .builder()
        .activity(this) // HERE
        .build().apply {
          inject(this@MainActivity)
        }
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }
}
// ...

In the code above, you simply use activity() to pass the reference to the existing object to the dependency graph. Because of this, NetworkModule also gets the reference to the same object.

Before you build and run, you also need to apply the same change to SplashActivity.kt, like this:

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

Now you can finally build and run the app successfully.

Using @Component.Factory

Using @Component.Builder, you learned how to customize the Builder implementation for the @Component Dagger creates for you. Usually, you get the reference to the Builder implementation, then you invoke some setter methods that pass the parameter you need and finally, you invoke build() to create the final object.

Dagger also allows you to implement a factory as a way to generate a factory method. You can use this to create an instance of the @Component that invokes a simple function to pass all the parameters at the same time.

Open AppComponent.kt and replace the @Component.Builder definition with the following:

@Component(modules = [AppModule::class, NetworkModule::class])
interface AppComponent {
  // ...
  @Component.Factory // 1
  interface Factory {
    // 2
    fun create(@BindsInstance activity: Activity): AppComponent
  }
}

In this case, you define:

  1. The Factory interface and annotate it with @Component.Factory.
  2. A create() function with a parameter of type Activity. create() can have any parameters you need, but it must return an object with the same type as the @Component. In this case, the return type is AppComponent. @BindsInstance here has the same meaning you learned in the previous paragraph: If you use it for a parameter, the provided value becomes part of the dependency graph and is available for injection.

Build and run the project and you’ll get some compilation errors. That’s because Dagger generates different code now. To fix this, open SplashActivity.kt and apply the following changes:

class SplashActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    makeFullScreen()
    setContentView(R.layout.activity_splash)
    DaggerAppComponent
        .factory() // 1
        .create(this) // 2
        .inject(this) // 3
    splashViewBinder.init(this)
  }
  // ...
}

In this code, you invoke:

  1. factory() to get the reference to the Factory for the AppComponent implementation.
  2. create(), passing the Activity it needs to create the AppComponent implementation.
  3. inject() for the actual injection.

Of course, you need to do the same for MainActivity as well, applying the same changes:

class MainActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    comp = DaggerAppComponent
        .factory() // HERE
        .create(this).apply {
          inject(this@MainActivity)
        }
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }
}
// ...

Note that @Component.Builder and @Component.Factory are basically two different ways of doing the same thing. A rule of thumb about which one to use involves the number of objects you need to provide for the actual construction of the @Component implementation.

In both cases, you can pass one of these objects as optional by using @Nullable as its parameter. @Component.Factory allows you to write less code, passing all the parameters in one call, while @Component.Builder is a little bit more verbose.

Great job! In this chapter, you finally migrated the Busso App from your homemade framework to Dagger. This long, and occasionally repetitive process reduced the number of lines of code in your project. Well, at least the lines of code you wrote.

The Dagger configuration you used in this chapter is still not optimal, however. To use your resources more efficiently, some components should have different lifecycles than others. While the BussoEndpoint should live as long as the app, the Navigator lifecycle should be bound to the Activity lifecycle.

In the next chapter, you’ll make more important changes. See you there!

Key points

  • The most important concept to understand in Dagger is the @Component, which works as the factory for the objects in the dependency graph.
  • You migrated Injectors to Dagger @Components and ServiceLocators to Dagger @Modules.
  • A dependency diagram helps you migrate an existing app to Dagger.
  • @Component.Builder lets you customize the Builder implementation that Dagger creates for your @Component.
  • With @Component.Factory, you ask Dagger to create a factory method to create your @Component implementation.
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.