Chapters

Hide chapters

Dagger by Tutorials

First Edition · Android 11 · Kotlin 1.4 · AS 4.1

11. Components & Scopes
Written by Massimo Carli

In the previous chapter, you migrated the Busso App from a homemade framework with ServiceLocators and Injectors to Dagger. You converted Injectors into @Components and ServiceLocators into @Modules, according to their responsibilities. You learned how to manage existing objects with types like Context or Activity by using a @Component.Builder or @Component.Factory.

However, the migration from the previous chapter isn’t optimal — there are still some fundamental aspects you need to improve. For instance, in the current code, you:

  • Create a new instance of BussoEndpoint every time you need a reference to an object of type BusStopListPresenter or a BusArrivalPresenter that depends on it. Instead, you should have just one instance of the endpoint, which lives as long as the app does.
  • Use the @Singleton annotation to solve the problem of multiple instances of BusStopListPresenterImpl that are bound to the BusStopListPresenter and BusStopListViewBinder.BusStopItemSelectedListener abstractions. However, @Singleton isn’t always a good solution, and you should understand when to use it and when not to.
  • Get the reference to LocationManager from an Activity, but it should have a broader lifecycle, like the app does, and it should also depend on the app Context.

These are just some of the problems you’ll fix in this chapter. You’ll also learn:

  • The definition of a component and how it relates to containers.
  • What a lifecycle is, why it’s important and what its relationship to scope is.
  • More about @Singletons.
  • What a @Scope is and how it improves your app’s performance.

It’s going to be a very interesting and important chapter, so get ready to dive in!

Components and Containers

It’s important to understand the concept behind components. When you ask what a component is in an interview, you usually get different answers. In the Java context, one of the most common answers is, “A component is a class with getters and setters.” However, even though a component’s implementations in Java might have getters and setters, the answer is incorrect.

Note: The getter and setter thing is probably a consequence of the JavaBean specification that Sun Microsystems released way back in 1997. A JavaBean is a Java component that’s reusable and that a visual IDE can edit. The last property is the important one. An IDE that wants to edit a JavaBean needs to know what the component’s properties, events and methods are. In other words, the component needs to describe itself to the container, either explicitly — by using BeanInfo — or implicitly. To use the implicit method, you need to follow some conventions, one of which is that a component has the property prop of type T if it has two methods — getProp(): T and setProp(T). Because of this, a JavaBean can have getters and setters — but even when a class has getters and setters, it’s not necessarily a JavaBean.

The truth is that there’s no component without a container. In the relationship between the components and their container:

  1. The container is responsible for the lifecycle of the components it contains.
  2. There is always a way to describe the component to the container.
  3. Implementing a component means defining what do to when its state changes according to its lifecycle.

A related interview question in Android is, “What are the standard components of the Android platform?” The correct answer is:

  • Activity
  • Service
  • ContentProvider
  • BroadcastReceiver

In this case, the following applies to the components:

  1. The Android environment is the container that manages the lifecycle of standard components according to the available system resources and user actions.
  2. You describe these components to the Android Environment using AndroidManifest.xml.
  3. When you define an Android component, you provide implementations for some callback functions, including onCreate(), onStart(), onStop() and so on. The container invokes these to send a notification that there’s a transition to a different state in the lifecycle.

Note: Is a Fragment a standard component? In theory, no, because the Android Environment doesn’t know about it. It’s a component that has a lifecycle bound to the lifecycle of the Activity that contains it. From this perspective, it’s just a class with a lifecycle, like any other class. But because it’s an important part of any Android app, as you’ll see, it has the dignity of a specific scope.

Figure 11.1 — The Android Environment as Container
Figure 11.1 — The Android Environment as Container

But why, then, do you need to delegate the lifecycle of those components to the container — in this case, the Android environment? Because it’s the system’s responsibility to know which resources are available and to decide what can live and what should die. This is true for all the Android standard components and Fragments. But all these components have dependencies.

