Hilt — The official new DI Library for Android (Part-2)
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.kt
under 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
andActivity
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:
- https://developer.android.com/training/dependency-injection/hilt-android
- https://medium.com/androiddevelopers/hilt-and-dagger-annotations-cheat-sheet-9adea070e495
- https://developer.android.com/codelabs/android-hilt#0
🙏 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.