- Dagger 2 for Android, Part III ー The @Qualifier and @Named Annotation
- Why do we need @Named?
- @Named to the rescue!
- Tweaking @NamedClone to use enum
- Wrapping up
- Kotlin and Dagger2 Qualifier Incompatibility
- How to use Dagger’s 2 @Named qualifier in Kotlin
- Dagger 2 Kotlin — @Named qualifier for @Binds method
- Kotlin qualifier annotaion is ignored
- Dagger2 — How to use @Named with @BindsInstance
Dagger 2 for Android, Part III ー The @Qualifier and @Named Annotation
In this article, I will talk about how to use the @Qualifier and @Named annotation.
This is part of my Dagger 2 blog series:
- Dagger 2 for Android, Part I ー What is Dependency Injection?
- Dagger 2 for Android, Part II ー The Basic Usage
- Dagger 2 for Android, Part III ー The @Qualifier and @Named Annotation (you are here)
- Dagger 2 for Android, Part IV ー The @Scope Annotation
- Dagger 2 for Android, Part V ー @Inject for Constructor Injection
- Dagger 2 for Android, Part VI ー @Component.Builder and @BindsInstance
The code example used in this article will be written in Kotlin for Android development.
Pre-requiresite: Understanding basic usage of Dagger 2: @Inject , Provides , @Module , and @Component . Read Part II above if you haven’t 😀
👆
Why do we need @Named?
In my previous post, I talked about how to use @Inject to inject a field variable and how @Provides fulfill the dependency to it.
There is a limitation though, we can only have a single @Provides for this type.
Let’s say we have Cat class for example, that we will use in our MainActivity
But this time we want to use 2 cats: «Garfield» and «HelloKitty» :
class MainActivity : AppCompatActivity() < @Inject lateinit var garfield: Cat @Inject lateinit var helloKitty: Cat override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContentView(. ) DaggerCatComponent.builder().build().inject(this) garfieldTextView.text = "injected: $" helloKittyTextView.text = "injected: $" > >
So we have 2 @Provides in the Module :
But if you try to compile this code above, you will get this error:
error: [Dagger/DuplicateBindings] packagename.something.something.Cat is bound multiple times:
That is because Dagger2 is confused which @Provides should go into which @Inject , because both are the same type Cat . If one of them is of Dog type, we will not have this problem.
@Named to the rescue!
To solve this we can use the annotation @Named . By using this annotation, we can give the providers name:
Now Dagger2 is able to differentiate between Garfield and Hello Kitty . We can use it in the MainActivity like this:
class MainActivity : AppCompatActivity() < @Inject @field:Named("Garfield") lateinit var garfield: Cat @Inject @field:Named("HelloKitty") lateinit var helloKitty: Cat override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContentView(R.layout.activity_qualifier) DaggerCatComponent.builder().build().inject(this) garfieldTextView.text = "injected: $" helloKittyTextView.text = "injected: $" > >
By annotaing @field:Named(«HelloKitty») on top of helloKitty variable, Dagger2 is able to recognize that the provider will be provideHelloKitty() . And the same for Garfield .
What is @Named?
The @Named annotation is good for identifying which provider to be used when we are trying to inject the dependency of the same type. Let’s take a look at the @Named annotation.
If you click into the definition of @Named , you will see this:
@Qualifier @Documented @Retention(RUNTIME) public @interface Named < /** The name. */ String value() default ""; >
The important annotation here to take note is @Qualifier . This is actually an annotation that is used to define new annotation. It is also defined in the JSR330 standard (Doc is here). In fact, we can define our own qualifier that will be similar to @Named .
Optional reading 1
JSR330 is a set of Specification that is maintained by some committe members. So that any DI libraries which follows this specification will have the same interface. So if you look at ToothPick (another DI framework) for example, you can find @Qualifier or @Inject , etc.
Optional reading 2
As for @Documented and @Retention(RUNTIME) you can look them up separately if you are interested. It seems to be a convention to always add them when defining new qualifier. I’ve tried to remove them and Dagger2 works just fine. @Documented is only used for documentation as the name indicated. As for @Retention(RUNTIME) , this answer suggests that it is there due to legacy reason when older Reflection-based DI library (Roboguice) needed it, but Dagger2 doesn’t actually need them.
Let’s define our own qualifier that is similar to @Named . Since all my code example is in Kotlin, let’s rewrite @Named in Kotlin. I will call it @NamedClone
@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class NamedClone(val value: String = "")
Yay! Now we just defined our own @Named annotation 🎉
@Provides @NamedClone("Red Apple") fun provideRedApple(): Apple = Apple("red") @Provides @NamedClone("Green Apple") fun provideGreenApple(): Apple = Apple("green")
@Inject @field:NamedClone("Red Apple") lateinit var redApple: Apple @Inject @field:NamedClone("Green Apple") lateinit var greenApple: Apple
The usage is exactly the same as @Named !
Tweaking @NamedClone to use enum
By using @Qualifier , it’s pretty handy. However, if you noticed, we are relying on loose strings in the value of @Named .
It’s pretty error prone. ⚠️
Let’s say we have «Red Apple» in capital in the Module .
@Provides @NamedClone("Red Apple") fun provideRedApple(): Apple = Apple("red")
And we have small case in the injecting side, «red apple»
@Inject @field:NamedClone("red apple") lateinit var redApple: Apple
Dagger2 will throw an error while compiling. 💀
This can be avoided if we change the String into enum .
@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class NamedClone(val color: AppleType) < >enum class AppleType
By using this, you will get syntax error if you misspell it!
Wrapping up
Now that you know how to use @Named and @Qualifier , it is up to you on how to structure your code. Some people prefer to use @Named since it is pre-defined, where some people prefer to always come up with @Qualifier to make things cleaner.
*Remember not to waste too much time on deciding if there is no practical difference for your team! 😆
That’s all for now, hope you learn a thing or two about qualifier annotation!
Kotlin and Dagger2 Qualifier Incompatibility
I am currently utilizing Kotlin 1.0 beta 3. When setting multiple annotations for a property, it is necessary to use the «annotation» keyword. It is worth noting that I am utilizing a non-nullable type, so there is no need to use nullable types. I desired to include the @Named qualifier for an object that is returned from a @Binds method, but discovered that this is only feasible through a static provides method. However, I was unable to implement this practically. The below code compiles successfully on the first attempt, but fails on subsequent builds, indicating that the compiler disregards annotations.
How to use Dagger’s 2 @Named qualifier in Kotlin
Lately, I encountered an issue with the @Named qualifier while working with Kotlin. My initial approach was to modify it from its existing state.
var boldTypeface: Typeface? = null [Inject] set
var boldTypeface: Typeface? = null [Inject Named("bold")] set
var boldTypeface: Typeface? = null [Inject] [Named("bold")] set
Despite the fact that I thought it would resolve the issue, the program is not compiling.
As Kotlin has undergone significant improvements, I had to revise my response. Presently, I am utilizing Kotlin 1.0 beta 3.
To accurately configure a property’s multiple annotations , the @field annotation must be utilized.
@field:[Inject Named("bold")] lateinit var boldTypeface: Typeface
It is worth noting that by utilizing lateinit , there is no requirement for employing a nullable type in Typeface? .
Dagger2 and qualifiers in dependent components, android kotlin dagger-2. Share. Improve this question. Follow edited Jan 1 (you are missing the quotes on UiScheduler), but regardless, this is for a @Named annotation, I am currently using qualifiers. The same solution does not apply, or at least not in the same way as far as I can tell. Is there a variant of this …
Dagger 2 Kotlin — @Named qualifier for @Binds method
My intention was to apply the @Named qualifier to the object obtained from the @Binds method. However, I realized that this can only be achieved by using a static provides method. Unfortunately, implementing this practically proved to be challenging, leaving me unable to achieve my original goal.
After the user logs in, all activities, fragments, and view models will be stored in my personalized UserScope. I have two ViewModel modules: AuthViewModelModule contains the LoginViewModel, and UserViewModelModule contains LeadViewModel and other VMs. Both of these modules bind to the VMProvider.Factory, and therefore, I must use the @Named qualifier for the VMFactory instance. This allows me to inject @Named ones into the relevant activities and fragments.
@Module internal abstract class AuthViewModelModule < @Binds @IntoMap @ViewModelKey(LoginViewModel::class) internal abstract fun bindLoginViewModel(loginViewModel: LoginViewModel): ViewModel @Binds internal abstract fun bindViewModelFactory(factory: AuthViewModelFactory): ViewModelProvider.Factory >@Module internal abstract class UserViewModelModule
Enhance your provider methods by including a qualifier.
@Binds @Named("Auth") internal abstract fun bindViewModelFactory(factory: AuthViewModelFactory): ViewModelProvider.Factory @Binds @Named("User") internal abstract fun bindViewModelFactory(factory: UserViewModelFactory): ViewModelProvider.Factory
The challenging aspect is the injection process, which requires utilizing the subsequent syntax.
@Inject @field:Named("Auth") internal lateinit var factory: ViewModelProvider.Factory @Inject @field:Named("User") internal lateinit var factory: ViewModelProvider.Factory
One way to incorporate a @Named into a Kotlin activity is by injection.
@JvmField @Inject @field:Named("PARAMETER_NAME") var something: Boolean = false
Alternatively, this approach can also be used for values that are not considered primitive.
@JvmField @Inject @field:Named("PARAMETER_NAME") lateinit var something: SomeType
Android — For Dagger 2, Is it possible to have variable, In Dagger 2, I could use @Qualifier to instantiate two object of same type with different arguments e.g. @Module open class Bag < @Provides @Named(LOVE) open fun sayLoveDagger2(): Info < return Info("I Love You") >@Provides @Named(HELLO) open fun sayHelloDagger2(): Info < return …
Kotlin qualifier annotaion is ignored
The code compiles successfully initially but encounters an error during the second build.
The object named SomeObject has been bound more than once, causing an error.
This code block specifies that the SomeModule will provide a non-null object of type SomeObject through the method provideSomeObject().
This code provides a scoped object of type «SomeObject» using the «provideSomeScopedObject()» method in the «SomeModule» module. The method is annotated with «@Provides» and «@NotNull».
The compiler appears to disregard the Qualifier annotations during subsequent builds.
When writing components and modules in Java, the annotations are disregarded in the Main class, which is unhelpful.
@dagger.Component(modules = arrayOf(SomeModule::class)) interface Component < fun inject(main: Main) >class Main < @field:[javax.inject.Inject SomeScope] lateinit var obj: SomeObject >@dagger.Module class SomeModule < @dagger.Provides fun provideSomeObject(): SomeObject < return SomeObject("noScope") >@SomeScope @dagger.Provides fun provideSomeScopedObject(): SomeObject < return SomeObject("someScope") >> data class SomeObject(val name: String) @javax.inject.Qualifier @Retention(AnnotationRetention.SOURCE) annotation class SomeScope
Kindly delete the line containing @Retention(AnnotationRetention.SOURCE) . It is worth noting that Dagger 2 mandates retention RUNTIME which Kotlin already has as its default, thus there’s no need to mention it explicitly.
Dagger 2 Kotlin — @Named qualifier for @Binds method, Add a qualifier to your provider methods: @Binds @Named («Auth») internal abstract fun bindViewModelFactory (factory: AuthViewModelFactory): ViewModelProvider.Factory @Binds @Named («User») internal abstract fun bindViewModelFactory (factory: UserViewModelFactory): …
Dagger2 — How to use @Named with @BindsInstance
Can you explain the implementation of @Named in conjunction with @BindsInstance? This component is what I’m referring to.
interface AppComponent : AndroidInjector< @Component.Builder abstract class Builder : AndroidInjector.Builder() < @BindsInstance abstract fun preferenceName( @Named("PreferenceName") name : String ) : Builder >>
attempting to perform an injection into MyApplication.
@Inject @Named("PreferenceName") lateinit var prefName : String
The code encounters a MissingBinding error for a String. Although it can be fixed by using a module provider, I am attempting to avoid using providers for constants.
A recent update to Dagger (version 2.25.2) has made it unnecessary to use any workarounds.
- Kotlin support ii. Qualifier annotations on fields can now be understood without The need for @field:MyQualifier (646e033) iii. @Module object classes no longer need @JvmStatic on the provides methods. (0da2180)
The issue at hand is not related to @BindsInstance , but instead concerns the annotations on @Named fields. The error message «MissingBinding for String» suggests a problem with a named string that would have occurred otherwise.
In his article «Correct usage of Dagger 2 in Kotlin,» Svetlozar Kostadinov explains how to specify to Kotlin that the annotations should be applied to the field.
@field:[Inject Named("PreferenceName")] lateinit var prefName : String;
The complexity of kotlin annotation is due to its need to function properly from a Java perspective. This is because Kotlin element can be a facade for multiple Java elements emitted in the bytecode. For instance, Kotlin property is a facade for a Java member variable, a getter, and a setter. When annotating a property, Dagger expects the underlying field to be annotated.
Dagger 2 is related to constructor injection and can be used in conjunction with Named arguments in Kotlin.
How to get component dependencies in Dagger 2, An alternative way to using fragment to generate activity context is to create an ActivityComponent and add this as a dependency to your FragmentComponent. In this way, you never have to pass the fragment to the module because the ActivityComponent will provide it for you. It’s completely …