In the Busso App, you’ve seen how an Activity or Fragment depends on other objects, which have a presenter, model or viewBinder role. If a component has a lifecycle, its dependencies do, too. Some objects need to live as long as the app and others only need to exist when a specific Fragment or Activity is visible.

As you can see, you still have work to do to improve the Busso App.

Fixing the Busso App

Open the Busso project from the starter folder in this chapter’s materials. The file structure for the project that is of interest right now is the one in Figure 11.2:

Figure 11.2 — Busso initial project structure
Figure 11.2 — Busso initial project structure

You can see that this structure has:

  • A single @Component in AppComponent.kt under the di package.
  • Two different @Modules, one in AppModule.kt in that di package with the @Component and the other in NetworkModule.kt in the network package.

Open AppComponent.kt and look at the current implementation:

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

  fun inject(activity: SplashActivity)

  fun inject(activity: MainActivity)

  fun inject(fragment: BusStopFragment)

  fun inject(fragment: BusArrivalFragment)

  @Component.Factory
  interface Factory {

    fun create(@BindsInstance activity: Activity): AppComponent
  }
}

The AppComponent interface is responsible for the entire app’s dependency graph. You have inject() overloads for all the Fragment and Activity components and you’re using all the @Modules. That means all the app’s objects are part of the same graph. This has important implications for scope.

So far, you learned that:

  • By default, Dagger creates a new instance of the class that’s bound to a specific abstraction type every time an injection of that type occurs.
  • Using @Singleton, you can only bind the lifecycle of an instance to the lifecycle of the @Component you use to get its reference.

In other words, a @Singleton IS NOT a Singleton!

Using @Singleton doesn’t give you a unique instance of a class across the entire app. It just means that each instance of a @Component always returns the same instance for an object of a given type. Different @Component instances will return different instances for the same type, even if you use @Singleton.

As you’ll see later, if you want an instance of an object that lives as long as the app does, you need a @Component that matches the app’s lifespan.

Note: Singleton is a foundational Gang Of Four Design Pattern that describes a way to have one and only one instance of a class, which you can access from any part of the app’s code. Many developers consider it an anti-pattern because of the concurrency issues that can arise, especially in a distributed environment.

To better understand this concept, your next step will be to fix the BussoEndpoint in the Busso app.

Fixing BussoEndpoint

The BussoEndpoint implementation is a good place to start optimizing the Busso App. It’s a typical example of a component that needs to be unique across the entire app. To recap, BussoEndpoint is an interface which abstracts the endpoint for the application.

Understanding the problem

Before you dive into the fix, take a moment to prove that, at the moment, Dagger creates a new instance every time it needs to inject an object with the BussoEndpoint type. Using the Android Studio feature in Figure 11.3, you see that two classes depend on BussoEndpoint:

Figure 11.3 — Find BussoEndpoint injections
Figure 11.3 — Find BussoEndpoint injections

Those are:

  • BusStopListPresenterImpl
  • BusArrivalPresenterImpl

Open BusStopListPresenterImpl.kt in ui.view.busstop and add the following init block:

