Dagger 2 for Dummies in Kotlin — Qualifier

Elye
Elye
Feb 17, 2018 · 5 min read

In this blog, I will introduce Mr. @Qualifier to you. Unlike the four super VIP, @Component, @Inject, @Provides and @Module that was introduced to you in my previous blog, Mr. @Qualifier is a low profile guy and works behind the scene… a humble person.

Anyway, before proceeding, if you miss my previous blogs, you could check out at.

Still dumb… can it be smarter?

If you read my previous blog, you might remember the following code.

class MainActivity : AppCompatActivity() {

@Inject lateinit var info: Info

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerMagicBox.create().poke(this)
text_view.text = info.text
}
}

@Module
class Bag {
@Provides
fun sayLoveDagger2(): Info {
return Info("Love Dagger 2")
}
}

class Info(val text: String)

@Component(modules = [Bag::class])
interface MagicBox {
fun poke(app: MainActivity)
}

In this code, we shows that we can now inject Info(“Love Dagger 2”). But this looks dumb, if we could not have another Info with different argument provided. Then there’s not much reason making Info having a parameter constructor.

What if we would like to retain both Info("Love Dagger 2") and Info("Hello Dagger 2")? Can we just do below?

    @Provides
fun sayLoveDagger2(): Info {
return Info("Love Dagger 2")
}
@Provides
fun sayHelloDagger2(): Info {
return Info("Hello Dagger 2")
}

Sorry, we can’t. As mentioned the name of the function is just for Dagger 2 to internally hook them up. It uses the return type i.e. Info to determine which to link to the needing Member Variable.

Having one @Inject lateinit var info: Info would confuse Dagger 2 on which one to bind to. So how?

Mr. @Qualifier, the behind scene hero

To resolve the confusion Dagger 2 has, we have something call a @Qualifier. He could appoint an arbiter to decide when to use what. Very powerful yeah…

The key here is, appoint. He doesn’t do it himself. A behind scene guy… low profile. Not needed until we have such conflict.

So who will he appoint? It is ANYONE I like him to appoint. POWER!! 💪💪💪

Well, to decide I need to do some work. Maybe better see if there is any already readily available?

YES, There is, and he is @Named.

Let’s investigate him a little before letting him do the job. Looks like he is defined in Java.

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
/** The name. */
String value() default "";
}

As you could see, Mr. @Named is given power by @Qualifier to perform as arbiter. It is taking it a String to make the decision.

As we see just now, @Named allow me to provide a String, that I could use to differentiate the different Info I have. Let me show you as below.

@Module
open class Bag {
@Provides @Named("Love")
fun sayLoveDagger2(): Info {
return Info("Love Dagger 2")
}
@Provides @Named("Hello")
fun sayHelloDagger2(): Info {
return Info("Hello Dagger 2")
}
}

Cool, each is given a String to differentiate the Provider.

And on the declaration side, we used to be just

@Inject lateinit var info: Info

Let change it to two member variable as well, where one is storing LOVE and the other one HELLO

@Inject @field:Named("Love") lateinit var infoLove: Info
@Inject @field:Named("Hello") lateinit var infoHello: Info

@field: is a Kotlin way of adding a field annotation when using it.

Of course, lastly, we should change the info usage side by changing

text_view.text = info.text

to

text_view.text = "${infoLove.text} ${infoHello.text}"

Cool. Now the code below

const val LOVE = "Love"
const val HELLO = "Hello"

class MainActivity : AppCompatActivity() {

@Inject @field:Named(LOVE) lateinit var infoLove: Info
@Inject @field:Named(HELLO) lateinit var infoHello: Info

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerMagicBox.create().poke(this)
text_view.text = "${infoLove.text} ${infoHello.text}"
}
}

@Module
class Bag {
@Provides @Named(LOVE)
fun sayLoveDagger2(): Info {
return Info("Love Dagger 2")
}
@Provides @Named(HELLO)
fun sayHelloDagger2(): Info {
return Info("Hello Dagger 2")
}
}

class Info(val text: String)

@Component(modules = [Bag::class])
interface MagicBox {
fun poke(app: MainActivity)
}

Compile, and all works … 👏👏👏. You have your LOVE and HELLO.

Ops, sorry I do a little change the code to use two Constant Variable i.e. LOVE and HELLO. They are just good programming practice to ensure same String are used both side, without accidental having typo.

What about my power to choose?

I don’t really like the name of @Named. I want it to have some other name. As was mentioned earlier, @Qualifier will grant me the power to decide who to use.

I like to use @Choose who didn’t exist yet.

The way to do it is very easy, just replicate how @Named is designed, and create a @Choose instead. But this round let’s use Kotlin instead of Java.

@Qualifier
@MustBeDocumented
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class Choose(val value: String = "")

This is really the same code as how @Named was defined (in Java), it is just looks different as it is in Kotlin.

Now we could get rid of Mr. @Named as we don’t like the name, and replace with @Choose as below.

const val LOVE = "Love"
const val HELLO = "Hello"

class MainActivity : AppCompatActivity() {

@Inject @field:Choose(LOVE) lateinit var infoLove: Info
@Inject @field:Choose(HELLO) lateinit var infoHello: Info

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerMagicBox.create().poke(this)
text_view.text = "${infoLove.text} ${infoHello.text}"
}
}

@Module
open class Bag {
@Provides @Choose(LOVE)
fun sayLoveDagger2(): Info {
return Info("I Love You")
}
@Provides @Choose(HELLO)
fun sayHelloDagger2(): Info {
return Info("Hello Dagger 2")
}
}

class Info(val text: String)

@Component(modules = [Bag::class])
interface MagicBox {
fun poke(app: MainActivity)
}

@Qualifier
@MustBeDocumented
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class Choose(val value: String = "")

Compile… and it shall work as you wish.

You could get the code from below

TL;DR

@Qualifier in itself is not used directly. But it’s is used to generate annotation that could help differentiate the same type of class object with different argument sent to it’s constructor.

It is a relatively low profile annotation, and only useful if you have same constructor. I plan to introduce to you another low profile annotation, but much more prominently used in the coming blog…

But before that, I plan to revisit @Module from the point of how it is use for testing purpose. I have the blog link below. Check it out.


I hope you appreciate this post and it’s helpful for you. Do share with others.

You could check out my other interesting topics here.

Follow me on medium, Twitter or Facebook for little tips and learning on Android, Kotlin etc related topics. ~Elye~


Elye

Written by

Elye

Learning and Sharing Android and iOS Development

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade