Start with the official Dagger handle
1. Introduction
In this article, you’ll learn about the importance of dependency injection (DI) for creating a robust and scalable large-scale application. We will use the Dagger as a DI tool to manage dependencies.
Dependency injection (DI) is a widely used programming technique that is well suited for Android development. Following the principles of DI can lay the foundation for a good application architecture.
Implementing dependency injection gives you the following advantages:
- Reuse code
- Easy to reconstruct
- Easy to test
Preliminary knowledge
- Have used Kotlin
- Understand dependency injection and see the benefits of using Dagger in Android applications
You can find out more about dependency injection and how to use Dagger at 👇
- Dependency injection in Android
- Manual dependency injection
- Dagger is used in Android
What will you learn
- How do I use Dagger in a large scale Android application
- Dagger thinking clearly to create a more robust and sustainably maintained application
- Why do I need the Dagger child components and how do I use them
- How do I use Dagger to complete the unit and pin tests for the application
At the end of this article, you will have created and tested a dependency diagram for an application class that looks like this:
2. Preparation
Get the code
Get the source code here or at Github, and follow the README instructions to complete the project configuration.
It is recommended to branch out from master and start coding while reading
Add Dagger library to project
Open the project configuration file app/build.gradle, add the two Dagger dependencies, then add the Kapt Plugin at the top of the file as follows:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'. dependencies { ...def dagger_version = "2.27"
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
}
Copy the code
Once the above configuration is added and clicked on ‘Sync Now’ the project automatically downloads the relevant configuration, we can start using the Dagger.
kapt
Dagger is implemented from Java’s annotation model, which uses an “annotation handler” to generate code at compile time. In Kotlin, the “annotation handler” is implemented using the kapt compilation plug-in, which corresponds to the aforementioned configuration statements apply Plugin: ‘kotlin-kapt’ and apply plugin: ‘kotlin-android-extensions’
Note: This is different from Java’s annotationProcessor, which generates annotation statements directly using JavAC.
Only the runtime generates the statement
In the dependency configured above, the dagger library contains all the annotations you can use, and the dagger-Compiler acts as the annotation processor to generate the code for us. It doesn’t itself get tucked into the App pack when you pack your App.
Here we can get the latest Dagger post information.
3. Start with @Inject — constructor injection and attribute injection
Let’s start by refactoring the registration process with the Dagger.
Dagger in order for us to automatically build the dependency graph for the application class, it needs to tell it how to create an instance of the class within the application. One way to do this is by adding @inject to the constructor of the class. The member attributes of this class are treated as a dependent type.
Open the registrationViewModel.kt file and replace the class definition declaration with the following statement:
// @Inject tells Dagger how to provide instances of this type
// Dagger also knows that UserManager is a dependency
class RegistrationViewModel @Inject constructor(val userManager: UserManager) {
...
}
Copy the code
As shown above, to apply an annotation to a class constructor in Kotlin syntax, you explicitly add the keyword constructor and then prefix it with the annotation.
With the @inject annotation, Dagger knows:
- How to createRegistrationViewModelClass.
- RegistrationViewModelThere is aUserManagerAs a dependency, one is required when constructing itUserManagerAs a parameter.
An aside:
We’re just getting started. To simplify things, RegistrationViewModel is not a ViewModel as we know it in Android framework components; It’s just a regular class that plays and assumes the responsibilities of a ViewModel.
For more information on using the Dagger in an Android framework component, download the Dagger from the official Android Blueprint code base and learn about it yourself.
However, the Dagger does not know how to create the UserManager, we can add @Inject before the Constructor of the UserManager by following the same processing above.
Open the userManager.kt file and replace the class definition declaration with the following statement:
class UserManager @Inject constructor(private val storage: Storage) {
...
}
Copy the code
Now, the Dagger knows how to provide instances of RegistrationViewModel and UserManager.
While the UserManager dependency is an interface, we need to use a different way to tell the Dagger how to create an instance of it, which we’ll explain later, for now press no table.
Obviously the Android framework’s own classes such as Activities and Fragments are initialized by the system, so the Dagger cannot be created for the developer. Activities In most cases, for the developer, all initialization code needs to be performed in the onCreate method. Because of this, we cannot use @inject annotations (also known as constructor injection) before the constructor of a view class. Instead, we have to use property injection.
In our application, the RegistrationActivity has a dependency on the RegistrationViewModel. If you open RegistrationActivity, we can see that in the onCreate method we created the ViewModel before calling supportFragmentManager. We don’t want to create it manually, we want the Dagger to provide it. In this regard, we need to:
- Annotate this attribute with @Inject
- Remove its initialization from the onCreate() method
class RegistrationActivity : AppCompatActivity() {
// @Inject annotated fields will be provided by Dagger
@Inject
lateinit var registrationViewModel: RegistrationViewModel
override fun onCreate(savedInstanceState: Bundle?).{...// Remove following line
registrationViewModel = RegistrationViewModel((application as MyApplication).userManager)
}
}
Copy the code
Constructor injection, which tells the Dagger how to provide an instance of the class; Property injection, which tells the Dagger class what type of member variable to populate.
How can we tell which Dagger object needs to be injected into the RegistrationActivity? We need to create a Dagger dependency graph (or application dependency graph) and use it to inject objects into the Activity.
The @Component annotation — a container that implements the dependency diagram
We want to create a dependency graph for our project, manage them for us, and be able to get all the dependency objects from that graph. For the Dagger to do this, we need to create an interface and annotate it with @Component. Dagger creates a container to implement manual dependency injection for the developer.
An interface annotated with @Component will make Dagger generate code with all the dependencies required to satisfy the parameters of the methods it exposes. Inside that interface, we can tell Dagger that RegistrationActivity requests injection.
We in the example below engineering com. Example. Android. The dagger package, create a new package of di. Inside the package, create a new Kotlin file, appComponent.kt, and define the following interface in the file:
package com.example.android.dagger.di
import com.example.android.dagger.registration.RegistrationActivity
import dagger.Component
// Definition of a Dagger component
@Component
interface AppComponent {
// Classes that can be injected by this Component
fun inject(activity: RegistrationActivity)
}
Copy the code
Define inject(Activity:) in the @Component interface, as shown above. RegistrationActivity), which is equivalent to the developer telling the Dagger RegistrationActivity that it needs injection, and it must provide those dependent objects marked with @Inject (e.g. : RegistrationViewModel, which I mentioned in the previous section.
Since the Dagger must create a RegistrationViewModel entity object, it means that it also creates a dependent object (e.g. UserManager) that satisfies the RegistrationViewModel. Similarly, the Dagger recursion discovers all dependencies, and if it finds a dependency that it does not know how to provide, it will report an error to the developer at compile time telling it that it cannot satisfy the corresponding dependency.
Summary: An @Component interface provides information for the Dagger dependency graph that needs to be generated at compile time. The class to be injected as the parameter type of the interface method.
After adding the above code, you can try to compile the entire project as follows:
dagger/app/build/tmp/kapt3/stubs/debug/com/example/android/dagger/di/AppComponent.java:7: error: [Dagger/MissingBinding] com.example.android.dagger.storage.Storage cannot be provided without an @Provides-annotated method
Let’s analyze this error message. First, it tells us that the error occurred in the AppComponent. The type of the error is [Dagger/MissingBinding] which means that Dagger does not know how to provide an explicit type. Finally, it tells the Storage that it cannot be provided.
That is, we do not tell the Dagger how to provide a Storage object required by the UserManager!
5. @Module, @Sharing, @Bindsinstance
Since Storage is an interface, it cannot be initialized directly, so the way we tell each Dagger how to provide a Storage entity object is different. We need to tell Dagger which implementation class of Storage we want to use. In this example, the implementation class is SharedPreferencesStorage.
To do this, we will use a Dagger Module. Dagger Module is a class annotated with @Module.
Similar to Components, the Dagger Module is a class that communicates with Dagger and tells Dagger how to provide an entity of a specific type. Dependent objects are defined using the @providers and @Sharing annotations.
More information about @Providers can be found in the official Using Dagger in Android Apps Documentation or in the final section of this article.
Since this Module will contain information about Storage, let’s create a new file named StorageModule. Kt under the same package we created appComponent.kt. The specific code is as follows:
package com.example.android.dagger.di
import dagger.Module
// Tells Dagger this is a Dagger module
@Module
class StorageModule {}Copy the code
@ Bind annotations
Using @bind tells Dagger which implementation class of that interface it can use when it needs to provide an interface.
Bind must annotate an abstract method. The return value of this abstract method is the type of interface that the Dagger provides. The implementation class corresponding to the interface is taken as an argument to the method. As follows:
// Tells Dagger this is a Dagger module
// Because of @Binds, StorageModule needs to be an abstract class
@Module
abstract class StorageModule {
// Makes Dagger provide SharedPreferencesStorage when a Storage type is requested
@Binds
abstract fun provideStorage(storage: SharedPreferencesStorage): Storage
}
Copy the code
Above, we tell Dagger “When you need a Storage object, please use SharedPreferencesStorage”
Key points:
-
ProvideStorage is just the name of an abstract method, it can be named whatever we like, Dagger doesn’t care what it’s called. Dagger only cares about its return value and arguments.
-
The StorageModule here is an abstract class, only because provideStorage is an abstract method.
Modules is a semantic layer path that encapsulates the semantics that tell how the Dagger creates some objects. As shown above, we named the StorageModule class and added logic to it about how to provide a Storage object. If we extend our application later, we can also provide an implementation class that is different from SharedPreferencesStorage.
Okay, so we told you above that when you want to generate a Storage object, you should use an instance of SharedPreferencesStorage, But we haven’t told Dagger how to create a SharedPreferencesStorage yet. As before, we do this by preempting the constructor of SharedPreferencesStorage with @Inject.
// @Inject tells Dagger how to provide instances of this type
class SharedPreferencesStorage @Inject constructor(context: Context) : Storage { ... }
Copy the code
In addition, our application dependency diagram needs to know that StorageModule exists. Therefore, we need to add it to the AppComponent, annotating @Component as the value of the Modules argument, as follows:
// Definition of a Dagger component that adds info from the StorageModule to the graph
@Component(modules = [StorageModule::class])
interface AppComponent {
// Classes that can be injected by this Component
fun inject(activity: RegistrationActivity)
}
Copy the code
This gives the AppComponent access to all the information contained in the StorageModule. In more complex applications, we might also have a NetworkModule that tells the container how to provide an OkHttpClient, how to configure a Gson or Moshi, and so on.
Ok, now if you Build our project again, you will find the error message we had before! Dagger can’t find the Context.
@BindsInstance annotation
How do we tell Dagger to provide a Context? Context is an object provided by the Android system, so its creation must occur outside of the application dependency diagram. Since the Context is valid before the dependency graph exists, we can create an entity in the dependency graph and then assign the Context to it.
This is done by defining a Component Factory with the @bindsInstance annotation:
@Component(modules = [StorageModule::class])
interface AppComponent {
// Factory to create instances of the AppComponent
@Component.Factory
interface Factory {
// With @BindsInstance, the Context passed in will be available in the graph
fun create(@BindsInstance context: Context): AppComponent
}
fun inject(activity: RegistrationActivity)
}
Copy the code
As shown above, we declare an interface using the @Component.Factory annotation, which also contains a method whose return type is Component and an argument of type Context using the @bindsInstance annotation.
The @bindsInstance tells the Dagger that the Dagger needs to add an instance to the dependency diagram container and that the instance can be used whenever a Context object is needed.
Use @bindsInstance — when an object is created outside the dependency diagram container (e.g. Context in the example)
🤩 Ok, now we have completed the initial registration process with the Dagger reconstruction
Build the project again, no more errors will be reported… Dagger has successfully generated application dependency diagrams and you can start using them.
The application dependency map is automatically generated by the annotation processor. The generated class is named Dagger{ComponentName} and contains the implementation of the dependency diagram. We’ll use the generated class DaggerAppComponent in the next section.
What does the AppComponent dependency graph look like now?
The AppComponent contains a StorageModule that tells you how to create a Storage instance with a Dagger. Storage has a dependency on the Context, but since we provided the Context when we created the dependency graph, Storage implicitly depends on the Context as well.
An instance of this Context is created using the Create method in the AppComponent factory. Thus, the Context can be considered a “global” object, which we identify in the figure above by placing a white dot in the upper left corner of it.
Now, the RegistrationActivity can access the Dagger dependency graph container to get the injected object, such as the RegistrationViewModel in this example (because the member variable is preceded by the annotation @Inject).
The following describes how the Dagger can be injected automatically after the compiler generates the dependency graph container:
As an AppComponent, it needs to inject the RegistrationViewModel into the RegistrationActivity. It needs to create an instance of the RegistrationViewModel. To do this, it needs to provide all the dependencies of RegistrationViewModel, which in turn needs to create an instance of UserManager.
As the UserManager, its constructor uses the @Inject annotation, and the Dagger will use this constructor to create the instance. UserManager has a Storage dependency again, but it is already in the dependency graph and does not need to do anything else.
Use the Dagger injection in the Activity
On Android, developers usually need to create a Dagger dependency graph container in their custom Application because the developer needs the dependency graph container to remain in memory while the App is running. In this way, the dependency graph is attached to the life cycle of the App. In our case, we also want to have an application Context in the dependency diagram. The advantage of this is that the dependency graph can be used by other Android framework classes (accessible through their Context), and it is also useful for testing, since you are using a custom Application class.
Let’s add a dependency graph instance to our custom Application — MyApplication:
open class MyApplication : Application() {
// Instance of the AppComponent that will be used by all the Activities in the project
val appComponent: AppComponent by lazy {
// Creates an instance of AppComponent using its Factory constructor
// We pass the applicationContext that will be used as Context in the graph
DaggerAppComponent.factory().create(applicationContext)
}
open val userManager by lazy {
UserManager(SharedPreferencesStorage(this))}}Copy the code
As we mentioned in the previous chapter, when we build the entire project, the Dagger generates a class called DaggerAppComponent that contains all implementations of the AppComponent dependency diagram. Since we use the @Component.factory annotation, we can call.factory(), which is a static method in DaggerAppComponent. So we can now also call the create method we defined earlier in the Factory interface, where we pass applicationContext as the value of the Context parameter.
Here we use Kotlin lazy initialization, so the variable is immutable and will only be initialized when it is used.
Note: If you are told by AndroidStudio that the DaggerAppComponent does not exist, you may need to Build a project that lets the Dagger annotation handler generate the code. Therefore, you must Build the project again when there is a new Dagger that needs to take effect.
We can then use an instance of the dependency diagram container in the RegistrationActivity to make the Dagger Inject instances of member variables annotated with @Inject. So how do we do that? We can call the AppComponent Inject method, passing it with the RegistrationActivity as an argument.
No need for developers to initialize these attributes themselves, Dagger will now initialize these attributes for developers as follows:
class RegistrationActivity : AppCompatActivity() {
// @Inject annotated fields will be provided by Dagger
@Inject lateinit var registrationViewModel: RegistrationViewModel
override fun onCreate(savedInstanceState: Bundle?). {
// Ask Dagger to inject our dependencies
(application as MyApplication).appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_registration)
// REMOVE THIS LINE
registrationViewModel = RegistrationViewModel((application as MyApplication).userManager)
supportFragmentManager.beginTransaction()
.add(R.id.fragment_holder, EnterDetailsFragment())
.commit()
}
...
}
Copy the code
The appComponent.Inject (this) method is called as above to inject a member variable annotated with @Inject in the RegistrationActivity.
Underline: When using a Dagger injection in an Activity, the Inject method must be called before the super.oncreate method to avoid problems when restoring the fragment. Because in super.onCreate, when the corresponding Activity is in the recovery phase, the fragment attached to it may access the Activity’s binding.
RegistrationActivity now uses the Dagger to manage its dependencies. You can take the next step and put the whole thing up and running.
Do you see any bugs? After the registration process, the login page on the home page should disappear, but it doesn’t. Why is that? The other processes do not use the Dagger dependency diagram container.
MainActivity still uses the userManager defined in MyApplication, however the registration module does already use a userManager from the Dagger dependency graph container.
Now let’s fix this problem with the Dagger in the main flow as well.
Using Dagger in the Main Flow
As before, we want to manage its dependency with a Dagger in MainActivity. In this case, we have MainViewModel and UserManager.
Add @inject to the property to be injected. Add new methods to the AppComponent container interface that take MainActivity as an argument, and so on. Once done, we can get a new dependency graph, which looks like this:
However, there is one more caveat:
Underline: The Dagger property injection, corresponding property cannot be private, the property must be package visible and above.
At this point we run the App again and it’s still buggy because the Dagger default provides a new injected property each time, in this case UserManager. So how do we make Dagger reuse the same entity property every time? In this case, Scope is used.
7, Use the Dagger container’s Using Scopes.
Sometimes, for complex reasons, developers may want to provide a globally unique instance of a dependent object within a Component, such as:
- Developers need to share the same instance among multiple dependencies (for example, in this example)UserManager).
- Initialization of an object is expensive, so developers don’t want to create a new object every time they use it (e.g., a Json parser).
Using scoped type annotations on the Dagger container (that is: @scope), you can achieve a unique instance of the Dagger container. It can also be called “scoped type consistent with the life cycle of the main container.” The scope type is the same as the main container declaration cycle, which means that any business module injected with Dagger will provide the same type of object for all business modules if the Dagger needs to provide an object for that object and the same annotation is added to the class of the main container.
The translator’s note:
First of all, this is a very, very, very important part of understanding the Dagger Scope;
Then, make fun of it, the above paragraph is not translated sentence by sentence according to the original text, but combined with my own understanding to translate, sentence by sentence translation that is destined to be a tragedy, absolutely can not understand, I am too difficult, because they want to explain the problem is relatively complex, a bunch of clauses can not be round;
And then I want to talk about @Singleton and the example of implementing a SingletonUserManagerObject doesn’t really matter, it’s just a name, maybe a name, but a Singleton is just a name, and the reason it works as a scoped type annotation is because it uses it, right@Scope. You can change the name altogether, and just make a custom @scope annotation. And it’s possible to realize the meaning of singleton, and that’s because in this exampleAppComponentIts instance is defined in the custom Application of the Application project. From the perspective of the whole Application,AppComponent“Is a Singleton, so @singleton is just a name.
@Singleton is a scope word, so the main container can’t be used, any other container with a different scope can’t be used, because once the container with a different scope has the same name how does your let Dagger locate it? It is impossible to define two identical file names for the same folder
Finally, the above paragraph will be helpful in understanding the subcontainer scope that we will use later.
To do this, we can use @singleton as follows:
AppComponent.kt
@Singleton
@Component(modules = [StorageModule::class])
interface AppComponent {... }Copy the code
UserManager.kt
@Singleton
class UserManager @Inject constructor(private val storage: Storage) {
...
}
Copy the code
At this point, we have solved the registration problem
The above code can be found in the 1_registration_main branch
Here’s the current dependency chart:
You can see that a white dot has been added to the UserManager, similar to Context.
8 Subcomponents.
The translator’s note: Here a long, I also don’t want to translation, too wordy, in simple terms, is to continue holding the Dagger is used to reconstruct the original registration processes, EnterDetailsFragment and TermsAndConditionsFragment still rely on problems, You need to inject the ViewModel via Dagger.
To highlight:
An Activity injects Dagger in the onCreate method before calling super.
A Fragment injects Dagger in the onAttach method after calling super.
To do this, add the following code to the AppComponent:
@Singleton
@Component(modules = [StorageModule::class])
interface AppComponent {...fun inject(activity: RegistrationActivity)
fun inject(fragment: EnterDetailsFragment)
fun inject(fragment: TermsAndConditionsFragment)
fun inject(activity: MainActivity)
}
Copy the code
Copy before the injection of way will be an error, because TermsAndConditionsFragment and EnterDetailsFragment, is to share a RegistrationViewModel, So we have to use the Dagger SubComponents to solve this.
We want the registration Fragments to reuse the same ViewModel coming from the Activity, but if the Activity changes, we want a different instance. We need to scope RegistrationViewModel to RegistrationActivity, for that, we can create a new Component for the registration flow and scope the ViewModel to that new registration Component. To achieve this we use Dagger subcomponents.
Dagger Subcomponents
Since the RegistrationViewModel relies on UserRepository, the RegistrationComponent must be able to fetch objects from the AppComponent. Using the Dagger SubComponents tells the Dagger that we want to create a new container using a partial module of another container. The new container (e.g. RegistrationComponent in this example) must be a child container that contains and shares the resources of another container (e.g. AppComponent in this example).
Child containers inherit and extend from a parent container, so all objects provided by the parent can be provided by the child container. Because of this, objects in a child container can depend on objects in the parent container.
Since this is for the “registration” module, we can create a new file named registrationComponent.kt under the registration package. In this file, We create a new interface called RegistrationComponent. I then annotate the interface with @subComponent to tell you that the Dagger is a child container.
registration/RegistrationComponent.kt
package com.example.android.dagger.registration
import dagger.Subcomponent
// Definition of a Dagger subcomponent
@Subcomponent
interface RegistrationComponent {}Copy the code
The container needs to contain information about registration, so we need to do two things:
-
Move the relevant registration in AppComponent inject method to the child in the container (such as: RegistrationActivity, EnterDetailsFragment, TermsAndConditionsFragment);
-
Create a child container factory that we can use to create instances of child containers.
RegistrationComponent.kt
// Definition of a Dagger subcomponent
@Subcomponent
interface RegistrationComponent {
// Factory to create instances of RegistrationComponent
@Subcomponent.Factory
interface Factory {
fun create(a): RegistrationComponent
}
// Classes that can be injected by this Component
fun inject(activity: RegistrationActivity)
fun inject(fragment: EnterDetailsFragment)
fun inject(fragment: TermsAndConditionsFragment)
}
Copy the code
In the AppComponent, we have to remove all registration injection methods because we don’t use it anymore and use RegistrationComponent instead.
In addition, in order for RegistrationActivity to create an instance of RegistrationComponent, we need to expose the factory of the child container in the AppComponent interface, as follows:
@Singleton
@Component(modules = [StorageModule::class])
interface AppComponent {
@Component.Factory
interface Factory {
fun create(@BindsInstance context: Context): AppComponent
}
// Expose RegistrationComponent factory from the graph
fun registrationComponent(a): RegistrationComponent.Factory
fun inject(activity: MainActivity)
}
Copy the code
As you can see from the above code, we can expose the injection to related dependencies by defining a method in the main container whose return value is defined as the factory type of the child container RegistrationComponent.
So far, we can see from the above section that there are two ways for the business module and Dagger dependency graph to interact:
Declare a method in the main container that returns Unit (except in Java, there is no return value) and then take a class as the parameter type of the method, which means that the Dagger is allowed to implement property injection in that class. (e.g. Fun inject(activity: MainActivity))
Declare a method with a return value in the main container, which means that entities of that type can be retrieved from the dependency graph. (such as: fun registrationComponent () : registrationComponent Factory)
Now we must also let the AppComponent know that it has a child container RegistrationComponent so that it can generate code for the child container. We need to create a Dagger Module to do this.
Let’s create a file called AppSubComponents. kt in the DI package. In this file, we define a class called AppSubcomponents and add the @Module annotation to it. To specify which business Module dependencies the subcontainer AppSubcomponents contains, we add a subcontainer collection type parameter named subComponents to the @Module annotation as follows:
// This module tells AppComponent which are its subcomponents
@Module(subcomponents = [RegistrationComponent::class])
class AppSubcomponents
Copy the code
The new Module also needs to be added to the AppComponent as follows:
@Singleton
@Component(modules = [StorageModule::class, AppSubcomponents::class])
interface AppComponent {... }Copy the code
What does the application dependency graph look like today?
View classes specific to registration get injected by the RegistrationComponent. Since RegistrationViewModel and EnterDetailsViewModel are only requested by classes that use the RegistrationComponent, they’re part of it and not part of AppComponent.
Scoping Subcomponents
To share the same RegistrationViewModel instance between activities and multiple Fragments, we create a child container. As we mentioned earlier in the section Using Scopes for the Dagger container, let’s use the same scoped annotation, Add it to the subcontainer class RegistrationComponent and the class RegistrationViewModel of the instance that needs to be shared, respectively, so that the instance is unique for the lifetime of the subcontainer.
However, since we already used @Singleton in the AppComponent, we need to create a scoped annotation with a different name.
In this case, I could name the scoped annotation @registrationScope, but that would not be a good design. It is best not to name scoped annotations using specific business names. Its name should depend on the declaration cycle so that the scoped annotation can be reused by other containers with similar declaration cycles (e.g. LoginComponent, SettingsComponent, and so on). Therefore, we use @ActivityScope instead of @registrationScope.
Scope criteria:
When a type is marked by a scoped annotation, it can only be used in the container corresponding to the scoped annotation.
When a container is marked with a scoped annotation, it can provide either a type annotated by the scope or a type not annotated by the scope.
A child container cannot use its parent’s scoped annotations.
The context of the main container also contains child containers.
To start coding, let’s create a new ActivityScope under the DI package as follows:
@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class ActivityScope
Copy the code
Specify the same scope as RegistrationComponent for RegistrationViewModel as follows:
// Scopes this ViewModel to components that use @ActivityScope
@ActivityScope
class RegistrationViewModel @Inject constructor(val userManager: UserManager) {
...
}
Copy the code
// Classes annotated with @ActivityScope will have a unique instance in this Component
@ActivityScope
@Subcomponent
interface RegistrationComponent {... }Copy the code
The translator’s note:
There are five chapters left, but I won’t translate them for the sake of time. It’s not too difficult to read, combined with the source code for the sample and the generated DaggerAppComponent
The remaining five chapters are roughly:
“Refactor logon flow” — basically it is recommended that when using the Dagger container, the business logic of the container should not be too large, which is not readable and maintainable, and should be refined as appropriate
“Same scoped object used in multiple activities” — this is the case with the Dagger subcontainer UserDataRepository in UserMananger to reconstruct its dependency. This example is purely for the purpose of showing you the use of Dagger, which is not recommended by the article, which calls it “Conditional property injection is very dangerous”, and this leads to the last chapter, which needs to be completed by the reader.
“Unit test for Dagger” — kaptAndroidTest
@Providers and Quantifiers — @Providers and @ Cursorier are similar, but less capable; But it can provide classes outside of engineering. As for the Qualifier, the Dagger library implements the @Qualifier annotation of type @qualifier on its own as @name in Java, except that the @name in Java is not fully applicable to the Android specific compiler and production environment
The above is all content, interested can go to see the original, this article is only as a personal note and warm up the content ~~ can be used to better use Hilt, lay a foundation.