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
class Cat(val name: String)
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: ${garfield.name}"
helloKittyTextView.text = "injected: ${helloKitty.name}"
}
}
So we have 2 @Provides
in the Module
:
@Module
class CatModule {
@Provides
fun provideGarfield(): Cat = Cat("Garfield")
@Provides
fun provideHelloKitty(): Cat = Cat("Hello Kitty")
}
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:
@Module
class CatModule {
@Provides
@Named("Garfield")
fun provideGarfield(): Cat = Cat("Garfield")
@Provides
@Named("HelloKitty")
fun provideHelloKitty(): Cat = Cat("Hello Kitty")
}
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: ${garfield.name}"
helloKittyTextView.text = "injected: ${helloKitty.name}"
}
}
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
.
That's the usage of @Named
🎉
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 🎉
This is how we use it:
The providers 👇
@Provides
@NamedClone("Red Apple")
fun provideRedApple(): Apple = Apple("red")
@Provides
@NamedClone("Green Apple")
fun provideGreenApple(): Apple = Apple("green")
The the injectors 👇
@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 {
Red, Green
}
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!
Next up: @Scope
. Stay tune!
Tan Jun Rong
Clap to support the author, help others find it, and make your opinion count.