Hilt For Android: A Beginner’s Guide to Dependency Injection👾

Reduces boilerplate code and ensures it’s tested thoroughly

DuAa AwAn
6 min readJan 13, 2024
Created by Leonardo.ai

You probably have heard of dagger or hilt.

Sometimes you come across code that has weird notations like @Inject @Module or @InstallIn (SingletonComponent::class).

Not sure what it does or how to use it? 😩

Me too. 😁

I was developing my Emoji Tunes app and chose to implement clean architecture. To facilitate communication between modules within the project, I discovered Hilt, which allows for dependency injection.

I began my journey with dependency injection by going through a lot of articles YouTube videos and official Android documentation but I couldn’t make any sense of it.

Initially, I was like…

Encountered numerous compilation and runtime problems. Couldn’t get the code to run, let alone work. Nonetheless, after three consecutive days and exhausting nights, it finally started to function.

So, here I am, sharing my two cents so that the hellish nightmare I went through, you don’t have to.

Why Use Hilt?

Dependency Injection is a method of passing other objects as constructor parameters to a class rather than a class creating its objects of other classes.

In summary, it minimizes repetitive code by separating the creation of dependencies from the class that uses them, making it more modular, less coupled, and easier to manage.

Suppose you have an Android app with multiple modules, and you want to test the communication between these modules. With Hilt, you can easily inject dependencies into your tests and ensure that your app’s modules are communicating correctly.

Here is a UML class diagram that illustrates the concept of dependency injection:

+----------------+          +----------------+
| Client | | Server |
+----------------+ +----------------+
| | | |
| -server: | | |
| IServer |<-------->| |
| | | |
|+Client(IServer)| | +Server() |
+----------------+ +----------------+

In this diagram, the Client class depends on an interface IServer. The Client class takes IServer as a constructor parameter and it won’t have to create its own Serverobject.

Getting Started With Hilt:

To begin, we need to import the library into our project. The complete steps are as follows.

Hilt Library To Your Project

If you don’t already have one, we will add a dependency and plugin for Hilt in build.gradle(:app) as shown below.

plugins {
alias(libs.plugins.hilt)
}
dependencies{
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
}

As shown below, we will add a plugin for Hilt in build.gradle(:Project).

alias(libs.plugins.hilt)

In case you are like me and haven’t yet worked with ksp you would be hit by a bolt of lightning if you try to execute your code.

KSP (Kotlin Symbol Processing) To Your Project

You would need to add it as well like below.

And pray it compiles.🙏

In your libs.version.toml file under plugins and version under versions.

[versions]
ksp = "1.9.21-1.0.16"

[libraries]
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }

[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }

Add a plugin to your build.gradle(:Project) as well as your build.gradle(:app)

plugins {
alias(libs.plugins.ksp) apply false
}

Sample Example:

Ok, we are ready, let’s use hilt.

We will be using hilt for our Emoji Service which communicates with the external emoji data API and fetches all face emojis.

To solve this problem, we will need the following: The structure for the classes & directories in both modules will be as follows:

** Module 1 **
- app/
- src/
- main/
- java/
- com/
- com.emojitunes.moodmusic/
- EmojiTunesApp.kt
- EmojiViewModel.kt

** Module 2 **
- emojis/
- src/
- main/
- java/
- com/
- com.emojitunes.emojis/
- data/
- Emoji.kt
- network/
- EmojiApiClient.kt
- api/
- EmojiApiService.kt
- EmojiModule.kt
- EmojiService.kt
- test/
- java/
- com/
- com.emojitunes.emojis/
- EmojiServiceTest.kt

It is a simple module to begin with I won’t go into details here about the annotations you can read more on the official documentation page here.

Just place it like this.

@Module
@InstallIn(SingletonComponent::class)

On top of a class, object, or interface and here you can bind or provide other objects.

In the case of an interface, it’s @Binds and @Provides for the object.

@Module
@InstallIn(SingletonComponent::class)
internal interface EmojiModule {
@Binds
fun binds(impl: EmojiApiClient): EmojiService
}

Create an Emoji Service interface that we need to bind to the Emoji API Client.

interface EmojiService   {

fun getAllEmojis(group: String): Flow<List<Emoji>>

}

It has the function to get all emojis from the API client.

Now time to create the actual client that will get data from the API as below. We will be injecting it into the Emoji API Client constructor here.

