How to Implement Hilt in Android App?
Tags: beginners, android, kotlin, jetpackcompose
Published: 2022-02-25 22:15:40

**Dependency injection library is hard to use, this article provides the simple and easy to follow steps to Implement Hilt in your Android app.**

## Introduction

There are 3 type of dependency injections:

- [Manual dependency injection](https://developer.android.com/training/dependency-injection/manual) - Inject dependency through constructor parameters
- [Service locator](https://developer.android.com/training/dependency-injection#di-alternatives) - Singleton container that holds the dependencies
- **Dependency injection library** - Library/framework such as [Hilt/Dagger](https://developer.android.com/training/dependency-injection/hilt-android) and Koin provide similar functionality as manual dependency injection with less code.

Dependency injection library is hard to use. When I first heard about Hilt/Dagger and Koin, I had no idea what they are about.  So I read their documentations and tutorials, it makes my understanding even worst!

My first experience was Koin in my [Android Kotlin Developer Nonodegree program](https://vtsen.hashnode.dev/android-kotlin-developer-nanodegree-projects-review#heading-course-4-project-build-a-location-reminder-app). The code is already provided, and I used it without 100% understanding it. 

Instead of trying to understand Koin, I came across Hilt which is built on top of Dagger, and it supposes to be a user-friendly. So I gave this a try and I found this useful and simple official tutorial - [Using Hilt in your Android app](https://developer.android.com/codelabs/android-hilt). This tutorial shows you step-by-step to convert service locator into Hilt dependency injection.

So I think it maybe a good idea for me to document the steps to implement Hilt. This article summarizes the steps in this Hilt tutorial.

## 1. Setup Hilt Dependencies

In `build.gradle`(project level), add Hilt Android gradle plugin.
```
buildscript {
    ...
    }
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40'
    }
}

```

In `build.gradle` (app level), add `kotlin-kapt` and `dagger.hilt.android.plugin`.
```
plugins {
    ...
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

```
Add Hilt implementation dependencies.
```
dependencies {
    ...
    implementation 'com.google.dagger:hilt-android:2.40'
    kapt 'com.google.dagger:hilt-android-compiler:2.40'
   ...
}
```

## 2. Add `@HiltAndroidApp` in your application class
If you don't have an application class, you need to create one. 
```kotlin
@HiltAndroidApp
class LogApplication : Application() {
    ...
}
```

Also, you need to update `android:name` in the `AndroidManifest.xml`.
```xml
<manifest>

    <application
        android:name=".LogApplication"
        ...
    </application>
</manifest>
```

## 3. Add `@AndroidEntryPoint` in your activity and fragment
If you want to inject your dependencies in activity, you only need to add  `@AndroidEntryPoint` in your activity class. However, if you want to inject your dependencies in fragment, you need to add `@AndroidEntryPoint` in both fragment and activity that hosts the fragment.
```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    ...
}

@AndroidEntryPoint
class LogsFragment : Fragment() {
    ...
}
```

## 4. Add `@Inject lateinit var` to perform field injection
Use `@Inject lateinit var` on the class field where you want Hilt automatically create the instance for you. In this example, Hilt will automatically create the `logger` instance for you after the fragment is first attached (i.e. after `onAttach()` is called) 

```kotlin
class LogsFragment : Fragment() {
    ...
    @Inject lateinit var dateFormatter: DateFormatter
    @Inject lateinit var logger: LoggerDataSource

    ...
}
```

## 5. Add `@Inject constructor()` to tell Hilt how to provide dependencies
Hilt doesn't know how to create the `DateFomatter` and `LoggerDataSource`. To do that, you need to add `@Inject constructor()` into the classes.
```kotlin
//Hilt knows how DataFormatter can be constructor injected
class DateFormatter @Inject constructor() {
    ...
}

//Hilt still doesn't know how LoggerLocalDataSource can be constructor injected 
class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao) {
    ...
}
```
Due to missing information on `LogDao`, Hilt still doesn't know how to constructor inject into `LoggerLocalDataSource`. Thus, we need to **create the hilt module - step 7** below to tell how `LogDao` can be created.

## 6 Add `@Singleton` to scope instance to entire application

```kotlin
@Singleton
class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao) : LoggerDataSource {
    ...
}
```
This means the same `LoggerLocalDataSource ` will be used for the entire application. There are different [component scopes](https://developer.android.com/training/dependency-injection/hilt-android#component-scopes) such as `@ActivityScoped`, `FragmentScoped` and `@ViewModelScoped`. 

## 7. Add `@Module` and `@InstallIn` to create Hilt Modules
For those class that Hilt doesn't know how to constructor inject the dependencies, you need to create the Hilt Module.

```kotlin
@InstallIn(SingletonComponent::class)
@Module
object DatabaseModule {
    ...
}
```
`@InstallIn` specify which Hilt component this module should be installed into which is related to the component scopes.

## 8. Inject instances with `@Provides`

There are 2 `@Provides`. The first `@Provides` provides the information how `LogDao` can be created. Because it depends on `AppDatabase`, the second `@Provides` provides information how `AppDatabase` can be created.
```kotlin
@InstallIn(SingletonComponent::class)
@Module
object DatabaseModule {

    @Provides
    fun provideLogDao(database: AppDatabase): LogDao {
        return database.logDao()
    }

    @Provides
    fun provideDatabase(@ApplicationContext appContext: Context): AppDatabase {
        return Room.databaseBuilder(
            appContext,
            AppDatabase::class.java,
            "logging.db"
        ).build()
    }
}
```
Please note the `@ApplicationContext` is the pre-defined binding which means Hilt will automatically get the `ApplicationContext` for you.

## 9. Inject interface instances with `@Binds`

If the injected field is an interface, you need to tell Hilt how to provide the implementation of the interface using `@Binds`. 

```kotlin
@InstallIn(ActivityComponent::class)
@Module
abstract class NavigationModule {

    @Binds
    abstract fun bindNavigator(impl: AppNavigatorImpl): AppNavigator
}
```
Please note that the abstract function and class are needed with `@Binds`.

## 10. Add `@Qualifier` for two implementations that have the same interface
If your field injection is an interface, and you have more than one implementation, you need to use `@Qualifier` to tell Hilt how to differentiate them.

![Simple_Steps_to_Implement_Hilt_in_Android_App_01.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1641286843034/fh3tQ4j0g.png)

For example, you need to add `@Qualifier` for `LoggerLocalDataSource` and `LoggerInMemoryDataSource.`

Define the `@Qualifier` class in the Hilt modules that binds the `LoggerLocalDataSource` and `LoggerInMemoryDataSource.`
```kotlin
@Qualifier
annotation class InMemoryLogger

@Qualifier
annotation class DatabaseLogger
```

Add `@DataBaseLogger` and `@InMemoryLogger` qualifiers above the `Binds`.
```kotlin
@InstallIn(SingletonComponent::class)
@Module
abstract class LoggingDatabaseModule {

    @DatabaseLogger
    @Binds
    abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}

@InstallIn(ActivityComponent::class)
@Module
abstract class LoggingInMemoryModule {

    @InMemoryLogger
    @Binds
    abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}
```

Add a qualifier above the field injection variable to indicate which implementation to use. For example, `@DatabaseLogger` indicates `LoggerLocalDataSource` implementation will be used.
```kotlin
@DatabaseLogger
@Inject lateinit var logger: LoggerDataSource
```

## Conclusion
Honestly, I'm not a fan of dependency injection library (for now). It makes the code harder to understand. If there is a compilation error, it is harder to fix. At least for me, it is hard. For now, it doesn't improve my productivity, but the other way round. 

On the other hand, service locator technically is not dependency injection because it doesn't inject the dependencies. It allows the consumers to retrieve the dependencies instead from everywhere. It is basically a global object.

Given all these reasons, I still prefer to use manual or constructor dependency injection. It is simple, clear, and easy to debug. Anyone can understand your code. Isn't this all clean code about?

I know I say this because I haven't mastered dependency injection. I hope one day I will.

---

*Originally published at [https://vtsen.hashnode.dev](https://vtsen.hashnode.dev/how-to-implement-hilt-in-android-app)*.