@Singleton
class BusStopListPresenterImpl @Inject constructor(
    private val navigator: Navigator,
    private val locationObservable: Observable<LocationEvent>,
    private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusStopListViewBinder>(),
    BusStopListPresenter {
  // HERE  	
  init {
    Log.d("BUSSOENDPOINT", "StopList: $bussoEndpoint")
  }
  // ...
}

Now, open BusArrivalPresenterImpl.kt in ui.view.busarrival and do the same thing:

class BusArrivalPresenterImpl @Inject constructor(
    private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusArrivalViewBinder>(),
    BusArrivalPresenter {
  // HERE    	
  init {
    Log.d("BUSSOENDPOINT", "Arrival: $bussoEndpoint")
  }
  // ...
}

That just added some logs that print information about the specific BussoEndpoint instance. Build and run, then navigate back and forth a few times in the arrivals fragment, and you’ll get a log like this:

D/BUSSOENDPOINT: StopList: retrofit2.Retrofit$1@68c7c92
D/BUSSOENDPOINT: Arrival: retrofit2.Retrofit$1@cb74e1b
D/BUSSOENDPOINT: Arrival: retrofit2.Retrofit$1@542346a
D/BUSSOENDPOINT: Arrival: retrofit2.Retrofit$1@dfeaf68

Now, you can clearly see that the BusStopListPresenterImpl and BusArrivalPresenterImpl objects are all using different instances for the BussoEndpoint type. It’s important to note that all the Fragments are using the same AppComponent instance through the comp extended property you defined in MainActivity.kt. You already know what to do to ensure that @Component always returns the same instance for the BussoEndpoint: Use @Singleton.

Using @Singleton

As you’ve learned, using @Singleton is the first solution to the multiple instances problem. In this specific case, however, something’s different: You can’t access the code of the class that’s bound to the BussoEndpoint interface because the Retrofit framework created it for you.

However, using Dagger gives you a way to get around this problem. Open NetworkModule.kt in the network and apply the following change:

@Module
class NetworkModule {

  @Provides
  @Singleton // HERE
  fun provideBussoEndPoint(activity: Activity): BussoEndpoint {
     // ...
  }
}

Using @Singleton in provideBussoEndPoint() achieves the same goal. You’re asking Dagger to create a single instance of the BussoEndpoint implementation. If you repeat the previous experiment with this new code, you’ll get the following output:

D/BUSSOENDPOINT: StopList: retrofit2.Retrofit$1@dc70bea
D/BUSSOENDPOINT: Arrival: retrofit2.Retrofit$1@dc70bea
D/BUSSOENDPOINT: Arrival: retrofit2.Retrofit$1@dc70bea
D/BUSSOENDPOINT: Arrival: retrofit2.Retrofit$1@dc70bea

Now, the instance for the BussoEndpoint type is always the same. Everything seems to work, but this isn’t a good solution for the reason mentioned above. Using @Singleton didn’t give you a single instance for the BussoEndpoint implementation for the entire app.

With this configuration, you’re creating an instance for BussoEndpoint for each instance of AppComponent — but you have one AppComponent per Activity. In the app, you have two of them.

To create a single BussoEndpoint instance across the entire app, you need to define a @Component with the same lifespan. This is called an application scope. You’ll learn how to define and use application scopes next.

Defining an application scope

When objects live as long as the app does, they have an application scope.

You already know what to do to make Dagger provide a single instance for a given type across the entire app. You need to define a:

  1. @Module that encapsulates the information about how to create the objects.
  2. @Component as a factory for those objects.
  3. Way to make the @Component live as long the app, and make it accessible from any other point in the code.

Look at Busso’s code and you’ll see that there are three types of objects that have an application scope:

  • BussoEndpoint
  • LocationManager
  • GeoLocationPermissionChecker

You’ll start by defining the @Module.

Note: As you know, the Observable<LocationEvent> depends on the LocationManager and GeoLocationPermissionChecker, but it’s a good practice to create a new instance every time. You could create a single instance of Observable<LocationEvent> to keep alive as long as the app, but you’d need to create a shareable version of it. To learn more about RxJava or RxKotlin, check out the book, Reactive Programming with Kotlin.

Note: As you improve the Dagger configuration, you won’t be able to successfully build and run after each step — you need to complete the refactoring for the app to run properly. But don’t worry, just code along and everything will be fine. :]

Defining ApplicationModule

This @Module tells Dagger how to create the objects it needs for the application scope. You have an opportunity to improve the structure of your code by putting your new Dagger knowledge to work.

You don’t want to have all the definitions in a single place, so your first step is to improve some of the existing files. Open NetworkModule.kt in the network and add the following:

@Module
class NetworkModule {
  // 1
  @Provides
  @Singleton
  fun provideCache(application: Application): Cache =
      Cache(application.cacheDir, 100 * 1024L)// 100K
  // 2
  @Provides
  @Singleton
  fun provideHttpClient(cache: Cache): OkHttpClient =
      Builder()
          .cache(cache)
          .build()
  // 3
  @Provides
  @Singleton
  fun provideBussoEndPoint(httpClient: OkHttpClient): BussoEndpoint {
    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(httpClient)
        .build()
    return retrofit.create(BussoEndpoint::class.java)
  }
}

This code gives you a better definition of the dependencies. In particular, you define:

  1. provideCache(), which returns the reference to the Cache implementation. It’s important to note that it has the same scope as the @Component annotated with the @Singleton that uses it. Another fundamental change is the input parameter for proviitsdeCache(): It’s now Application.
  2. provideHttpClient(), which returns the OkHttpClient that receives the Cache implementation as its parameter. You use @Singleton again here.
  3. provideBussoEndPoint(), which returns the Retrofit BussoEndpoint implementation from the OkHttpClient it receives as a parameter. You want this to be a @Singleton as well.

Note: You might wonder if Cache and OkHttpClient actually need to be @Singletons or not. In the app, you’re just consuming the BussoEndpoint object, which is the only one that needs to be @Singleton, so you could avoid the annotation on its dependencies. On the other hand, you should only have one Cache and one OkHttpClient in the app. Therefore, it’s preferable to use @Singleton on them as well.

Now, create a new file named LocationModule.kt in di and add the following code:

@Module
class LocationModule {
  // 1
  @Singleton
  @Provides
  fun provideLocationManager(application: Application): LocationManager =
      application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
  // 2
  @Singleton
  @Provides
  fun providePermissionChecker(application: Application): GeoLocationPermissionChecker =
      GeoLocationPermissionCheckerImpl(application)
  // 3
  @Provides
  fun provideLocationObservable(
      locationManager: LocationManager,
      permissionChecker: GeoLocationPermissionChecker
  ): Observable<LocationEvent> = provideRxLocationObservable(locationManager, permissionChecker)
}

This refactoring is similar to the one you did before, but it’s a bit more important. In this code, you define:

  1. provideLocationManager(), which returns the LocationManager from the Application you pass as a parameter. You only need a single LocationManager, so you use @Singleton.
  2. providePermissionChecker(), which returns the implementation for GeoLocationPermissionChecker using the Application you get as a parameter. You only need one, so you use @Singleton again.
  3. provideLocationObservable(), which returns the Observable<LocationEvent> implementation using the LocationManager and GeoLocationPermissionChecker you get as parameters. Because of the previous @Provides functions, Dagger will create a new Observable<LocationEvent> implementation from the same LocationManager and GeoLocationPermissionChecker objects it gets as input parameters.

Note that in LocationManager’s case, you don’t actually know if you need @Singleton or not. That’s because the LocationManager you get from the Context using getSystemService() might already be a Singleton in the Design Pattern sense.

Finally, you’ll merge the two modules into one as a convenience. Create a new file named ApplicationModule.kt in di and enter the following code:

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

This lets you keep all the @Modules for the application scope objects in the same place.

Note: Before implementing the @Component, remember to delete the provideLocationObservable() definition from AppModule.kt in di.

Defining ApplicationComponent

Now, you need to define the @Component for the objects with application scope. The main thing to note here is that NetworkModule and LocationModule need an Application, which is an object you don’t have to create. You provide it instead.

You already know how to do that. Create a new file named ApplicationComponent.kt in di and enter the following code:

@Component(modules = [ApplicationModule::class]) // 1
@Singleton // 2
interface ApplicationComponent {

  @Component.Factory
  interface Builder {

    fun create(@BindsInstance application: Application): ApplicationComponent // 3
  }
}

This is a very simple but important definition. Here, you:

  1. Require the information from ApplicationModule.

  2. Use @Singleton because you have bindings that should have the same lifespan as the ApplicationComponent.

  3. Define a @Component.Factory that generates a factory method, which you’ll use to create the ApplicationComponent instance from the existing Application object.

You don’t have any inject() functions in this code because you won’t use the ApplicationComponent directly. As you’ll see shortly, you just need a way to make these objects available to other @Components with different @Scopes.

For instance, both SplashActivity and BusStopListPresenterImpl need Observable<LocationEvent>, and they definitely don’t have application scope.

Note: You’ll learn all about @Component dependency in the next chapter. Here, you’ll use the dependencies attribute of the @Component annotation, which is also the approach originally implemented in Dagger.

To get the reference to an object in the dependency graph from a @Component, Dagger requires you to be explicit. Using inject()s, you ask Dagger to create the code to inject dependencies into a target object — but you don’t have direct access to the objects it injects.

To do that, Dagger asks you to provide factory methods for the objects you want to expose. To access Observable<LocationEvent> and BussoEndpoint directly from the ApplicationComponent, you need to add the following code to the @Component definition in ApplicationComponent.kt.

@Component(modules = [ApplicationModule::class])
@Singleton
interface ApplicationComponent {
  // 1
  fun locationObservable(): Observable<LocationEvent>
  // 2
  fun bussoEndpoint(): BussoEndpoint

  @Component.Factory
  interface Builder {

    fun create(@BindsInstance application: Application): ApplicationComponent
  }
}

Once you get the reference to ApplicationComponent’s instance, you just need to invoke:

  1. locationObservable() to get a reference to the Observable<LocationEvent> implementation.
  2. bussoEndpoint() to get the reference to the BussoEndpoint.

These functions also make the Observable<LocationEvent> and BussoEndpoint available to other dependent @Components, as you’ll see shortly.

Now, you need to create a unique instance of ApplicationComponent and make it available across the entire app. In later chapters, you’ll learn another way for sharing objects between different @Components with different @Scopes called @Subcomponents. For now, you’ll use Main.

The Main component

As you learned in the first section of this book, Main is the object that kicks off the creation of the dependency graph. In this case, it must be an object where you create the instance of ApplicationComponent, which keeps it alive as long as the app is. In Android, this is easy because you just need to:

  1. Create a class that extends Android’s Application.
  2. Create the ApplicationComponent instance and make it available from any point in the Busso app’s code.
  3. Register this class in AndroidManifest.xml.

Start by creating a new file named Main.kt in the main package of the app with the following code:

// 1
class Main : Application() {
  // 2
  lateinit var appComponent: ApplicationComponent

  override fun onCreate() {
    super.onCreate()
    // 3
    appComponent = DaggerApplicationComponent
        .factory()
        .create(this)
  }
}
// 4
val Context.appComp: ApplicationComponent
  get() = (applicationContext as Main).appComponent

Here, you:

  1. Create Main, which extends the Android Application.
  2. Define lateinit appComponent, which will contain a reference to ApplicationComponent.
  3. Invoke create() on the Factory you get from the static factory method, factory(). This passes the reference to the Application itself and saves the ApplicationComponent instance in the appComponent property.
  4. Define the appComp extension property for the Context type. If you try to access this property from a Context that’s not using Main as its ApplicationContext, you’ll get a crash. This is good because it lets you catch errors early in development.

To access DaggerApplicationComponent, you need to build the app. Don’t worry if the build fails, Dagger will be able to generate the code for the ApplicationComponent, anyway.

Now, open AndroidManifest.xml and apply the following change, adding the android:name attribute for the application element:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  package="com.raywenderlich.android.busso">

  <application ...
    android:name=".Main"> // HERE
    // ...
  </application>
</manifest>

You’ve now set up everything you need to manage objects with application scope. But how can you actually use them?

Check out SplashActivity and you see that it needs the reference to the SplashPresenter and SplashViewBinder implementations. SplashPresenterImpl needs the reference to Observable<LocationEvent>, but SplashViewBinderImpl needs a Navigator that depends on the Activity — and that’s not available at the Application level. That means you need another scope.

Creating a custom @Scope

In the previous section, you implemented all the code you need to manage objects with application scope. These are objects that need to live as long as the app does. You managed them with a @Component that you created by using the Application you got from Main.

You also realized that there are other objects, like Navigator, that need an Activity, instead. You need to bind the lifecycle of these objects to the lifecycle of the Activity they depend upon. Based on what you’ve learned so far, you should just follow these steps:

  1. Create the @Modules for the objects that depend on the Activity.
  2. Define a @Component that uses those @Modules and has a @Component.Builder or @Component.Factory that accepts the reference to the Activity they need.
  3. Use @Singleton for the objects you want to bind to the Activity lifecycle.
  4. Create the instance of this @Component in the onCreate() of the specific Activity implementation, as you did for the existing AppComponent.

This is exactly what you’re going to do, but with a small but very important difference: You’re going to create and use a new @Scope named @ActivityScope.

Creating @ActivityScope

As you read in the previous chapters, @Singleton is nothing special. It doesn’t say that an object is a Singleton, it just tells Dagger to bind the lifecycle of the object to the lifestyle of a @Component. For this reason, it’s a good practice to define a custom scope using the @Scope annotation.

To do this, create a new file named ActivityScope.kt in a new di.scopes package and add the following code:

@Scope // 1
@MustBeDocumented // 2
@Retention(RUNTIME) // 3
annotation class ActivityScope // 4

You already defined a custom annotation when you learned about custom qualifiers. This isn’t much different. Here, you:

  1. Use @Scope to mark this annotation as a way to define a scope. Like @Singleton, @Scope is part of Java Specification Request 330. In @Singleton’s source code, you can see it has the same @Scope annotation.
  2. Use @MustBeDocumented to mark that a Java doc should be generated for this annotation.
  3. Apply RUNTIME as a retention value. This means that you store the annotation in binary output where it’s visible for reflection.
  4. Create a simple annotation class named ActivityScope.

Now, you’re ready to use the @ActivityScope in the same way you used @Singleton. The only difference is the name.

Creating the ActivityModule

Now, you need to define a @Module for the objects with an @ActivityScope. In this case, those objects are implementation for:

  • Navigator
  • SplashPresenter
  • SplashViewBinder
  • MainPresenter

To do this, create a new file named ActivityModule.kt in di and enter the following code:

@Module(includes = [ActivityModule.Bindings::class])
class ActivityModule {

  @Module
  interface Bindings {
    @Binds
    fun bindSplashPresenter(impl: SplashPresenterImpl): SplashPresenter

    @Binds
    fun bindSplashViewBinder(impl: SplashViewBinderImpl): SplashViewBinder

    @Binds
    fun bindMainPresenter(impl: MainPresenterImpl): MainPresenter
  }

  @Provides
  @ActivityScope
  fun provideNavigator(activity: Activity): Navigator = NavigatorImpl(activity)
}

It’s important to note that you use @ActivityScope to tell Dagger it should always return the same instance of the Navigator implementation if you get the reference through a @Component that has @ActivityScope as one of the scopes it supports. At the moment, you don’t have any @Components like that — so it’s time to create one.

Remember to delete the same definitions from the existing AppModule.kt file.

Creating the ActivityComponent

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

@Component(
    modules = [ActivityModule::class] // 1
)
@ActivityScope // 2
interface ActivityComponent {

  fun inject(activity: SplashActivity) // 3

  fun inject(activity: MainActivity) // 3

  fun navigator(): Navigator // 4

  @Component.Factory
  interface Factory {
    // 5
    fun create(@BindsInstance activity: Activity): ActivityComponent
  }
}

This code should be familiar. Here, you:

  1. Create a @Component with a reference to the @Modules it needs to create the objects for its dependency graph.
  2. Use @ActivityScope to tell Dagger that the objects that use the same annotation have a lifecycle bound to an ActivityComponent.
  3. Define inject() for SplashActivity and MainActivity.
  4. Define navigator() as the factory method for Navigator. This is also necessary to make Navigator visible to its dependent @Component.
  5. Use @Component.Factory to ask Dagger to generate a factory method that creates an ActivityComponent from an Activity.

Now, take a quick look at the dependencies in Figure 11.4:

Figure 11.4 — SplashActivity dependencies
Figure 11.4 — SplashActivity dependencies

In this class diagram, you can see that SplashActivity depends on the SplashPresenter abstraction whose implementation is SplashPresenterImpl. This class depends on Observable<LocationEvent>. The problem is that SplashActivity has an activity scope while Observable<LocationEvent> has an application scope.

You need a way for ActivityComponent to access the object of the ApplicationComponent graph. You’ll tackle that problem next.

Managing @Component dependencies

The problem now is finding a way to share the objects in the ApplicationComponent dependency graph with the ones in ActivityComponent. Is this a problem similar to the one you saw with existing objects? What if you think of the Observable<LocationEvent> and BussoEndpoint as objects that already exist and that you can get from an ApplicationComponent? That’s exactly what you’re going to do now. Using this method, you just need to:

  1. Tell Dagger that you need the objects from another @Component.
  2. Create a @Component.Builder or @Component.Factory that allows you to pass the reference of the dependent @Component.

Open ActivityComponent.kt, which you just created in di, and make the following changes:

@Component(
    modules = [ActivityModule::class],
    dependencies = [ApplicationComponent::class] // 1
)
@ActivityScope
interface ActivityComponent {

  fun inject(activity: SplashActivity)

  fun inject(activity: MainActivity)

  fun navigator(): Navigator

  @Component.Factory
  interface Factory {
    fun create(
        @BindsInstance activity: Activity,
        applicationComponent: ApplicationComponent // 2
    ): ActivityComponent
  }
}

In this code, you:

  1. Use the dependencies@Component attribute to tell Dagger which dependent @Components it needs to build its dependency graph.
  2. Add a new applicationComponent parameter of type ApplicationComponent to the static factory method for the ActivityComponent. Note that you’re not using @BindsInstance. As you recall, you can accomplish this by using @Modules as parameters.

This is going to change the code Dagger generates for you. To see how, you’ll use this component in SplashActivity and MainActivity.

Using the ActivityComponent

In the previous paragraph, you changed the @Component.Factory definition for the ActivityComponent. Now, you need to change how to use it in Busso’s activities.

Start by opening SplashActivity.kt and applying the following changes:

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

In this code, you:

  1. Use factory() to get the Factory implementation that Dagger created for you.
  2. Invoke create(), passing the reference to the current Activity as the first parameter. As the second parameter, you pass the reference to the ApplicationComponent that you get by using the appComp extended property you defined in Main.kt. It’s fundamental to understand that you only have one instance of appComp across the entire app.
  3. Inject the dependent objects using inject() as usual.

Now, you can do the same for MainActivity. Open MainActivity.kt in the ui.view.main and apply the following changes:

class MainActivity : AppCompatActivity() {

  @Inject
  lateinit var mainPresenter: MainPresenter

  lateinit var comp: ActivityComponent // 1

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    comp = DaggerActivityComponent // 2
        .factory()
        .create(this, this.application.appComp)
        .apply {
          inject(this@MainActivity)
        }
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }
}

