For the longest time, I just made do with a single
AppComponent that I kept in my
Application subclass. Whenever I needed to inject dependencies, I’d just call
getApplication(), cast it and grab my component. Part of it was because I didn’t understood scopes and subcomponents. The other part was because the projects I worked on weren’t big enough to require another component.
AppComponent is good enough for smaller projects, but as your application grows in complexity, it starts becoming hard to manage using 1 component and a couple scopes.
Scopes tell Dagger when to make a new instance when injecting dependencies. Subcomponents are good if you want full-access to the parent component. Component dependencies are good if you want to control what is accessible between components. This is useful for making sure states are persisted only within the same context. Like if you had two instances of the same
Activity, you don’t want them to share the same presenter. So, you scope the presenter to the activity, you make each
Activity get a different presenter instance. You scope classes using a custom annotation and defining a new component that uses the same annotation.
The tl;dr doesn’t come close to an adequate explanation, but without the nitty gritty, that’s the best I can do. I mean, we’re talking about a library where the official documentation says things like
In other words, the object graph of a subcomponent’s parent component is a subgraph of the object graph of the subcomponent itself.
I had to read that like 10 times before I understood it.
Some Exposition on Scopes and Subcomponents
So, aside from your common Dagger terminology (dependency graph, injection, component, module, etc.), there’s two more terms you need to know:
Scope refers to the group of classes that a component applies to. This is marked by an included annotation, like
@Singleton or a custom annotation like
@ActivityScope. You can have dependencies scoped to Activities or dependencies scoped to Fragments. Scoping is used to determine how instances are shared and generated.
Subcomponent refers to a component that relies on a parent component. It’s kind of like a subclass. It’s marked with
@Subcomponent. Components can depend on zero or more other components, but it works differently from subcomponents.
So, here’s a question. Why use subcomponents if regular components can depend on other components? It’s nuanced, but can be explained like this:
- Subcomponents have access to the entire graph of the parent component. That is, they can access all the dependencies of the parent. It’s tightly coupled.
- Component Dependencies only have access to what the other component exposes.
- So, say
ComponentCandyhas two dependencies,
Oreois exposed in
ComponentChocolatecan only access
- So, say
As a rule of thumb, I would start with subcomponents. Use component dependencies when you have a lot of modules and need to share dependencies across all of them.
Quickly, Unscoped Means…?
There’s also the concept of being unscoped. For provided dependencies, this means Dagger will re-evaluate the
@Provides method whenever the dependency is needed. Typically, this generates a new instance every time.
All components and dependencies are unscoped unless you explicitly say otherwise. You can specify otherwise by using a
About the Android Injector
One more thing, I will not be using the Android Injector classes introduced in Dagger 2.11. That itself is complex enough to warrant its own article.
Singleton Scope - Application Scope
Let’s define our basic
AppModule and a basic network dependency.
Alright, that’s a basic Dagger setup. Let’s then instantiate it in our
And inject it into our
This all was pretty normal in the early days of Dagger 2. Since the dependency and component are unscoped, the instance of
NetworkClient we get is a new one, every time.
If you annotate
@Singleton, we can make the dependency a “local singleton”. Let’s do that
This means for this instance of
AppComponent the same instance of
NetworkClient will be returned. If we made a new instance of
AppComponent, we would have a new instance of
Extending from the Application Scope - Activity Scope
Now, let’s start getting a little more fancy. I mentioned using custom annotations to define scopes before. There’s nothing special about these custom annotations. They are used as identifiers and provide no inherent functionality.
Once that’s defined, we can construct our first subcomponent
So, we have an
ActivityComponent, but what is it doing? Nothing that
AppComponent isn’t already doing. The usefulness of a subcomponent becomes clear when you have dependencies specific to the scope. So, let’s define a new module that provides
Then, tie it to our
As you’ve noticed, we haven’t referenced the
AppComponent at all. So, how does it know what to extend?
We need to update our
After that, we need to integrate the component to the Activity’s lifecycle. We have the following options for that:
ActivityComponentevery time in
- keep the activity component in the
- persist the
Personally, I find the last option to be the best. It allows us persist the component across config changes. This, in effect, persists our scoped dependencies, as well. So, if you scope a
Presenter to the activity, then by persisting the component, you persist the
If your component holds a reference to the Activity in any way, you will need to
null the component ref in
This approach requires that you change the component type to
ActivityComponent?, which will require safety operators.
Extending from the Activity Scope - Fragment Scope
Subcomponents can keep going deeper and deeper. For most intents and purposes, defining an
Fragment component is more than enough.
Start by defining another scope annotation
Then, we repeat what we did earlier- create the module and component
And integrate it
For Fragments, there’s various ways to handle config changes, which can be used to persist the
You could persist it in the parent activity if the fragment is present for the same duration as the activity.
You could persist it in another headless fragment with
setRetainInstance(true) or in the same fragment with the same method. If you go with the latter, be sure to clean up references that shouldn’t stay alive.
Protip: Side Stepping @Provides
You don’t need to define a
@Provides method to expose a dependency. If you use constructor injection and add a scope annotation on the
class declaration, Dagger will know to generate code for that class. It will be scoped appropriately, as well.
All of the code is available on GitHub if you wanted to look over it.
See How the Android Injector Makes Things Easier
Alright, since we’ve covered scopes and subcomponents, we can move onto how Dagger makes this easier for Android.