Sign in Go Pro

Dependency Injection with Dagger 2

Home Screen Component

This lesson is for PRO members.

Upgrade today to get access to all the PRO lessons.

Unlock this lesson
Autoplay

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

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?

  1. We update AppDeps so that any other components that depend on it (HomeComponent) will know that they now have an Application and Context that they can inject.

  2. We update ApplicationComponent, which is extending AppDeps, to provide the new dependencies that AppDeps defines. @BindsInstance tells Dagger: "When someone injects an Application, here is the instance to give them". We're "binding" an "instance" of the function argument type into the Dagger graph.

  3. We update where we're creating the DaggerApplicationComponent to pass in our application again, since it satisfies both the Application and Context 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 @Injecting 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.

🤯 Thanks so much for the detailed explanation. I will take some time to check out AssistedInject and refactor it again to the second solution.

>

Lessons in Dependency Injection with Dagger 2

1. Intro to Dependency Injection - Simple example with no DI
01:47
Brandon Gogetap
2. Intro to Dependency Injection - Dependency Injection in Practice
00:35
Brandon Gogetap
3. Why we use Dependency Injection
01:59
Brandon Gogetap
4. How Dependency Injection frameworks help
00:37
Brandon Gogetap
5. What we're building - Final Product
01:04
Brandon Gogetap
6. Creating the GitHub Browser Project
01:45
Brandon Gogetap
7. Creating the Application Component Interface
01:10
Brandon Gogetap
8. Building the Application Component
01:02
Brandon Gogetap
9. Dagger codegen: Inspecting the generated Application Component
00:47
Brandon Gogetap
10. Adding the GitHubApi Module
02:01
Brandon Gogetap
11. Creating a Dagger 2 Module
02:36
Brandon Gogetap
12. Creating the AppRepository - The single source of data for the UI
Pro
02:47
Brandon Gogetap
13. Dagger codegen: Checking in on the generated Application Component
Pro
02:41
Brandon Gogetap
14. Unit Testing the AppRepository
Pro
03:07
Brandon Gogetap
15. Feature: Home Screen
Pro
01:55
Brandon Gogetap
16. Home Screen Repository List Setup
Pro
04:40
Brandon Gogetap
17. Home Screen ViewModel
Pro
04:34
Brandon Gogetap
18. Injecting a Jetpack ViewModel
Pro
03:14
Brandon Gogetap
19. Exposing Singleton Dependencies in a Modular Way
Pro
02:34
Brandon Gogetap
20. Home Screen Component
Pro
04:01
Brandon Gogetap
21. Dagger codegen: Component Dependencies
Pro
03:16
Brandon Gogetap
22. Dagger codegen: Multibindings
Pro
02:31
Brandon Gogetap
23. Dagger codegen: @Binds, @Provides, and Modules
Pro
03:03
Brandon Gogetap
24. Matching Dagger Component Scopes with ViewModel Scopes
Pro
04:07
Brandon Gogetap
25. Unit Testing HomeViewModel
Pro
04:14
Brandon Gogetap
26. Displaying fake data in the Home Fragment
Pro
03:39
Brandon Gogetap
27. Espresso tests and Dagger: Modularizing ApplicationComponent
Pro
02:39
Brandon Gogetap
28. Espresso tests and Dagger: Adding the TestApplicationComponent
Pro
04:42
Brandon Gogetap
29. Espresso tests and Dagger: Adding the Test Application and Runner
Pro
01:49
Brandon Gogetap
30. HomeFragment Espresso Test
Pro
03:56
Brandon Gogetap
31. Retrofit integration: Suspend Functions
Pro
03:49
Brandon Gogetap
32. Retrofit integration: Preparing for the real GitHub API
Pro
04:00
Brandon Gogetap
33. Retrofit integration: Swapping mock GitHubApi with implementation created by Retrofit
Pro
04:11
Brandon Gogetap
34. Dagger Modules: Build variants/flavors
Pro
01:46
Brandon Gogetap
35. Dagger codgen: Effects of Module changes
Pro
02:19
Brandon Gogetap
36. Feature: Repo Details Screen
Pro
01:20
Brandon Gogetap
37. Repo Details Component
Pro
01:55
Brandon Gogetap
38. RepoDetails ViewModel Part 1
Pro
01:40
Brandon Gogetap
39. Dagger codegen: Qualifier Annotations
Pro
01:30
Brandon Gogetap
40. Extending GitHub API: Single repository fetch
Pro
01:31
Brandon Gogetap
41. AppRepository updates: Caching and new request
Pro
01:19
Brandon Gogetap
42. AppRepository Unit Test Updates
Pro
02:05
Brandon Gogetap
43. Repo Details Contributors List Setup
Pro
01:29
Brandon Gogetap
44. Repo Details View States
Pro
02:12
Brandon Gogetap
45. Repo Details ViewModel Test
Pro
01:35
Brandon Gogetap
46. Hooking up RepoDetailsFragment with View States
Pro
01:43
Brandon Gogetap
47. RepoDetailsFragment Espresso Test
Pro
02:34
Brandon Gogetap
48. Preparing for screen navigation
Pro
04:08
Brandon Gogetap
49. Activity Scoped Component
Pro
01:43
Brandon Gogetap
50. Activity Screen Navigator
Pro
02:04
Brandon Gogetap
51. Fake ScreenNavigator: A fake navigator for tests
Pro
01:12
Brandon Gogetap
52. Navigating from the Home screen to the Details screen
Pro
03:36
Brandon Gogetap
53. Dagger codegen: Walking through all the layers
Pro
06:03
Brandon Gogetap
54. PowerAdapter - A Dependency Injection driven RecyclerView Adapter
Pro
01:32
Brandon Gogetap
55. Using PowerAdapter for the Home screen
Pro
03:37
Brandon Gogetap