12.
Components Dependencies
Written by Massimo Carli
In the previous chapter, you achieved a very important goal: You optimized the Busso App by defining different @Components with different @Scopes. Using the existing @Singleton as well as custom @ActivityScopes and @FragmentScopes, you gave each object the right lifecycle, optimizing the app’s resources. In the process, you had the opportunity to experience what component dependency means.
In this chapter, you’ll learn even more about @Components and dependencies. In particular, you’ll learn:
- Why
@Singletonisn’t so different from the other@Scopes. - Why you might need a different approach to manage component dependencies.
- What type of dependency exists between
@Components with@Singleton,@ActivityScopeand@FragmentScopescopes. - How to use
@Subcomponent.Builderand@Subcomponent.Factoryand why you might need them. - When and how to use the
@Reusableannotation.
You still have a lot to learn.
Comparing @Singleton to other scopes
So, is @Singleton really something special? As you saw in the previous chapter, @Singleton is like any other @Scope. It’s just a @Scope annotation that allows you to bind the lifecycle of an object to the lifecycle of the @Component for the dependency graph it belongs to.
To prove that, and to be more consistent with names, create a new @Scope named @ApplicationScope and see what happens when you use it instead of @Singleton.
Create a new file named ApplicationScope.kt in the di.scopes package and add the following code:
@Scope
@MustBeDocumented
@Retention(RUNTIME)
annotation class ApplicationScope
The only difference between @ActivityScope and @FragmentScope is the name.
Now, open ApplicationComponent.kt in di and replace @Singleton with @ApplicationScope:
@Component(modules = [ApplicationModule::class])
@ApplicationScope // HERE
interface ApplicationComponent {
// ...
}
Now, you have to do the same in LocationModule.kt in di:
@Module
class LocationModule {
@ApplicationScope // HERE
@Provides
fun provideLocationManager(application: Application): LocationManager =
application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
@ApplicationScope // HERE
@Provides
fun providePermissionChecker(application: Application): GeoLocationPermissionChecker =
GeoLocationPermissionCheckerImpl(application)
// ...
}
Finally, apply the same changes in NetworkModule.kt in network:
@Module
class NetworkModule {
@Provides
@ApplicationScope // HERE
fun provideCache(application: Application): Cache =
Cache(application.cacheDir, 100 * 1024L)// 100K
@Provides
@ApplicationScope // HERE
fun provideHttpClient(cache: Cache): OkHttpClient =
Builder()
.cache(cache)
.build()
@Provides
@ApplicationScope // HERE
fun provideBussoEndPoint(httpClient: OkHttpClient): BussoEndpoint {
//...
}
}
Now, you can build and run Busso to prove that nothing’s changed, other than using more consistent naming in the app’s @Scopes.
Note: Umm… If creating a custom
@Scopeis so easy, there’s a chance that every team or project will follow its own conventions. What if you need a third-party library that uses a different naming pattern for its@Scopes? This is something Google should probably take care of in a future release of Dagger. :]
Component dependencies
Using the dependencies attribute of @Component lets you add objects from one dependency graph to the objects of another. It’s a kind of graph composition. If @Component A needs some objects that are part of the dependency graph of @Component B, you just add B as one of the dependencies for A.
To do this, the objects of B need to be explicitly exposed using factory methods. You can see this in Figure 12.1, where @Component B exposes the green objects to the other @Components. This is also why you had to add ApplicationComponent as a dependency of FragmentComponent.
While this is good to know, you might wonder if it’s possible to implement something similar to an inheritance relationship between different @Components.
Something that allows you to say that @Component A IS-A @Component B, and so inherits all the objects in its dependency graph, as shown in Figure 12.2.
That’s where @Subcomponents come in. Soon, you’ll learn all about what a @Subcomponent is and how it differs from a @Component. Before you dive into that, however, it’s important to quickly recap what you have in Busso.
Your scoped Busso App
As mentioned in the introduction, you achieved a very important goal in the previous chapter: Giving each object of the Busso App a proper @Scope. You implemented this by creating three different @Components:
ApplicationComponentActivityComponentFragmentComponent
for three different @Scopes:
@ApplicationScope@ActivityScope@FragmentScope
That was quite straightforward. The most interesting part is the way objects from different components depend on each other. Objects with @ApplicationScope should be available to objects with @ActivityScope and @FragmentScope, while objects with @ActivityScope should be also accessible from objects with @FragmentScope.
You can represent the relationship between objects with @ApplicationScope and @ActivityScope, as shown in Figure 12.3:
This diagram shows two different @Components:
ActivityComponentApplicationComponent
It also tells you many interesting things:
-
The «Provided» stereotype tells you which objects you need to explicitly provide when you create an instance of a
@Component. For instance, you need to provide anApplicationwhen you create an@ApplicationComponentand anActivitywhen you create anActivityComponent. -
When you create the
ActivityComponentinstance, you have to provide the instance of theApplicationComponentit depends on. That’s whyApplicationComponenthas theProvidedstereotype. The same is true for theActivityComponentthat you need to provide to theFragmentComponent— although, to keep the diagram clean and clear, that isn’t shown above. -
Objects with a gray background are private to the
@Component. This means that these objects aren’t directly visible to other@Components using the dependencies attribute. This is important because you can only access objects you explicitly expose using factory methods, as you did in ApplicationComponent.kt:@Component(modules = [ApplicationModule::class]) @ApplicationScope interface ApplicationComponent { fun locationObservable(): Observable<LocationEvent> // HERE fun bussoEndpoint(): BussoEndpoint // HERE @Component.Factory interface Builder { fun create(@BindsInstance application: Application): ApplicationComponent } }
Here, you can see:
- Objects with a green background are public.
@Componentexplicitly exposes them to dependent@Components using factory methods. This is whyObservable<LocationEvent>andBussoEndpointare part of the dependency graph forActivityComponentandFragmentComponent. For the same reason, objects inFragmentComponentcan inject aNavigatorimplementation.
If you consider only the provided and public objects, Figure 12.4 shows what happens for the FragmentComponent:
This diagram shows how:
- The implementation for
BusStopListPresenterdepends on objects with different scopes. Specifically:Navigator, which has@ActivityScopeandObservable<LocationEvent>andBussoEndpoint, which both have@ApplicationScope. - All of
FragmentComponent’s objects are private to the@Componentbecause no other@Components depend on it.
This is all well and good, but Dagger allows you to implement the same thing using @Subcomponents, creating a sort of inheritance relationship among dependency graphs with different @Scopes.
Using @Subcomponents
So, you’ve just learned in detail how the objects of different @Components of the Busso App depend on one other. You also learned that Busso requires some kind of inheritance relationship between the @Components because you want to use the objects from the ApplicationComponent in the ActivityComponent and in the FragmentComponent. You also want to use the objects of the ActivityComponent in the FragmentComponent.
To do all this, you need an inheritance relationship like the one in Figure 12.5:
Dagger allows you to implement that relationship using @Subcomponents, but doing so requires some important changes in Busso. You need to:
- Change the dependent
@Components to@Subcomponents to tell Dagger that an inheritance relationship is taking place. - You need to create the dependency and use it to tell Dagger which
@Componentor@Subcomponentyou’re inheriting from. As you’ll see very soon, you do this using a tool you’ve used before: factory methods. - Use
@Subcomponent.Builderor@Subcomponent.Factoryin the@Subcomponents when you need to provide an existing object. - Update the way you create the
@Components for the different@Scopes.
Note: As often happens, you won’t be able to successfully build the app until the end of the migration. Just code along and everything will be fine.
For your next step, you’ll start identifying which @Components should be @Subcomponents.
Migrating @Components to @Subcomponents
Busso will contain one @Component and two different @Subcomponents, which follows the hierarchy in Figure 12.5. Open ActivityComponent.kt from di and apply the following change:
// 1
@Subcomponent(
modules = [ActivityModule::class]
// 2
)
@ActivityScope
interface ActivityComponent {
// ...
}
It’s very important to note that you need to:
- Replace
@Componentwith@Subcomponent. - Delete the dependencies attribute since
@Subcomponentdoesn’t support it.
Now, open FragmentComponent.kt in di and apply the same change:
// 1
@Subcomponent(
modules = [FragmentModule::class]
// 2
)
@FragmentScope
interface FragmentComponent {
// ...
}
Here you do exactly the same thing:
- Replace
@Componentwith@Subcomponent. - Delete the dependencies attribute.
So far, you’re just telling Dagger that ActivityComponent and FragmentComponent are @Subcomponents, but you haven’t told Dagger anything about their parents. In the next step, you’ll create that relationship by adding a factory method for the @Subcomponent to the parent @Components.
Creating the @Subcomponent relationship
For your next step, you need to tell Dagger that:
-
ActivityComponentis a@SubcomponentofApplicationComponent. -
FragmentComponentis a@SubComponentofActivityComponent.
Usually, you’d just need to provide a factory method for the @Subcomponent. For ApplicationComponent, you’d add something like this:
@Component(modules = [ApplicationModule::class])
@ApplicationScope
interface ApplicationComponent {
fun activityComponent(): ActivityComponent // HERE
// ...
}
Unfortunately, in Busso’s case, you need a bit more because ActivityComponent needs an Activity. So what if, instead of creating a method that returns the ActivityComponent, you define a method to get the Factory or the Builder for it? This is what you can use to create or build the ActivityComponent given an Activity.
This is actually how Dagger works. Open ApplicationComponent.kt in di and add the following definition:
@Component(modules = [ApplicationModule::class])
@ApplicationScope
interface ApplicationComponent {
fun activityComponentFactory(): ActivityComponent.Factory // HERE
// ...
}
It might be a little weird to have a factory method that returns a factory, but that’s exactly what the code above does. :]
You now need to do the same for ActivityComponent but, in this case, the FragmentComponent doesn’t need any existing objects other than the one it inherits from the other @Components.
Open ActivityComponent in di and add the following definition:
@Subcomponent(
modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {
// ...
fun fragmentComponent(): FragmentComponent // HERE
}
Now, you’ve created the @Subcomponent relationship between ApplicationComponent and ActivityComponent and the same relationship between ActivityComponent and FragmentComponent. Because ActivityComponent needs an Activity, you’ve defined a factory method for its Factory.
FragmentComponent doesn’t need anything specific, so you just define a factory method for it.
Because of this change in the way you create the @Components, you now need to make some changes to the Factory and Builder.
Using @Subcomponent.Builder & @Subcomponent.Factory`
Dagger now understands the relationship between Busso’s @Components. Before you go any farther, however, you need to do some clean-up. Start by opening FragmentComponent.kt in di and change it like this:
@Subcomponent(
modules = [FragmentModule::class]
)
@FragmentScope
interface FragmentComponent {
fun inject(fragment: BusStopFragment)
fun inject(fragment: BusArrivalFragment)
}
This completely deletes the definition of @Component.Factory. You can do that because, according to the @Subcomponent relationship, FragmentComponent already inherits what it needs from its parent, ActivityComponent.
The same isn’t true for ActivityComponent, because it needs an Activity that it doesn’t inherit from its parent, ApplicationComponent. To handle this, open ActivityComponent.kt in di and apply the following change:
@Subcomponent(
modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {
// ...
@Subcomponent.Factory // 1
interface Factory {
fun create(
@BindsInstance activity: Activity
// 2
): ActivityComponent
}
}
You can assume that ActivityComponent already received the objects from its parent, ApplicationComponent, so the Factory’s create() only needs one parameter. Because it’s a @Subcomponent, Dagger asks you to use either @Subcomponent.Factory or the dual @Subcomponent.Builder. Therefore, in the code above, you:
- Replace
@Component.Factorywith@Subcomponent.Factory. - Remove the existing parameter of type
ApplicationComponent, because you’ll implicitly inherit all its objects.
Note: Another option would be to use
@Subcomponent.Builderinstead. You’ll see how this works later in the chapter.
Very good! Now Dagger knows how Busso’s @Components and @Subcomponents relate to one another.
Keep in mind that if you build the app now, you’ll get some errors. That’s because Dagger generates something different than you had before, and you need to update the way you create the different @Components and @Subcomponents.
Using @Subcomponent’s generated code
Dagger should now be able to happily generate all the code to create:
ApplicationComponentActivityComponentFragmentComponent
Of course, the build still fails because you need to update your code accordingly.
Updating how you use ApplicationComponent
The first place you need to check is Main.kt in the app’s main package. This is where you create the ApplicationComponent instance.
Open the file and you’ll see the following code:
class Main : Application() {
lateinit var appComponent: ApplicationComponent
override fun onCreate() {
super.onCreate()
appComponent = DaggerApplicationComponent
.factory()
.create(this)
}
}
val Context.appComp: ApplicationComponent
get() = (applicationContext as Main).appComponent
It’s good to see that in this case, you don’t have to do anything. :] That’s because you didn’t change the way you create ApplicationComponent. Just like before, you need to create an instance of ApplicationComponent to save in the appComponent property you expose using the appComp extension property.
Now, you need to check how you create the instance of the ActivityComponent.
Updating how you use ActivityComponent
You create a new instance of the ActivityComponent in the following places:
SplashActivityMainActivity
Open SplashActivity.kt from the ui.view.splash package and apply the following change:
class SplashActivity : AppCompatActivity() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
makeFullScreen()
setContentView(R.layout.activity_splash)
application.appComp // 1
.activityComponentFactory() // 2
.create(this) // 3
.inject(this) // 4
splashViewBinder.init(this)
}
// ...
}
In this code, you now:
- Use
applicationto accessappComp. -
appCompis the reference to theApplicationComponentthat now exposesactivityComponentFactory()to get the Factory for theActivityComponent. - Invoke
create()on theactivityComponentFactory(), passing the reference of theActivityand creating theActivityComponentinstance. - Use
inject()to execute the actual injection.
Note: During this migration, you’ll need to delete the missing imports, which Android Studio displays in red.
Now, you need to do the same for MainActivity. Open MainActivity.kt from ui.view.main and apply the following:
class MainActivity : AppCompatActivity() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
comp = application.appComp // HERE
.activityComponentFactory()
.create(this)
.apply {
inject(this@MainActivity)
}
if (savedInstanceState == null) {
mainPresenter.goToBusStopList()
}
}
}
In this case, remember that you also save the ActivityComponent reference in the comp extended variable.
Now, it’s time to update the code for FragmentComponent.
Updating how you use FragmentComponent
The two places you create FragmentComponents are:
BusStopFragmentBusArrivalFragment
The change you need to make is basically the same as what you did for ActivityComponent above. Open BusStopFragment.kt in ui.view.busstop and apply the following change:
class BusStopFragment : Fragment() {
// ...
override fun onAttach(context: Context) {
context.activityComp // 1
.fragmentComponent() // 2
.inject(this) // 3
super.onAttach(context)
}
// ...
}
In this code, you:
- Access the
activityCompextended property of theContextyou receive as a parameter ofonAttach(). - Invoke
fragmentComponent()to get the reference to the newFragmentComponentinstance. - Use
inject()for the actual injection.
This is exactly what you have to do in BusArrivalFragment.kt in ui.view.busarrival:
class BusArrivalFragment : Fragment() {
// ...
override fun onAttach(context: Context) {
context.activityComp // HERE
.fragmentComponent()
.inject(this)
super.onAttach(context)
}
// ...
}
Now, you can finally build and run the app and see something similar to Figure 12.6:
Great job, but there’s still one small thing to do: some clean-up!
Cleaning up the factory methods
Using @Subcomponents, you discovered a new way to implement a @Component dependency. Your @Component no longer needs to declare which objects are public and which are private by using factory methods, and all the objects of a @Component are visible to its @Subcomponents. This allows you to remove the factory methods from:
ApplicationComponentActivityComponent
Open ApplicationComponent.kt in di and remove locationObservable() and bussoEndpoint(). The content of this file becomes:
@Component(modules = [ApplicationModule::class])
@ApplicationScope
interface ApplicationComponent {
fun activityComponentBuilder(): ActivityComponent.Builder
@Component.Factory
interface Builder {
fun create(@BindsInstance application: Application): ApplicationComponent
}
}
Next, open ActivityComponent.kt in di and remove navigator(). That file should now look like this:
@Subcomponent(
modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {
fun inject(activity: SplashActivity)
fun inject(activity: MainActivity)
fun fragmentComponent(): FragmentComponent
@Subcomponent.Factory
interface Factory {
fun create(
@BindsInstance activity: Activity
): ActivityComponent
}
}
Build and run the app to check that everything still works as expected. This proves that those factory methods are no longer necessary.
Using @Subcomponent.Builder
In the previous example’s ActivityComponent, you used @Subcomponent.Factory, but you could have used @Subcomponent.Builder, instead. To prove this, open ActivityComponent.kt from di and apply the following change:
@Subcomponent(
modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {
// ...
@Subcomponent.Builder // 1
interface Builder {
// 2
fun activity(
@BindsInstance activity: Activity
): Builder
// 3
fun build(): ActivityComponent
}
}
Here you:
- Replace
@Subcomponent.Factorywith@Subcomponent.Builder. - Add
activity()to provide the reference to theActivityyou need. This function must return the reference to theBuilderitself. - Define the
build()method theBuilderneeds to createActivityComponent.
Because you’re now using a Builder instead of a Factory, you also need to update ApplicationComponent. Open ApplicationComponent.kt in di and replace the existing activityComponentFactory() with the following:
@Component(modules = [ApplicationModule::class])
@ApplicationScope
interface ApplicationComponent {
// ...
fun activityComponentBuilder(): ActivityComponent.Builder // HERE
// ...
}
This tells Dagger that you need a Builder to create ActivityComponent from ApplicationComponent.
Finally, you need to update the code when you create ActivityComponent. Open SplashActivity.kt in ui.view.splash and change the following code:
class SplashActivity : AppCompatActivity() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
makeFullScreen()
setContentView(R.layout.activity_splash)
application.appComp
.activityComponentBuilder() // 1
.activity(this) // 2
.build() // 3
.inject(this)
splashViewBinder.init(this)
}
// ...
}
In this code, you now:
- Use
activityComponentBuilder()to access theBuilderDagger generates for you. - Invoke
activity()to pass the reference of theActivityyou need. - Use
build()to actually createActivityComponent.
In MainActivity, you need to do the same. Open MainActivity.kt in ui.view.main and apply the following change:
class MainActivity : AppCompatActivity() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
comp = application.appComp
.activityComponentBuilder() // HERE
.activity(this)
.build()
.apply {
inject(this@MainActivity)
}
if (savedInstanceState == null) {
mainPresenter.goToBusStopList()
}
}
}
Now, build and run Busso and make sure that everything works as expected.
How do you know when to use a @Subcomponent.Factory or a @Subcomponent.Builder? You use the same criteria as you learned to decide between @Component.Factory and @Component.Builder.
@Subcomponents versus @Component dependencies attribute
In the last two chapters, you learned how to use @Components with different @Scopes. You also learned two different ways of managing dependencies between objects with different @Scopes using:
- The dependencies attribute of the
@Componentannotation -
@Subcomponents
But which one is the best? As always, there’s no answer that works in every case. Each solution has its pros and cons.
If you use @Component dependencies:
- You need to use factory methods to explicitly publish the objects that a
@Componentwants to share with others. - The dependent
@Componentmust list the dependency@Components as values of its dependencies attribute. - In this case, the dependency is not transitive. If
@ComponentA depends on B and B depends on C, that doesn’t mean A depends on C. If you need that relationship, the dependency must be explicit and you need to list all the dependent@Componentsin the dependencies attribute. - When you have existing objects, you can use
@Component.Builderand@Component.Factory. - As you’ll see in the next chapter, multibinding doesn’t work when you manage
@Componentdependencies using the dependencies attribute.
If you use @Subcomponents:
- Each
@Subcomponentinherits all the objects of the parent@Components or@Subcomponents. You don’t need to explicitly publish any of them. - In this case, the dependency between
@Subcomponents is transitive. If@SubcomponentA inherits from B and B inherits from C, then A inherits from C. - You create
@Subcomponentinstances using factory methods you define in the parent@Componentor@Subcomponent. - When you have existing objects, you can use
@Subcomponent.Builderand@Subcomponent.Factory. The parent will define factory methods for them. - As you’ll see in the next chapter, multibinding works with
@Subcomponents.
In the following chapters, you can use either solution unless limitations are in place.
Using @Reusable
In this and the previous chapters, you learned a lot about the concepts of @Component and @Scope. In particular, you learned how to create a custom @Scope that’s not much different than the existing @Singleton.
@Scope allows you to control the number of instances Dagger creates for the injection of a specific type. You can create a new instance for a specific type any time you need to inject it or you can ask Dagger to create only one that is bound to the @Component it belongs to. As you’ve read many times now, the lifespan of a scoped object is bound to the lifespan of the @Component with the same scope.
Sometimes, for performance reasons, you might need to limit the number of instances for a given type without binding those instances to the lifespan of a specific @Component. If you want Dagger to check if a convenient instance of an object already exists before creating a new one, you can use @Reusable.
@Reusable’s source code looks like this:
@Documented
@Beta
@Retention(RUNTIME)
@Scope
public @interface Reusable {}
As you can see, @Reusable is a @Scope that Dagger treats in a particular and privileged way. You don’t need to use any objects with @Reusable scope for Busso, but it’s interesting to note how Dagger manages them.
Note: As you can see in the previous code,
@Reusableis in@Beta, so the logic Dagger uses for it might change in future releases.
To give an example, suppose you define a @Component hierarchy as in Figure 12.7:
Here you have four @Components, where:
-
@Component1defines the binding between two objects with types A and B. -
@Component2does the same for two objects with types C and D. -
@Component3handles binding the two objects with types E, F and R -
@Component4defines a binding between two objects with types G and R.
@Component4 and @Component3 define the bindings for objects of the same type R, but the objects are bound to different @Scopes.
At this point, you’d consider whether you can reuse the objects of type R. If so, you can bind them to the @Reusable scope and Dagger will treat them as if they were bound to the scope for @Component1. This is the scope of the least common ancestor @Component.
In this case, you’d consider the pros and cons of @Reusable scopes. For a modern JVM implementation running on a desktop or server, creating and destroying objects isn’t a big issue. On mobile, things are different because the Garbage Collector can impact an app’s performance. @Reusable is an interesting tool you should always consider, but it isn’t the best choice in every situation.
Wow! This has been another dense and important chapter. In it, you learned one of the most difficult topics in Dagger: how to implement @Component dependencies using @Subcomponents. This chapter also concludes Section Three of this book. Congratulations!
Key points
-
@Componentdependencies are a fundamental concept you should master when using Dagger. - Using the
@Componentdependencies attribute requires you to explicitly expose the objects you want to share using factory methods. The dependency is not transitive. -
@Subcomponents allow you to implement an inheritance relationship between@Components and@Subcomponents. In this case, you don’t need to use a factory method. Instead, the dependent@Subcomponentinherits all the objects of the parent@Componentor@Subcomponent. - To provide existing objects to a
@Subcomponent, use@Subcomponent.Factoryor@Subcomponent.Builder. - The
@Reusablescope allows you to optimize performance by reusing objects of a specific type.
In the next chapter, you’ll learn all about a very important and useful Dagger feature: multibindings.