val Context.activityComp: ActivityComponent // 3
  get() = (this as MainActivity).comp

In this code, you can see that:

  1. The @Component type you want to retain is ActivityComponent.
  2. You create the instance of DaggerActivityComponent as you did before — by saving ActivityComponent’s reference in the comp property.
  3. You implement the activityComp extension property to access the ActivityComponent from the Fragment the MainActivity will contain.

At this point, you’re using:

  • @Singleton in a @Component for the objects with application scope.
  • @ActivityScope in a @Component for the objects with activity scope.

What about the Fragments? Well, at this point, you should know exactly what to do — create another custom scope.

Creating the @FragmentScope

Not all of Busso’s objects will have an application or activity scope. Most of them live as long as a Fragment, so they need a new scope: the FragmentScope. Knowing that, you just repeat the same process you followed for @Singleton and @ActivityScope.

Create a new file named FragmentScope.kt in di.scopes with the following code:

@Scope
@MustBeDocumented
@Retention(RUNTIME)
annotation class FragmentScope

This defines the new @FragmentScope. The only way this code differs from @Singleton and @ActivityScope is in its name.

Now, create a new file named FragmentModule.kt in the di folder and add the following code:

@Module
interface FragmentModule {

  @Binds
  fun bindBusStopListViewBinder(impl: BusStopListViewBinderImpl): BusStopListViewBinder