@Singleton
internal class EmojiApiClient @Inject constructor() : EmojiService {

private val networkApi = Retrofit.Builder()
.baseUrl("https://emoji-api.com/")
.addConverterFactory(
GsonConverterFactory.create(),
)
.build()
.create(EmojiApiService::class.java)
private val emojis = mutableMapOf<String, Emoji>()
override fun getAllEmojis(group: String): Flow<List<Emoji>> = flow{
val response = networkApi.searchEmojis(group, API_KEY) // Use the provided group for filtering
val newEmojis = response.associateBy { it.codePoint }
emojis.putAll(newEmojis) // Update the existing map
emit(emojis.values.toList()) // Return the list of all emojis
}



}

Now for the client service, we create the API service interface.

interface EmojiApiService {

@GET("emojis")
suspend fun searchEmojis(
@Query("search") query: String,
@Query("access_key") apiKey: String
): List<Emoji>
@GET("emojis")

}

Let’s not forget the data class for emojis.

data class Emoji(
val slug: String,
val character: String,
val unicodeName: String,
val codePoint: String,
val group: String,
val subGroup: String
)

And now our hilt implementation is complete.

We should write a test to ensure that everything is working smoothly.

Writing Your First Hilt Test:

Start by navigating to androidTest for the instrumentation test, as it will run on an Android emulator or device.

Next, create a custom runner class for hilt to Hilt Test Application we call it EmojiTunesTestRunner class.


// A custom runner to set up the instrumented application class for tests.
class EmojiTunesTestRunner : AndroidJUnitRunner() {

override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}

Inside your build.gradle (:Emoji) file, change the value of testInstrumentationRunner to your custom test runner class. In our case, it is EmojiTunesTestRunner.

    defaultConfig {
minSdk = 26


testInstrumentationRunner = "com.emojitunes.emojis.EmojiTunesTestRunner"
consumerProguardFiles("consumer-rules.pro")
}

Next, create a EmojiServiceTest class with @HiltAndroidTest annotation at the top of the class.

@HiltAndroidTest
class EmojiServiceTest {


}

After that, we add a rule and set up the init() function with the @Before annotation on top for Hilt to do its thing.

@get:Rule
var hiltRule = HiltAndroidRule(this)

@Before
fun init() {
hiltRule.inject()
}

Here we’ll create a test function to fetch emojis from the emoji service by injecting the emoji service.

@Inject
lateinit var emojiService: EmojiService

No need to create any constructor for API Client or emoji service hilt will auto-inject it here.

You can see how less code for Emoji service here.

The final code looks like below.


@HiltAndroidTest
class EmojiServiceTest {
private val _faceEmojiList = MutableStateFlow<List<Emoji>>(emptyList())
private val faceEmojiList: StateFlow<List<Emoji>> = _faceEmojiList
@get:Rule
var hiltRule = HiltAndroidRule(this)

@Inject
lateinit var emojiService: EmojiService

@Before
fun init() {
hiltRule.inject()
}

@Test
fun testConnectionEmojiService() = runBlocking {

val response = emojiService.getAllEmojis("face")

// Asserting that the response is not null
assertNotNull(response)

emojiService.getAllEmojis("face").collect { dataList ->
_faceEmojiList.value = dataList
}

val containsGroupSmileysEmotion = faceEmojiList.value.any { it.group == "smileys-emotion" }
assertTrue("No emoji with group 'smileys-emotion' found",
containsGroupSmileysEmotion
)

}

}

It’s time to execute the test.

Check if it works.

And the test passed successfully.

If you don’t understand the annotations or details you can read more about them on official documentation here. This is just a quick guide and basic setup to get you started with hilt fast.

Feel free to ask any questions.

Thank you for following along with this Android article! If you enjoyed learning about Android development, I have some exciting news to share.

I’ve just launched my new YouTube channel, and my first video is all about getting started with iOS and macOS development using the Swift language in Urdu | Hindi.

If you’re curious about broadening your development skills and diving into the Apple ecosystem, this video is the perfect starting point.

👉 Watch the Video Now

By subscribing to my channel, you’ll get access to:

  • Detailed tutorials and guides.
  • Comparisons and best practices between Android and iOS development.
  • Hands-on projects and coding challenges.

Join me on this exciting journey of cross-platform development. Let’s master Swift together and create amazing apps for iOS and macOS!

🔔 Don’t forget to like, share, and subscribe for more awesome content!

Happy Coding! 🧑‍💻💡

--

--

DuAa AwAn

Software Engineer | Android | Tech Writer. Join me in exploring the endless possibilities of app development!