Dagger 2 for Android, Part III ー The @Qualifier and @Named Annotation

Tan Jun Rong avatar

Tan Jun Rong

Photo by shiangling on Unsplash
Photo by shiangling on Unsplash

In this article, I will talk about how to use the @Qualifier and @Named annotation.

This is part of my Dagger 2 blog series:

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!

Bye!
Bye!

Next up: @Scope. Stay tune!

Tan Jun Rong avatar
Written By

Tan Jun Rong

Android Programmer who likes writing blogs, reading, coffee, snowboarding.
Published in Android
Enjoyed the post?

Clap to support the author, help others find it, and make your opinion count.