  @Binds
  fun bindBusStopListPresenter(impl: BusStopListPresenterImpl): BusStopListPresenter

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

  @Binds
  fun bindBusArrivalPresenter(impl: BusArrivalPresenterImpl): BusArrivalPresenter

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

This is nothing new — you just define the bindings for the components you need in the Fragments of the Busso App. Note that this code contains the remaining definitions you had in AppModule.kt, so delete that file now.

Then, create a new file named FragmentComponent.kt in di with the following code:

@Component(
    modules = [FragmentModule::class],
    dependencies = [ActivityComponent::class, ApplicationComponent::class] // 1
)
@FragmentScope // 2
interface FragmentComponent {

  fun inject(fragment: BusStopFragment) // 3

  fun inject(fragment: BusArrivalFragment) // 3

  @Component.Factory
  interface Factory {
    // 4
    fun create(
        applicationComponent: ApplicationComponent,
        activityComponent: ActivityComponent
    ): FragmentComponent
  }
}

This code just contains the:

  1. Definition of the dependencies on the ActivityComponent and ApplicationComponent @Components. It’s important to note here that these dependencies are not transitive, so the dependency on ApplicationComponent must be explicit.
  2. @FragmentScope annotation, because some bindings need to have a lifecycle bound to this @Component.
  3. Definition of the inject() methods for the actual injection of the dependencies in BusStopFragment and BusArrivalFragment.
  4. The @Component.Factory interface that asks Dagger to generate a factory method that creates a FragmentComponent from the existing ApplicationComponent and ActivityComponent.

This allows you to finally delete AppComponent.kt in the di package.

Now, it’s time to use the FragmentComponent in Busso’s Fragments.

Using the FragmentScope

In the previous paragraph, you added a new @Component.Builder that asks Dagger to generate a custom factory method for you. You now need to create the FragmentComponent instances in BusStopFragment and BusArrivalFragment. Before doing that, it’s important to open BusStopListPresenterImpl.kt in the ui.view.busstop package and apply the following change:

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

This binds the lifecycle of BusStopListPresenterImpl to FragmentComponent with the scope @FragmentScope — which means you have to replace @Singleton with it. Otherwise, you’d prevent Dagger from generating DaggerFragmentComponent, which you’ll use in your Fragments.

Next, open BusStopFragment.kt in ui.view.busstop and apply the following change:

class BusStopFragment : Fragment() {
  // ...
  override fun onAttach(context: Context) {
    with(context) {
      DaggerFragmentComponent.factory()
          .create(applicationContext.appComp, activityComp) // HERE
          .inject(this@BusStopFragment)
    }
    super.onAttach(context)
  }
  // ...
}

The only thing to mention here is that you use the references to ApplicationComponent and ActivityComponent as parameters of the factory method for the FragmentComponent.

Now, open BusStopFragment.kt in ui.view.busstop and apply the same change:

class BusArrivalFragment : Fragment() {
  // ...
  override fun onAttach(context: Context) {
    with(context) {
      DaggerFragmentComponent.factory()
          .create(applicationContext.appComp, activityComp) // HERE
          .inject(this@BusArrivalFragment)
    }
    super.onAttach(context)
  }
  // ...
}

Now, you can finally build and run Busso and check that everything works as expected.

Key points

  • There’s no component without a container that’s responsible for its lifecycle.
  • The Android environment is the container for the Android standard components and manages their lifecycle according to the resources available.
  • Fragments are not standard components, but they have a lifecycle.
  • Android components have dependencies with lifecycles.
  • @Scopes let you bind the lifecycle of an object to the lifecycle of a @Component.
  • @Singleton is a @Scope like any other.
  • A @Singleton is not a Singleton.
  • You can implement @Component dependencies by using the dependencies @Component attribute and providing explicit factory methods for the object you want to export to other @Components.

Great job! This was another important chapter, and you managed to implement different @Scopes for Busso’s components. But you’re far from done! Dagger is evolving and there are many other important concepts to learn. See you in the next chapter.

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.