Dependency Injection with Dagger 2
Home Screen Component
Up next
Previous
About
In this lesson we create the HomeComponent which will be responsible for injecting the HomeFragment.
We'll be using Dagger modules for the first time, as well as Component dependencies.
Quick Summary--Subcomponents, why we aren't using them
To allow for the most modularity, this course will be using Component dependencies, rather than Subcomponents. When using Subcomponents, the "parent" Components must know about all of their Subcomponents, because the Subcomponents are created as nested classes inside the parent Components. The plus side of this is the Subcomponent automatically has access to all dependencies provided by its parent, without having to explicitly add an interface method to the parent Component. The down side is using Subcomponents can impact not only modularity, but also build time. Changes to a Subcomponent inherently cause changes to the generated parent Component. Depending on the size, this could make your small change in one module fan out to require other modules to incrementally rebuild as well.
That's not to say that Subcomponents should never be used. There may be a situation where you would like a subset of dependencies with a different lifecycle than your feature's main Component. If the Subcomponent is part of the same feature, or if it is a smaller module that your feature depends on, Subcomponents may be a good route to go.
When talking about fragments (or "screens") in an application, though, I find enforcing modularity via Components + Component dependencies to be the best route forward. For example, your top level component (ApplicationComponent) cannot know about modules that are provided as dynamic feature modules. If you were using Subcomponents for your Fragment/Screen Scope, you would not be able to use Android's dynamic feature module system.
Good reads:
https://dagger.dev/dev-guide/subcomponents.html
https://developer.android.com/training/dependency-injection/dagger-multi-module
Instructor
Links
Comments
Hi Brandon,
I like how you explain and dig into the generated code. Dagger is no longer an enemy. :)
Question: How do I provide Application or Context? I have something like this (below) in AuthModule and added it to HomeCompopent as a module.
@Module
object AuthModule {
@Provides
fun providesGoogleSignInClient(application: Application, googleSignInOptions: GoogleSignInOptions): GoogleSignInClient =
GoogleSignIn.getClient(application, googleSignInOptions)
}
but I get an error Application cannot be provided without @Inject.
Hi Thomas!
To provide Application
or Context
, you'd need to expose them from ApplicationDeps
and make sure you're adding them to the dependency graph in ApplicationComponent (which is what ultimately implements ApplicationDeps
).
First, in ApplicationDeps
add the functions for Application and Context:
```
interface ApplicationDeps {
fun application(): Application
fun appContext(): Context
fun appRepository(): AppRepository
}
```
Then, in ApplicationComponent
, change the create
function to take another @BindsInstance
parameter for Application (we already have one for Context).
```
@Singleton
@Component(modules = [GitHubApiModule::class])
interface ApplicationComponent : ApplicationDeps {
@Component.Factory
interface Factory {
fun create(@BindsInstance application: Application, @BindsInstance context: Context): ApplicationComponent
}
}
```
That factory method is called from GitHubBrowserApp
, so we have to update it:
DaggerApplicationComponent.factory().create(this, this)
So what is all of the above doing?
We update
AppDeps
so that any other components that depend on it (HomeComponent) will know that they now have anApplication
andContext
that they can inject.We update
ApplicationComponent
, which is extendingAppDeps
, to provide the new dependencies thatAppDeps
defines. @BindsInstance tells Dagger: "When someone injects anApplication
, here is the instance to give them". We're "binding" an "instance" of the function argument type into the Dagger graph.We update where we're creating the DaggerApplicationComponent to pass in our application again, since it satisfies both the
Application
andContext
dependencies.
Be sure to check out the generated code in DaggerHomeComponent, after building, to see how that all gets wired up.
Let me know if any of that isn't clear and thanks for checking out the course!
Thanks, Brandon.
This worked and the broader picture is getting clearer. :) I, however, hit a new roadblock. I need to provide Fragment. In my previous implementation, I had something like below
@Module(includes = [AuthBindingsModule::class])
interface AuthModule {
@Provides
fun provideFacebookAdapter(fragment: Fragment): FacebookAdapter = FacebookAdapter(fragment)
}
@Module
interface AuthBindingsModule {
@Binds
fun provideLoginFragment(fragment: LoginFragment): Fragment
}
class FacebookAdapter @Inject constructor(
private val fragment: Fragment
) {...}
I'm unsure of how to proceed to refactor this old implementation and use what you have. I tried using the same principle as we did with Application and Context by creating an interface for it but no luck. This is out of scope of this. If you can help, I'd really appreciate it. I have been banging my head all weekend.
interface AuthDependencies {
fun fragment(): Fragment
}
...
(Sorry code block formatting doesn't seem to be working as I expect)
For a quick answer that I don't recommend (because it will be incompatible with the future direction this course takes):
Bind the fragment instance into your Dagger graph in your Fragment's Component
```
@Component(dependencies = [ApplicationDeps::class], modules = [HomeModule::class])
interface HomeComponent {
fun inject(homeFragment: HomeFragment)
@Component.Factory
interface Factory {
fun create(applicationDeps: ApplicationDeps, @BindsInstance fragment: Fragment): HomeComponent
}
}
fun HomeFragment.inject() {
DaggerHomeComponent.factory()
.create(requireContext().applicationDeps(), this) // passing "this" as the Fragment instance to bind into the Dagger graph
.inject(this)
}
```
With that, you can inject objects into your Fragment like FacebookAdapter
that take a Fragment as a dependency
Longer answer:
Just FYI, This response is going to go a bit into the future of this course, because we change how Fragment Components work.
The Dagger graph in this course is set up to have components outlive individual Fragment/Activity instances. Because of this, having a Fragment as an injectable dependency isn't recommended (The "Screen scope" lives on even if a Fragment is destroyed/created due to a config change--just like a ViewModel, so having a single Fragment instance being in the dependency graph wouldn't work as expected).
There are a few ways to get around this.
1 Don't use Dagger to inject the Fragment into another object. Just pass it to the object when needed.
2 Used assisted inject:
https://github.com/square/AssistedInject
This will allow you to inject a FacebookAdapter_AssistedFactory
, then you can call factory.create(fragment)
You have to be careful to not use this in objects that will live longer than the Fragment that you are injecting into them.
3 Create a new, smaller scoped Component.
For example, if you have your LoginFragment (?), you'd have:
-ApplicationComponent (Singleton Application scope)
-LoginComponent ("Local" singleton, Screen scope)
-AuthComponent (Created with each LoginFragment instance)
AuthComponent would look something like this:
```
// In AuthComponent.kt
@Component(dependencies = [ApplicationDeps::class])
interface AuthComponent {
fun facebookAdapter(): FacebookAdapter
@Component.Factory
interface Factory {
fun create(applicationDeps: ApplicationDeps, @BindsInstance loginFragment: Fragment): AuthComponent
}
}
fun LoginFragment.authComponent(): AuthComponent {
return DaggerAuthComponent.factory().create(requireContext().applicationDeps(), this) // Passing this
as the Fragment instance to "bind" it into the Dagger dependency graph
}
// In LoginFragment
private val facebookAdapter = authComponent().facebookAdapter() // Note, we aren't @Inject
ing this--we need to access it off of our new component, assuming you have a separate LoginComponent that's performing injection on the Fragment
```
If you need LoginComponent dependencies, then you'd have to add that as a dependency of AuthComponent. You could extract the helper, if you're using the same strategy as later in this course, you could extract the getComponent {}
piece to be able to get a reference to your Fragment's component to pass as a dependency to this new AuthComponent.
What we're doing here is defining a new component layer that lives only as long as the Fragment that is using it. This makes it safe to "bind" the Fragment into the dependency graph at this component level.
This is pretty hard to describe in a reply, but I hope it is giving you some idea of the path forward.
My personal strategy is to not make a Fragment an injectable dependency into other objects. But there is absolutely nothing wrong with it if you are doing it in Components that are scoped to the lifecycle of a Fragment.
Hi Brandon,
Can you tell me if I can use a viewmodel in an activity too?
I tried to bind the viewmodel in the activity's module, but I have some errors regarding the scopes and I am not sure where I have to modify to achieve to use the viewmodel with an activity.
You can definitely use a ViewModel in the Activity. In that case, you'd probably want an ActivityScope, and would need a Component for your Activity.
In lesson 49-50, we do create a Component for the MainActivity and show an example of an ActivityScope injectable dependency (it's not a view model, but it would work mostly the same).
In a module you create for the Activity's Component, you'd bind your view model just like you do in the Fragment module in this lesson. From there, everything is the same as when using a ViewModel in a Fragment.
Lessons in Dependency Injection with Dagger 2






















































