1. The best practice
If you are familiar with Dagger, we will continue ^^. Here are best practices:
* Use constructor injection with @Inject to add Dagger graphs whenever possible. When it does not:
- Use @Binds to tell Dagger what interface to implement
- Use @Provides to tell Dagger how to provide classes that your project doesn’t own
* You should only declare modules once in a component.
* Naming the scope of anotation depends on its usage life cycle. Examples are @ApplicationScope , @LoggedUserScope , and @ActivityScope
2. Adding dependencies
To use Dagger, you add these dependencies to the build.gradle file. The latest version of github dagger can be found
1 2 3 4 5 | dependencies <span class="token punctuation">{</span> implementation <span class="token string">'com.google.dagger:dagger:2.x'</span> annotationProcessor <span class="token string">'com.google.dagger:dagger-compiler:2.x'</span> <span class="token punctuation">}</span> |
3. Dagger in Android
Consider a sample Android application with the dependency charts below:
In Android you usually create a Dagger diagram in the application layer because you want an instance of the diagram to stay in memory as long as the application is running. In this case the diagram attaches to the application life cycle. For that you will also need the diagram in the application layer. An advantage of this method is that charts are available in the Android Framework classes. Also it simplifies testing by allowing us to use a custom application class in test cases.
Because the interface that creates graphs is annotated with @Component , you can call it ApplicationComponent or ApplicationGraph. We usually keep an instance of that component in our custom application class and call it whenever you need the application diagram, as shown in the following code:
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token comment">// Definition of the Application graph</span> <span class="token annotation punctuation">@Component</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ApplicationComponent</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token comment">// appComponent lives in the Application class to share its lifecycle</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyApplication</span> <span class="token keyword">extends</span> <span class="token class-name">Application</span> <span class="token punctuation">{</span> <span class="token comment">// Reference to the application graph that is used across the whole app</span> ApplicationComponent appComponent <span class="token operator">=</span> DaggerApplicationComponent <span class="token punctuation">.</span> <span class="token function">create</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Because some Android framework classes like activity and fragment are system-initiated, Dagger cannot initialize them for us. For specific Activities, any initialization code needs to go into the onCreate () method. That means you can’t use anotation @Inject in the constructor like you did in the previous example. Instead we have to use field injection
Instead of creating dependencies in an Activity required in the onCreate () method, we want Dagger to include those dependencies. For field injection, you apply anotation @Inject to the fields you want to get from the Dagger diagram
1 2 3 4 5 6 7 | <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LoginActivity</span> <span class="token keyword">extends</span> <span class="token class-name">Activity</span> <span class="token punctuation">{</span> <span class="token comment">// You want Dagger to provide an instance of LoginViewModel from the graph</span> <span class="token annotation punctuation">@Inject</span> LoginViewModel loginViewModel <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
For simplicity, the LoginViewModel is not the ViewModel of Architecture Components, it is just a regular class that works like ViewModel.
One of the considerations for Dagger is that the injected fields cannot be private
I would like to introduce the details about injecting Activity, Dagger Modules, Dagger Scopes, Dagger subcomponents. …. Due to some limitations on the time I will update later ^^
4. Best Practices when building a Dagger chart
When building a Dagger chart for your application:
- When you create a component, you should consider which element is responsible for the component’s life cycle. In this case, the application layer is responsible for ApplicationComponent and LoginActivity is responsible for LoginComponent.
- Use scope when it makes sense. Abuse of the scope can negatively impact application runtime performance: the object stays in memory as long as the component is in memory and receives a larger scope object. When Dagger provides the object, it uses the DoubleCheck keyword instead of providing factory-type
5. Testing a project when using Dagger
One of the benefits of using Dependency Injection with Dagger is making it easier for us to test.
Unit tests
We don’t have to use Dagger for Unit Tests. When testing a class using constructor injection, you do not need to use Dagger to initialize that class. We can call its constructor directly, directly passing fake or mock dependencies if they are not commented.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <span class="token annotation punctuation">@ActivityScope</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LoginViewModel</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">final</span> UserRepository userRepository <span class="token punctuation">;</span> <span class="token annotation punctuation">@Inject</span> <span class="token keyword">public</span> <span class="token function">LoginViewModel</span> <span class="token punctuation">(</span> UserRepository userRepository <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span> <span class="token punctuation">.</span> userRepository <span class="token operator">=</span> userRepository <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LoginViewModelTest</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">happyPath</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// You don't need Dagger to create an instance of LoginViewModel</span> <span class="token comment">// You can pass a fake or mock UserRepository</span> LoginViewModel viewModel <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LoginViewModel</span> <span class="token punctuation">(</span> fakeUserRepository <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function">assertEquals</span> <span class="token punctuation">(</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
End-to-end tests
For integration tests, a good practice is to create a meaningful TestApplicationComponent for testing. Production and testing use a different component configuration.
This requires more modular design than the actual modules in the application. Component testing extends the production component and installs a different set of modules.
1 2 3 4 5 6 7 8 9 | <span class="token comment">// TestApplicationComponent extends from ApplicationComponent to have them both</span> <span class="token comment">// with the same interface methods. You need to include the modules of the</span> <span class="token comment">// Component here as well, and you can replace the ones you want to override.</span> <span class="token comment">// This sample uses FakeNetworkModule instead of NetworkModule</span> <span class="token annotation punctuation">@Singleton</span> <span class="token annotation punctuation">@Component</span> <span class="token punctuation">(</span> modules <span class="token operator">=</span> <span class="token punctuation">{</span> FakeNetworkModule <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">,</span> SubcomponentsModule <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">TestApplicationComponent</span> <span class="token keyword">extends</span> <span class="token class-name">ApplicationComponent</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> |
FakeNetworkModule has fake implemented the original NetworkModule. There we can provide fake or mock fields of whatever you want to replace.
1 2 3 4 5 6 7 8 9 10 11 | <span class="token comment">// In the FakeNetworkModule, pass a fake implementation of LoginRetrofitService</span> <span class="token comment">// that you can use in your tests.</span> <span class="token annotation punctuation">@Module</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FakeNetworkModule</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Provides</span> <span class="token keyword">public</span> LoginRetrofitService <span class="token function">provideLoginRetrofitService</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">FakeLoginService</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
In integration or end-to-end tests, you will use TestApplication to create TestApplicationComponent instead of ApplicationComponent
1 2 3 4 5 | <span class="token comment">// Your test application needs an instance of the test graph</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyTestApplication</span> <span class="token keyword">extends</span> <span class="token class-name">MyApplication</span> <span class="token punctuation">{</span> ApplicationComponent appComponent <span class="token operator">=</span> DaggerTestApplicationComponent <span class="token punctuation">.</span> <span class="token function">create</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
This testing application is then used in the custom TestRunner that you will use to run instrumentation tests.
6. Working with Dagger modules
Dagger modules are a way to encapsulate how to provide objects semantically. We can include modules within components but you can include modules inside other modules. This is powerful but also easy to use wrong.
When a module has been added to another component or module, it is already in the Dagger diagram; Dagger can provide objects in that composition. Before adding a module, check that the module must be part of the Dagger diagram by checking if it has been added to the component by compiling the program so that Daggercos can find the necessary dependencies for That module does not.
Good practice indicates that modules should only be declared once in a component
Suppose you have a chart configured this way. ApplicationComponent includes Module1 and Module2 and Module1 includes ModuleX
1 2 3 4 5 6 7 8 9 | <span class="token annotation punctuation">@Component</span> <span class="token punctuation">(</span> modules <span class="token operator">=</span> <span class="token punctuation">{</span> Module1 <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">,</span> Module2 <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ApplicationComponent</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Module</span> <span class="token punctuation">(</span> includes <span class="token operator">=</span> <span class="token punctuation">{</span> ModuleX <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Module1</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Module</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Module2</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> |
If now Module2 depends on the classes provided by ModuleX. A bad practice is to include ModuleX in Module2 because ModuleX is included twice in the diagram as seen in the following code:
1 2 3 4 5 6 7 8 9 10 | <span class="token comment">// Bad practice: ModuleX is declared multiple times in this Dagger graph.</span> <span class="token annotation punctuation">@Component</span> <span class="token punctuation">(</span> modules <span class="token operator">=</span> <span class="token punctuation">{</span> Module1 <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">,</span> Module2 <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ApplicationComponent</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Module</span> <span class="token punctuation">(</span> includes <span class="token operator">=</span> ModuleX <span class="token operator">:</span> <span class="token operator">:</span> <span class="token keyword">class</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Module1</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Module</span> <span class="token punctuation">(</span> includes <span class="token operator">=</span> ModuleX <span class="token operator">:</span> <span class="token operator">:</span> <span class="token keyword">class</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Module2</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> |
Instead, one should do one of the following:
- Reconstruction of the modules and extract the common modules into components.
- Create a new module with objects that both modules share and extract it into the component
Not restructuring in this way leads to a lot of modules being included with each other without a clear sense of organization and making it harder to see where each dependency comes from.
Good practice (option 1): ModuleX is declared once in the Dagger diagram
1 2 3 4 5 6 7 8 9 | <span class="token annotation punctuation">@Component</span> <span class="token punctuation">(</span> modules <span class="token operator">=</span> <span class="token punctuation">{</span> Module1 <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">,</span> Module2 <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">,</span> ModuleX <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ApplicationComponent</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Module</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Module1</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Module</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Module2</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> |
Good practice (option 2): Common dependencies from Module1 and Module2 in ModuleX are extracted from a new module called ModuleXCommon that is included in the Component. Then two modules named ModuleXWithModule1Dependencies and ModuleXWithModule2Dependencies are created with dependencies specific to each module. All modules are declared once in the Dagger diagram
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token annotation punctuation">@Component</span> <span class="token punctuation">(</span> modules <span class="token operator">=</span> <span class="token punctuation">{</span> Module1 <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">,</span> Module2 <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">,</span> ModuleXCommon <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ApplicationComponent</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Module</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ModuleXCommon</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Module</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ModuleXWithModule1SpecificDependencies</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Module</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ModuleXWithModule2SpecificDependencies</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Module</span> <span class="token punctuation">(</span> includes <span class="token operator">=</span> ModuleXWithModule1SpecificDependencies <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Module1</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Module</span> <span class="token punctuation">(</span> includes <span class="token operator">=</span> ModuleXWithModule2SpecificDependencies <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Module2</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> |
7. Conclusion
Through this article, I have basically introduced using Dagger. To complete this article, I have consulted a document from Google Android. Surely there are many shortcomings, I hope to receive contributions from you and will supplement.