Using Dagger in Android apps
Stay organized with collections
Save and categorize content based on your preferences.
The Dagger basics page explained how Dagger can help you automate dependency injection in your app. With Dagger, you don't have to write tedious and error-prone boilerplate code.
Best practices summary
- Use constructor injection with
@Injectto add types to the Dagger graph whenever it's possible. When it's not:- Use
@Bindsto tell Dagger which implementation an interface should have. - Use
@Providesto tell Dagger how to provide classes that your project doesn't own.
- Use
- You should only declare modules once in a component.
- Name the scope annotations depending on the lifetime where the
annotation is used. Examples include
@ApplicationScope,@LoggedUserScope, and@ActivityScope.
Adding dependencies
To use Dagger in your project, add these dependencies to your application in
your build.gradle file. You can find the latest version of Dagger
in this GitHub project.
Kotlin
plugins{ id'kotlin-kapt' } dependencies{ implementation'com.google.dagger:dagger:2.x' kapt'com.google.dagger:dagger-compiler:2.x' }
Java
dependencies{ implementation'com.google.dagger:dagger:2.x' annotationProcessor'com.google.dagger:dagger-compiler:2.x' }
Dagger in Android
Consider an example Android app with the dependency graph from Figure 1.
Figure 1. Dependency graph of the example code
In Android, you usually create a Dagger graph that lives in your application
class because you want an instance of the graph to be in memory as long as the
app is running. In this way, the graph is attached to the app lifecycle. In some
cases, you might also want to have the application context available in the
graph. For that, you would also need the graph to be in the
Application class. One advantage of this
approach is that the graph is available to other Android framework classes.
Additionally, it simplifies testing by allowing you to use a custom
Application class in tests.
Because the interface that generates the graph is annotated with @Component,
you can call it ApplicationComponent or ApplicationGraph. You usually keep
an instance of that component in your custom Application class and call it
every time you need the application graph, as shown in the following code
snippet:
Kotlin
// Definition of the Application graph @Component interfaceApplicationComponent{...} // appComponent lives in the Application class to share its lifecycle classMyApplication:Application(){ // Reference to the application graph that is used across the whole app valappComponent=DaggerApplicationComponent.create() }
Java
// Definition of the Application graph @Component publicinterface ApplicationComponent{ } // appComponent lives in the Application class to share its lifecycle publicclass MyApplicationextendsApplication{ // Reference to the application graph that is used across the whole app ApplicationComponentappComponent=DaggerApplicationComponent.create(); }
Because certain Android framework classes such as activities and fragments are
instantiated by the system, Dagger can't create them for you. For activities
specifically, any initialization code needs to go into the onCreate() method.
That means you cannot use the @Inject annotation in the constructor of the
class (constructor injection) as you did in the previous examples. Instead,
you have to use field injection.
Instead of creating the dependencies an activity requires in the onCreate()
method, you want Dagger to populate those dependencies for you. For field
injection, you instead apply the @Inject annotation to the fields that you
want to get from the Dagger graph.
Kotlin
classLoginActivity:Activity(){ // You want Dagger to provide an instance of LoginViewModel from the graph @InjectlateinitvarloginViewModel:LoginViewModel }
Java
publicclass LoginActivityextendsActivity{ // You want Dagger to provide an instance of LoginViewModel from the graph @Inject LoginViewModelloginViewModel; }
For simplicity, LoginViewModel is not an Android Architecture Components
ViewModel; it's just a regular class that acts as a ViewModel.
For more information about how to inject these classes, check out the code
in the official Android Blueprints Dagger implementation, in
the dev-dagger branch.
One of the considerations with Dagger is that injected fields cannot be private. They need to have at least package-private visibility like in the preceding code.
Injecting activities
Dagger needs to know that LoginActivity has to access the graph in order to
provide the ViewModel it requires. In the Dagger basics page, you used
the @Component interface to get objects from the graph
by exposing functions with the return type of what you want to get from the
graph. In this case, you need to tell Dagger about an object (LoginActivity
in this case) that requires a dependency to be injected. For that, you expose
a function that takes as a parameter the object that requests injection.
Kotlin
@Component interfaceApplicationComponent{ // This tells Dagger that LoginActivity requests injection so the graph needs to // satisfy all the dependencies of the fields that LoginActivity is requesting. funinject(activity:LoginActivity) }
Java
@Component publicinterface ApplicationComponent{ // This tells Dagger that LoginActivity requests injection so the graph needs to // satisfy all the dependencies of the fields that LoginActivity is injecting. voidinject(LoginActivityloginActivity); }
This function tells Dagger that LoginActivity wants to access the graph and
requests injection. Dagger needs to satisfy all the dependencies that
LoginActivity requires (LoginViewModel with its own dependencies).
If you have multiple classes that request injection, you have to specifically
declare them all in the component with their exact type. For example, if you had
LoginActivity and RegistrationActivity requesting injection, you'd have two
inject() methods instead of a generic one covering both cases. A generic
inject() method doesn't tell Dagger what needs to be provided. The functions
in the interface can have any name, but calling them inject() when they
receive the object to inject as a parameter is a convention in Dagger.
To inject an object in the activity, you'd use the appComponent defined in
your Application class and call the inject() method, passing in an instance
of the activity that requests injection.
When using activities, inject Dagger in the
activity's onCreate() method before calling super.onCreate() to avoid issues
with fragment restoration. During the restore phase in super.onCreate(),
an activity attaches fragments that might want to access activity bindings.
When using fragments, inject Dagger in the fragment's onAttach()
method. In this case, it can be done before or after calling super.onAttach().
Kotlin
classLoginActivity:Activity(){ // You want Dagger to provide an instance of LoginViewModel from the graph @InjectlateinitvarloginViewModel:LoginViewModel overridefunonCreate(savedInstanceState:Bundle?){ // Make Dagger instantiate @Inject fields in LoginActivity (applicationContextasMyApplication).appComponent.inject(this) // Now loginViewModel is available super.onCreate(savedInstanceState) } } // @Inject tells Dagger how to create instances of LoginViewModel classLoginViewModel@Injectconstructor( privatevaluserRepository:UserRepository ){...}
Java
publicclass LoginActivityextendsActivity{ // You want Dagger to provide an instance of LoginViewModel from the graph @Inject LoginViewModelloginViewModel; @Override protectedvoidonCreate(BundlesavedInstanceState){ // Make Dagger instantiate @Inject fields in LoginActivity ((MyApplication)getApplicationContext()).appComponent.inject(this); // Now loginViewModel is available super.onCreate(savedInstanceState); } } publicclass LoginViewModel{ privatefinalUserRepositoryuserRepository; // @Inject tells Dagger how to create instances of LoginViewModel @Inject publicLoginViewModel(UserRepositoryuserRepository){ this.userRepository=userRepository; } }
Let's tell Dagger how to provide the rest of the dependencies to build the graph:
Kotlin
classUserRepository@Injectconstructor( privatevallocalDataSource:UserLocalDataSource, privatevalremoteDataSource:UserRemoteDataSource ){...} classUserLocalDataSource@Injectconstructor(){...} classUserRemoteDataSource@Injectconstructor( privatevalloginService:LoginRetrofitService ){...}
Java
publicclass UserRepository{ privatefinalUserLocalDataSourceuserLocalDataSource; privatefinalUserRemoteDataSourceuserRemoteDataSource; @Inject publicUserRepository(UserLocalDataSourceuserLocalDataSource,UserRemoteDataSourceuserRemoteDataSource){ this.userLocalDataSource=userLocalDataSource; this.userRemoteDataSource=userRemoteDataSource; } } publicclass UserLocalDataSource{ @Inject publicUserLocalDataSource(){} } publicclass UserRemoteDataSource{ privatefinalLoginRetrofitServiceloginRetrofitService; @Inject publicUserRemoteDataSource(LoginRetrofitServiceloginRetrofitService){ this.loginRetrofitService=loginRetrofitService; } }
Dagger modules
For this example, you're using the Retrofit networking library.
UserRemoteDataSource has a dependency on LoginRetrofitService. However,
the way to create an instance of LoginRetrofitService is different from what
you've been doing until now. It's not a class instantiation; it's the result of
calling Retrofit.Builder() and passing in different parameters to configure
the login service.
Apart from the @Inject annotation, there's another way to tell Dagger how to
provide an instance of a class: the information inside Dagger modules. A Dagger
module is a class that is annotated with @Module. There, you can define
dependencies with the @Provides annotation.
Kotlin
// @Module informs Dagger that this class is a Dagger Module @Module classNetworkModule{ // @Provides tell Dagger how to create instances of the type that this function // returns (i.e. LoginRetrofitService). // Function parameters are the dependencies of this type. @Provides funprovideLoginRetrofitService():LoginRetrofitService{ // Whenever Dagger needs to provide an instance of type LoginRetrofitService, // this code (the one inside the @Provides method) is run. returnRetrofit.Builder() .baseUrl("https://example.com") .build() .create(LoginService::class.java) } }
Java
// @Module informs Dagger that this class is a Dagger Module @Module publicclass NetworkModule{ // @Provides tell Dagger how to create instances of the type that this function // returns (i.e. LoginRetrofitService). // Function parameters are the dependencies of this type. @Provides publicLoginRetrofitServiceprovideLoginRetrofitService(){ // Whenever Dagger needs to provide an instance of type LoginRetrofitService, // this code (the one inside the @Provides method) is run. returnnewRetrofit.Builder() .baseUrl("https://example.com") .build() .create(LoginService.class); } }
Modules are a way to semantically encapsulate information on how to provide
objects. As you can see, you called the class NetworkModule to group the logic
of providing objects related to networking. If the application expands, you can
also add how to provide an OkHttpClient here, or how to
configure Gson or Moshi.
The dependencies of a @Provides method are the parameters of that method. For
the previous method, LoginRetrofitService can be provided with no dependencies
because the method has no parameters. If you had declared an OkHttpClient as a
parameter, Dagger would need to provide an OkHttpClient instance from the
graph to satisfy the dependencies of LoginRetrofitService. For example:
Kotlin
@Module classNetworkModule{ // Hypothetical dependency on LoginRetrofitService @Provides funprovideLoginRetrofitService( okHttpClient:OkHttpClient ):LoginRetrofitService{...} }
Java
@Module publicclass NetworkModule{ @Provides publicLoginRetrofitServiceprovideLoginRetrofitService(OkHttpClientokHttpClient){ ... } }
In order for the Dagger graph to know about this module, you have to add it to
the @Component interface as follows:
Kotlin
// The "modules" attribute in the @Component annotation tells Dagger what Modules // to include when building the graph @Component(modules=[NetworkModule::class]) interfaceApplicationComponent{ ... }
Java
// The "modules" attribute in the @Component annotation tells Dagger what Modules // to include when building the graph @Component(modules=NetworkModule.class) publicinterface ApplicationComponent{ ... }
The recommended way to add types to the Dagger graph is by using constructor
injection (i.e. with the @Inject annotation on the constructor of the class).
Sometimes, this is not possible and you have to use Dagger modules. One example
is when you want Dagger to use the result of a computation to determine how to
create an instance of an object. Whenever it has to provide an instance of that
type, Dagger runs the code inside the @Provides method.
This is how the Dagger graph in the example looks right now:
Figure 2. Representation of the graph with
LoginActivity being injected by Dagger
The entry point to the graph is LoginActivity. Because LoginActivity injects
LoginViewModel, Dagger builds a graph that knows how to provide an instance
of LoginViewModel, and recursively, of its dependencies. Dagger knows how to
do this because of the @Inject annotation on the classes' constructor.
Inside the ApplicationComponent generated by Dagger, there's a factory-type
method to get instances of all the classes it knows how to provide. In this
example, Dagger delegates to the NetworkModule included in
ApplicationComponent to get an instance of LoginRetrofitService.
Dagger scopes
Scopes were mentioned on the Dagger basics page as a way to have a unique instance of a type in a component. This is what is meant by scoping a type to the component's lifecycle.
Because you might want to use UserRepository in other features of the app and
might not want to create a new object every time you need it, you can designate
it as a unique instance for the whole app. It is the same for
LoginRetrofitService: it can be expensive to create, and you also want a
unique instance of that object to be reused. Creating an instance of
UserRemoteDataSource is not that expensive, so scoping it to the
component's lifecycle is not necessary.
@Singleton is the only scope annotation that comes with
the javax.inject package. You can use it to annotate ApplicationComponent
and the objects you want to reuse across the whole application.
Kotlin
@Singleton @Component(modules=[NetworkModule::class]) interfaceApplicationComponent{ funinject(activity:LoginActivity) } @Singleton classUserRepository@Injectconstructor( privatevallocalDataSource:UserLocalDataSource, privatevalremoteDataSource:UserRemoteDataSource ){...} @Module classNetworkModule{ // Way to scope types inside a Dagger Module @Singleton @Provides funprovideLoginRetrofitService():LoginRetrofitService{...} }
Java
@Singleton @Component(modules=NetworkModule.class) publicinterface ApplicationComponent{ voidinject(LoginActivityloginActivity); } @Singleton publicclass UserRepository{ privatefinalUserLocalDataSourceuserLocalDataSource; privatefinalUserRemoteDataSourceuserRemoteDataSource; @Inject publicUserRepository(UserLocalDataSourceuserLocalDataSource,UserRemoteDataSourceuserRemoteDataSource){ this.userLocalDataSource=userLocalDataSource; this.userRemoteDataSource=userRemoteDataSource; } } @Module publicclass NetworkModule{ @Singleton @Provides publicLoginRetrofitServiceprovideLoginRetrofitService(){...} }
Take care not to introduce memory leaks when applying scopes to objects. As
long as the scoped component is in memory, the created object is in memory
too. Because ApplicationComponent is created when the app is launched (in the
Application class), it is destroyed when the app gets destroyed. Thus, the
unique instance of UserRepository always remains in memory until the
application is destroyed.
Dagger subcomponents
If your login flow (managed by a single LoginActivity) consists of multiple
fragments, you should reuse the same instance of LoginViewModel in all
fragments. @Singleton cannot annotate LoginViewModel to reuse the instance
for the following reasons:
The instance of
LoginViewModelwould persist in memory after the flow has finished.You want a different instance of
LoginViewModelfor each login flow. For example, if the user logs out, you want a different instance ofLoginViewModel, rather than the same instance as when the user logged in for the first time.
To scope LoginViewModel to the lifecycle of LoginActivity you need to create
a new component (a new subgraph) for the login flow and a new scope.
Let's create a graph specific to the login flow.
Kotlin
@Component interfaceLoginComponent{}
Java
@Component publicinterface LoginComponent{ }
Now, LoginActivity should get injections from LoginComponent because it
has a login-specific configuration. This removes the responsibility to inject
LoginActivity from the ApplicationComponent class.
Kotlin
@Component interfaceLoginComponent{ funinject(activity:LoginActivity) }
Java
@Component publicinterface LoginComponent{ voidinject(LoginActivityloginActivity); }
LoginComponent must be able to access the objects from ApplicationComponent
because LoginViewModel depends on UserRepository. The way to tell Dagger that
you want a new component to use part of another component is with
Dagger subcomponents. The new component must be a subcomponent of the
component containing shared resources.
Subcomponents are components that inherit and extend the object graph of a parent component. Thus, all objects provided in the parent component are provided in the subcomponent too. In this way, an object from a subcomponent can depend on an object provided by the parent component.
To create instances of subcomponents, you need an instance of the parent component. Therefore, the objects provided by the parent component to the subcomponent are still scoped to the parent component.
In the example, you must define LoginComponent as a subcomponent of
ApplicationComponent. To do this, annotate LoginComponent with
@Subcomponent:
Kotlin
// @Subcomponent annotation informs Dagger this interface is a Dagger Subcomponent @Subcomponent interfaceLoginComponent{ // This tells Dagger that LoginActivity requests injection from LoginComponent // so that this subcomponent graph needs to satisfy all the dependencies of the // fields that LoginActivity is injecting funinject(loginActivity:LoginActivity) }
Java
// @Subcomponent annotation informs Dagger this interface is a Dagger Subcomponent @Subcomponent publicinterface LoginComponent{ // This tells Dagger that LoginActivity requests injection from LoginComponent // so that this subcomponent graph needs to satisfy all the dependencies of the // fields that LoginActivity is injecting voidinject(LoginActivityloginActivity); }
You also must define a subcomponent factory inside LoginComponent so that
ApplicationComponent knows how to create instances of LoginComponent.
Kotlin
@Subcomponent interfaceLoginComponent{ // Factory that is used to create instances of this subcomponent @Subcomponent.Factory interfaceFactory{ funcreate():LoginComponent } funinject(loginActivity:LoginActivity) }
Java
@Subcomponent publicinterface LoginComponent{ // Factory that is used to create instances of this subcomponent @Subcomponent.Factory interface Factory{ LoginComponentcreate(); } voidinject(LoginActivityloginActivity); }
To tell Dagger that LoginComponent is a subcomponent of
ApplicationComponent, you have to indicate it by:
Creating a new Dagger module (e.g.
SubcomponentsModule) passing the subcomponent's class to thesubcomponentsattribute of the annotation.Kotlin
// The "subcomponents" attribute in the @Module annotation tells Dagger what // Subcomponents are children of the Component this module is included in. @Module(subcomponents=LoginComponent::class) classSubcomponentsModule{}
Java
// The "subcomponents" attribute in the @Module annotation tells Dagger what // Subcomponents are children of the Component this module is included in. @Module(subcomponents=LoginComponent.class) publicclass SubcomponentsModule{ }
Adding the new module (i.e.
SubcomponentsModule) toApplicationComponent:Kotlin
// Including SubcomponentsModule, tell ApplicationComponent that // LoginComponent is its subcomponent. @Singleton @Component(modules=[NetworkModule::class,SubcomponentsModule::class]) interfaceApplicationComponent{ }
Java
// Including SubcomponentsModule, tell ApplicationComponent that // LoginComponent is its subcomponent. @Singleton @Component(modules={NetworkModule.class,SubcomponentsModule.class}) publicinterface ApplicationComponent{ }
Note that
ApplicationComponentdoesn't need to injectLoginActivityanymore because that responsibility now belongs toLoginComponent, so you can remove theinject()method fromApplicationComponent.Consumers of
ApplicationComponentneed to know how to create instances ofLoginComponent. The parent component must add a method in its interface to let consumers create instances of the subcomponent out of an instance of the parent component:Expose the factory that creates instances of
LoginComponentin the interface:Kotlin
@Singleton @Component(modules=[NetworkModule::class,SubcomponentsModule::class]) interfaceApplicationComponent{ // This function exposes the LoginComponent Factory out of the graph so consumers // can use it to obtain new instances of LoginComponent funloginComponent():LoginComponent.Factory }
Java
@Singleton @Component(modules={NetworkModule.class,SubcomponentsModule.class}) publicinterface ApplicationComponent{ // This function exposes the LoginComponent Factory out of the graph so consumers // can use it to obtain new instances of LoginComponent LoginComponent.FactoryloginComponent(); }
Assigning scopes to subcomponents
If you build the project, you can create instances of both ApplicationComponent
and LoginComponent. ApplicationComponent is attached to the lifecycle of the
application because you want to use the same instance of the graph as long as
the application is in memory.
What's the lifecycle of LoginComponent? One of the reasons why you needed
LoginComponent is because you needed to share the same instance of the
LoginViewModel between Login-related fragments. But also, you want different
instances of LoginViewModel whenever there's a new login flow. LoginActivity
is the right lifetime for LoginComponent: for every new activity, you need
a new instance of LoginComponent and fragments that can use that instance of
LoginComponent.
Because LoginComponent is attached to the LoginActivity lifecycle, you have to
keep a reference to the component in the activity in the same way you kept the
reference to the applicationComponent in the Application class. That way,
fragments can access it.
Kotlin
classLoginActivity:Activity(){ // Reference to the Login graph lateinitvarloginComponent:LoginComponent ... }
Java
publicclass LoginActivityextendsActivity{ // Reference to the Login graph LoginComponentloginComponent; ... }
Notice that the variable loginComponent is not annotated with @Inject
because you're not expecting that variable to be provided by Dagger.
You can use the ApplicationComponent to get a reference to LoginComponent
and then inject LoginActivity as follows:
Kotlin
classLoginActivity:Activity(){ // Reference to the Login graph lateinitvarloginComponent:LoginComponent // Fields that need to be injected by the login graph @InjectlateinitvarloginViewModel:LoginViewModel overridefunonCreate(savedInstanceState:Bundle?){ // Creation of the login graph using the application graph loginComponent=(applicationContextasMyDaggerApplication) .appComponent.loginComponent().create() // Make Dagger instantiate @Inject fields in LoginActivity loginComponent.inject(this) // Now loginViewModel is available super.onCreate(savedInstanceState) } }
Java
publicclass LoginActivityextendsActivity{ // Reference to the Login graph LoginComponentloginComponent; // Fields that need to be injected by the login graph @Inject LoginViewModelloginViewModel; @Override protectedvoidonCreate(BundlesavedInstanceState){ // Creation of the login graph using the application graph loginComponent=((MyApplication)getApplicationContext()) .appComponent.loginComponent().create(); // Make Dagger instantiate @Inject fields in LoginActivity loginComponent.inject(this); // Now loginViewModel is available super.onCreate(savedInstanceState); } }
LoginComponent is created in the activity's onCreate() method, and it'll get
implicitly destroyed when the activity gets destroyed.
The LoginComponent must always provide the same instance of LoginViewModel
each time it's requested. You can ensure this by creating a custom annotation
scope and annotating both LoginComponent and LoginViewModel with it. Note
that you cannot use the @Singleton annotation because it's already been used
by the parent component and that'd make the object an application singleton
(unique instance for the whole app). You need to create a different annotation
scope.
In this case, you could have called this scope @LoginScope but it's not a good
practice. The scope annotation's name should not be explicit to the purpose it
fulfills. Instead, it should be named depending on its lifetime because
annotations can be reused by sibling components such as RegistrationComponent
and SettingsComponent. That's why you should call it @ActivityScope instead
of @LoginScope.
Kotlin
// Definition of a custom scope called ActivityScope @Scope @Retention(value=AnnotationRetention.RUNTIME) annotationclassActivityScope // Classes annotated with @ActivityScope are scoped to the graph and the same // instance of that type is provided every time the type is requested. @ActivityScope @Subcomponent interfaceLoginComponent{...} // A unique instance of LoginViewModel is provided in Components // annotated with @ActivityScope @ActivityScope classLoginViewModel@Injectconstructor( privatevaluserRepository:UserRepository ){...}
Java
// Definition of a custom scope called ActivityScope @Scope @Retention(RetentionPolicy.RUNTIME) public@interfaceActivityScope{} // Classes annotated with @ActivityScope are scoped to the graph and the same // instance of that type is provided every time the type is requested. @ActivityScope @Subcomponent publicinterface LoginComponent{...} // A unique instance of LoginViewModel is provided in Components // annotated with @ActivityScope @ActivityScope publicclass LoginViewModel{ privatefinalUserRepositoryuserRepository; @Inject publicLoginViewModel(UserRepositoryuserRepository){ this.userRepository=userRepository; } }
Now, if you had two fragments that need LoginViewModel, both of them are
provided with the same instance. For example, if you have a
LoginUsernameFragment and a LoginPasswordFragment they need to get injected
by the LoginComponent:
Kotlin
@ActivityScope @Subcomponent interfaceLoginComponent{ @Subcomponent.Factory interfaceFactory{ funcreate():LoginComponent } // All LoginActivity, LoginUsernameFragment and LoginPasswordFragment // request injection from LoginComponent. The graph needs to satisfy // all the dependencies of the fields those classes are injecting funinject(loginActivity:LoginActivity) funinject(usernameFragment:LoginUsernameFragment) funinject(passwordFragment:LoginPasswordFragment) }
Java
@ActivityScope @Subcomponent publicinterface LoginComponent{ @Subcomponent.Factory interface Factory{ LoginComponentcreate(); } // All LoginActivity, LoginUsernameFragment and LoginPasswordFragment // request injection from LoginComponent. The graph needs to satisfy // all the dependencies of the fields those classes are injecting voidinject(LoginActivityloginActivity); voidinject(LoginUsernameFragmentloginUsernameFragment); voidinject(LoginPasswordFragmentloginPasswordFragment); }
The components access the instance of the component that lives in the
LoginActivity object. Example code for LoginUserNameFragment appears in the
following code snippet:
Kotlin
classLoginUsernameFragment:Fragment(){ // Fields that need to be injected by the login graph @InjectlateinitvarloginViewModel:LoginViewModel overridefunonAttach(context:Context){ super.onAttach(context) // Obtaining the login graph from LoginActivity and instantiate // the @Inject fields with objects from the graph (activityasLoginActivity).loginComponent.inject(this) // Now you can access loginViewModel here and onCreateView too // (shared instance with the Activity and the other Fragment) } }
Java
publicclass LoginUsernameFragmentextendsFragment{ // Fields that need to be injected by the login graph @Inject LoginViewModelloginViewModel; @Override publicvoidonAttach(Contextcontext){ super.onAttach(context); // Obtaining the login graph from LoginActivity and instantiate // the @Inject fields with objects from the graph ((LoginActivity)getActivity()).loginComponent.inject(this); // Now you can access loginViewModel here and onCreateView too // (shared instance with the Activity and the other Fragment) } }
And the same for LoginPasswordFragment:
Kotlin
classLoginPasswordFragment:Fragment(){ // Fields that need to be injected by the login graph @InjectlateinitvarloginViewModel:LoginViewModel overridefunonAttach(context:Context){ super.onAttach(context) (activityasLoginActivity).loginComponent.inject(this) // Now you can access loginViewModel here and onCreateView too // (shared instance with the Activity and the other Fragment) } }
Java
publicclass LoginPasswordFragmentextendsFragment{ // Fields that need to be injected by the login graph @Inject LoginViewModelloginViewModel; @Override publicvoidonAttach(Contextcontext){ super.onAttach(context); ((LoginActivity)getActivity()).loginComponent.inject(this); // Now you can access loginViewModel here and onCreateView too // (shared instance with the Activity and the other Fragment) } }
Figure 3 shows how the Dagger graph looks with the new subcomponent. The classes
with a white dot (UserRepository, LoginRetrofitService, and LoginViewModel)
are the ones that have a unique instance scoped to their respective components.
Figure 3. Representation of the graph you built for the Android app example
Let's break down the parts of the graph:
The
NetworkModule(and thereforeLoginRetrofitService) is included inApplicationComponentbecause you specified it in the component.UserRepositoryremains inApplicationComponentbecause it's scoped to theApplicationComponent. If the project grows, you want to share the same instance across different features (e.g. Registration).Because
UserRepositoryis part ofApplicationComponent, its dependencies (i.e.UserLocalDataSourceandUserRemoteDataSource) need to be in this component too in order to be able to provide instances ofUserRepository.LoginViewModelis included inLoginComponentbecause it's only required by the classes injected byLoginComponent.LoginViewModelis not included inApplicationComponentbecause no dependency inApplicationComponentneedsLoginViewModel.Similarly, if you hadn't had scoped
UserRepositorytoApplicationComponent, Dagger would automatically have includedUserRepositoryand its dependencies as part ofLoginComponentbecause that is currently the only placeUserRepositoryis used.
Apart from scoping objects to a different lifecycle, creating subcomponents is a good practice to encapsulate different parts of your application from each other.
Structuring your app to create different Dagger subgraphs depending on the flow of your app helps towards a more performant and scalable application in terms of memory and startup time.
Best practices when building a Dagger graph
When building the Dagger graph for your application:
When you create a component, you should consider what element is responsible for the lifetime of that component. In this case, the
Applicationclass is in charge ofApplicationComponentandLoginActivityis in charge ofLoginComponent.Use scoping only when it makes sense. Overusing scoping can have a negative effect on your app's runtime performance: the object is in memory as long as the component is in memory and getting a scoped object is more expensive. When Dagger provides the object, it uses
DoubleChecklocking instead of a factory-type provider.
Testing a project that uses Dagger
One of the benefits of using dependency injection frameworks like Dagger is that it makes testing your code easier.
Unit tests
You don't have to use Dagger for unit tests. When testing a class that uses constructor injection, you don't need to use Dagger to instantiate that class. You can directly call its constructor passing in fake or mock dependencies directly just as you would if they weren't annotated.
For example, when testing LoginViewModel:
Kotlin
@ActivityScope classLoginViewModel@Injectconstructor( privatevaluserRepository:UserRepository ){...} classLoginViewModelTest{ @Test fun`Happy path`(){ // You don't need Dagger to create an instance of LoginViewModel // You can pass a fake or mock UserRepository valviewModel=LoginViewModel(fakeUserRepository) assertEquals(...) } }
Java
@ActivityScope publicclass LoginViewModel{ privatefinalUserRepositoryuserRepository; @Inject publicLoginViewModel(UserRepositoryuserRepository){ this.userRepository=userRepository; } } publicclass LoginViewModelTest{ @Test publicvoidhappyPath(){ // You don't need Dagger to create an instance of LoginViewModel // You can pass a fake or mock UserRepository LoginViewModelviewModel=newLoginViewModel(fakeUserRepository); assertEquals(...); } }
End-to-end tests
For integration tests, a good practice is to create a
TestApplicationComponent meant for testing.
Production and testing use a different component configuration.
This requires more up-front design of the modules in your application. The testing component extends the production component and installs a different set of modules.
Kotlin
// TestApplicationComponent extends from ApplicationComponent to have them both // with the same interface methods. You need to include the modules of the // component here as well, and you can replace the ones you want to override. // This sample uses FakeNetworkModule instead of NetworkModule @Singleton @Component(modules=[FakeNetworkModule::class,SubcomponentsModule::class]) interfaceTestApplicationComponent:ApplicationComponent{ }
Java
// TestApplicationComponent extends from ApplicationComponent to have them both // with the same interface methods. You need to include the modules of the // Component here as well, and you can replace the ones you want to override. // This sample uses FakeNetworkModule instead of NetworkModule @Singleton @Component(modules={FakeNetworkModule.class,SubcomponentsModule.class}) publicinterface TestApplicationComponentextendsApplicationComponent{ }
FakeNetworkModule has a fake implementation of the original NetworkModule.
There you can provide fake instances or mocks of whatever you want to replace.
Kotlin
// In the FakeNetworkModule, pass a fake implementation of LoginRetrofitService // that you can use in your tests. @Module classFakeNetworkModule{ @Provides funprovideLoginRetrofitService():LoginRetrofitService{ returnFakeLoginService() } }
Java
// In the FakeNetworkModule, pass a fake implementation of LoginRetrofitService // that you can use in your tests. @Module publicclass FakeNetworkModule{ @Provides publicLoginRetrofitServiceprovideLoginRetrofitService(){ returnnewFakeLoginService(); } }
In your integration or end-to-end tests, you'd use a TestApplication that
creates the TestApplicationComponent instead of an ApplicationComponent.
Kotlin
// Your test application needs an instance of the test graph classMyTestApplication:MyApplication(){ overridevalappComponent=DaggerTestApplicationComponent.create() }
Java
// Your test application needs an instance of the test graph publicclass MyTestApplicationextendsMyApplication{ ApplicationComponentappComponent=DaggerTestApplicationComponent.create(); }
Then, this test application is used in a custom TestRunner that you'll use to
run instrumentation tests. For more information about this, check out the Using
Dagger in your Android app codelab.
Working with Dagger modules
Dagger modules are a way to encapsulate how to provide objects in a semantic way. You can include modules in components but you can also include modules inside other modules. This is powerful, but can be easily misused.
Once a module has been added to either a component or another module, it's already in the Dagger graph; Dagger can provide those objects in that component. Before adding a module, check if that module is part of the Dagger graph already by checking if it's already added to the component or by compiling the project and seeing if Dagger can find the required dependencies for that module.
Good practice dictates that modules should only be declared once in a component (outside of specific advanced Dagger use cases).
Let's say you have your graph configured in this way. ApplicationComponent
includes Module1 and Module2 and Module1 includes ModuleX.
Kotlin
@Component(modules=[Module1::class,Module2::class]) interfaceApplicationComponent{...} @Module(includes=[ModuleX::class]) classModule1{...} @Module classModule2{...}
Java
@Component(modules={Module1.class,Module2.class}) publicinterface ApplicationComponent{...} @Module(includes={ModuleX.class}) publicclass Module1{...} @Module publicclass Module2{...}
If now Module2 depends on classes provided by ModuleX. A bad practice
is including ModuleX in Module2 because ModuleX is included twice in
the graph as seen in the following code snippet:
Kotlin
// Bad practice: ModuleX is declared multiple times in this Dagger graph @Component(modules=[Module1::class,Module2::class]) interfaceApplicationComponent{...} @Module(includes=[ModuleX::class]) classModule1{...} @Module(includes=[ModuleX::class]) classModule2{...}
Java
// Bad practice: ModuleX is declared multiple times in this Dagger graph. @Component(modules={Module1.class,Module2.class}) publicinterface ApplicationComponent{...} @Module(includes=ModuleX.class) publicclass Module1{...} @Module(includes=ModuleX.class) publicclass Module2{...}
Instead, you should do one of the following:
- Refactor the modules and extract the common module out to the component.
- Create a new module with the objects that both modules share and extract it out to the component.
Not refactoring in this way results in a lot of modules including each other without a clear sense of organization and making it more difficult to see where each dependency is coming from.
Good practice (Option 1): ModuleX is declared once in the Dagger graph.
Kotlin
@Component(modules=[Module1::class,Module2::class,ModuleX::class]) interfaceApplicationComponent{...} @Module classModule1{...} @Module classModule2{...}
Java
@Component(modules={Module1.class,Module2.class,ModuleX.class}) publicinterface ApplicationComponent{...} @Module publicclass Module1{...} @Module publicclass Module2{...}
Good practice (Option 2): Common dependencies from Module1 and Module2
in ModuleX are extracted out to a new module named ModuleXCommon that is
included in the component. Then two other modules named
ModuleXWithModule1Dependencies and ModuleXWithModule2Dependencies are
created with the dependencies that are specific to each module. All modules
are declared once in the Dagger graph.
Kotlin
@Component(modules=[Module1::class,Module2::class,ModuleXCommon::class]) interfaceApplicationComponent{...} @Module classModuleXCommon{...} @Module classModuleXWithModule1SpecificDependencies{...} @Module classModuleXWithModule2SpecificDependencies{...} @Module(includes=[ModuleXWithModule1SpecificDependencies::class]) classModule1{...} @Module(includes=[ModuleXWithModule2SpecificDependencies::class]) classModule2{...}
Java
@Component(modules={Module1.class,Module2.class,ModuleXCommon.class}) publicinterface ApplicationComponent{...} @Module publicclass ModuleXCommon{...} @Module publicclass ModuleXWithModule1SpecificDependencies{...} @Module publicclass ModuleXWithModule2SpecificDependencies{...} @Module(includes=ModuleXWithModule1SpecificDependencies.class) publicclass Module1{...} @Module(includes=ModuleXWithModule2SpecificDependencies.class) publicclass Module2{...}
Assisted injection
Assisted injection is a DI pattern that is used to construct an object where some parameters may be provided by the DI framework and others must be passed in at creation time by the user.
In Android, this pattern is common in details screens where the id of the element to show is only known at runtime, not at compile time when Dagger generates the DI graph. To learn more about assisted injection with Dagger, see the Dagger documentation.
Conclusion
If you haven't already, review the best practices section. To see how to use Dagger in an Android app, see the Using Dagger in an Android app codelab.