Hilt — The official new DI Library for Android (Part-2)

PRANAY PATEL
Simform Engineering
4 min readAug 2, 2021

I hope you have learned the basic setup of Hilt in Part-1, if you haven’t read it, then first read that blog so you are aware of the primary details regarding this part. We have learned how we can inject dependencies and create a module in the app, in the previous part. Now in this part, we will learn what is “Qualifiers” and how we can use them.

Using Qualifiers:

A qualifier is an annotation used to identify a binding.

Step 1: create a new file LoggerDataSource.kt with an interface that has common database methods:

interface LoggerDataSource {
fun addLog(message: String)
fun getAllLogs(callback:(List<Log>)->Unit)
fun removeLogs()
}

Step 2: Open LogsFragment and make the logger variable of type LoggerDataSource:

@AndroidEntryPoint
class LogsFragment : Fragment() {
@Inject lateinit var logger: LoggerDataSource
...
}

Step 3: Do same for ButtonsFragment:

@AndroidEntryPoint
class ButtonsFragment : Fragment() {
@Inject lateinit var logger: LoggerDataSource
...
}

Step 4: As we have created common LoggerDataSource interface now make LoggerLocalDataSource implement LoggerDataSource and override required methods.

@Singleton
class LoggerLocalDataSource @Inject constructor(
private val logDao: LogDao
) : LoggerDataSource {
...
override fun addLog(msg: String) { ... }
override fun getAllLogs(callback: (List<Log>) -> Unit) { ... }
override fun removeLogs() { ... }
}

Step 5: To manage in-memory data source, create new LoggerInMemoryDataSource under data package and implement the same LoggerDataSource interface:

  • As we need the scope of this under-activity scope, we need to annotate the type with @ActivityScoped:
@ActivityScoped
class LoggerInMemoryDataSource @Inject constructor(
) : LoggerDataSource {
private val logs = LinkedList<Log>() override fun addLog(msg: String) {
logs.addFirst(Log(msg, System.currentTimeMillis()))
}
override fun getAllLogs(callback: (List<Log>) -> Unit) {
callback(logs)
}
override fun removeLogs() {
logs.clear()
}
}

Step 6: How to create two different implementations for the same interface( here: LoggerDataSource )? To do so we can create bindings for both modules in the same file. So now create a new file LoggingModule.ktunder di package.

package com.example.android.hilt.di@InstallIn(ApplicationComponent::class)
@Module
abstract class LoggingDatabaseModule {
@Singleton
@Binds
abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}
@InstallIn(ActivityComponent::class)
@Module
abstract class LoggingInMemoryModule {
@ActivityScoped
@Binds
abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}

⚠️ In this stage, while building a project you will get the DuplicateBindings error! why it is?

Reason: Because we have the LoggerDataSource injected in both fragments but Hilt doesn’t know which implementation to use because there are two bindings of the same type! Here qualifier comes into the role!

What is the use of a qualifier?: It is used to tell Hilt to provide different implementations (multiple bindings) of the same type. e.g LoggerDataSource We consider the qualifier is just an annotation! Let's see how we can use it.

Let’s create two qualifiers in the LoggingModule.kt file:

@Qualifier
annotation class InMemoryLogger
@Qualifier
annotation class DatabaseLogger

Now, these qualifiers must annotate the @Binds functions that provide each implementation. So the full code of LoggingModule.kt will be:

@Qualifier
annotation class InMemoryLogger
@Qualifier
annotation class DatabaseLogger
@InstallIn(ApplicationComponent::class)
@Module
abstract class LoggingDatabaseModule {
@DatabaseLogger
@Singleton
@Binds
abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}
@InstallIn(ActivityComponent::class)
@Module
abstract class LoggingInMemoryModule {
@InMemoryLogger
@ActivityScoped
@Binds
abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}

Next, we have to use the same qualifier while injecting the class. In our case, if we use LoggerInMemoryDataSource in our both fragment, we need to add qualifier with @Inject :

LogsFragment.kt :

@AndroidEntryPoint
class LogsFragment : Fragment() {
@InMemoryLogger
@Inject lateinit var logger: LoggerDataSource
...
}

ButtonsFragment.kt :

@AndroidEntryPoint
class ButtonsFragment : Fragment() {
@InMemoryLogger
@Inject lateinit var logger: LoggerDataSource
...
}

If you want to change the database implementation(see bindDatabaseLogger) you want to use, you just need to annotate the injected fields with @DatabaseLogger instead of @InMemoryLogger.

So now you can run the app and check all logs.

Wrap Up!

That was pretty cool, right? This is all the stuff you need to know about Hilt integration for your Android app.

Summary:

Let’s just take a look at the summary of what we have learned:

  • Intro about Hilt and how to setup Hilt libraries
  • How to set up different android classes with hilt including Application, Fragment and Activity using different annotations.
  • How to setup modules for different types
  • How to use qualifier to identify the same multiple bindings

Here is the Github repository link of the app:

Resources:

🙏 Thanks for reading this article. Be sure to clap/recommend as much as you can and also share with your friends. It means a lot to me. Also, let’s become friends on Twitter, and Github.

--

--

PRANAY PATEL
Simform Engineering

Senior Software Engineer - Android @mutualmobile | Flutter | Android | KMP | Lazy Investor