Dagger Hilt basics
Tags: android, kotlin, dagger, di
Published: 2020-06-23 08:05:10

<span>Photo by <a href="https://unsplash.com/@mreichelt?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Marc Reichelt</a> on <a href="https://unsplash.com/s/photos/kotlin?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>
As you may be aware, Dagger Hilt is the new Dependency Injection library introduced by Google, it's the recommended approach for DI on Android, despite beginning it in Alpha in this small talk I will try to explain some basics about Dagger Hilt.
This article assumes that you are familiar with Architecture components and MVVM.

## What is Dependency Injection?

Dependency Injection is a technique that allows an object to receive other objects that it depends on, the receiving object is called the client and the provided objects are called service. A Client depends on one Service or more. This helps us follow the SOLID's Single responsibility principle.

> Single responsibility principle: means that a class should have only one responsibility and that means it has only one reason to change.

Ok, you might ask how?

Let's take an example:

```kotlin
class Repository {

	private val remoteDataSource = RemoteDataSource()
	private val localDataSource = LocalDataSource()

	fun getUser(id: Int): User {
		var user = localDataSource.getUser(id)
		if (user == null) {
			user = remoteDataSource.getUser(id)
		}
		return user
	}
}
```

As you see above the `Repository` class has 3 responsibilities:

1. Creating `RemoteDataSource` object.
2. Creating `LocalDataSource` object.
3. Retrieving Data from the data sources.

This has many problems:

1. The `Repository` class can change for a variety of reasons.
2.  This makes testing the `Repository` class much harder, and writing tests is hard enough.

Ok, How we are going to fix this? Simply provide the `RemoteDataSource` and `LocalDataSource` in the constructor.

```kotlin
class Repository(
	private val remoteDataSource: RemoteDataSource,
	private val localDataSource: LocalDataSource
) {

	fun getUser(id: Int): User {
		var user = localDataSource.getUser(id)
		if (user == null) {
			user = remoteDataSource.getUser(id)
		}
		return user
	}
}
```

Now as you can see the `Repository` class has one responsibility and it's Retrieving Data from the data sources.

But you will ask how we can create an instance of the `Repository` class?

Let's take a look at our ViewModel

```kotlin
class MainViewModel: ViewModel() {

	private val remoteDataSource = RemoteDataSource()
	private val localDataSource = LocalDataSource()
	private val repository = Repository(remoteDataSource, localDataSource)

	private val _user: MutableLiveData<User> = MutableLiveData()
	val user: LiveData<User> = _user

	fun getUser(id: Int) {
		viewModelScope.launch {
			_user.value = repository.getUser(id)
		}
	}
}
```

And as you might guess the `ViewModel` has 4 responsibilities 😲:

1. Creating `RemoteDataSource` object.
2. Creating `LocalDataSource` object.
3. Creating `Repository` object.
4. Retrieving Data from the repository.

Ok let's fix this

```kotlin
class MainViewModel: ViewModel(private val repository: Repository) {

	private val _user: MutableLiveData<User> = MutableLiveData()
	val user: LiveData<User> = _user

	fun getUser(id: Int) {
		viewModelScope.launch {
			_user.value = repository.getUser(id)
		}
	}
}
```

Better huh. But wait we don't control the creation of a `MainViewModel` instance. `ViewModel` is an Android-specific class like Activity, Fragment and Service, so what is the solution?

Here comes our little friend `Hilt` that will solve our problems, but this isn't the only solution before `Hilt` developers use `ViewModelFactory`, but it introduced a lot of boilerplate code and if you have many ViewModels you will have to do some magic to only use one `ViewModelFactory`.

## Setup

To be able to use Hilt, you need to add some dependencies. Note that at the time of writing this article, the current version is `2.28-alpha`. Check out the [documentation](https://developer.android.com/training/dependency-injection/hilt-android#setup) for the latest version.

- build.gradle

```groovy
dependencies {
		...
		// Current version is 2.28-alpha
        classpath "com.google.dagger:hilt-android-gradle-plugin:2.28-alpha"
}
```

- app/build.gradle:

```groovy
...
apply plugin: "kotlin-kapt"
apply plugin: "dagger.hilt.android.plugin"

android {
    ...
}

dependencies {
		...

		implementation "com.google.dagger:hilt-android:2.28-alpha"
        kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"

		// For injecting ViewModel
        implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01"
		// For injecting WorkManager    
		implementation "androidx.hilt:hilt-work:1.0.0-alpha01"

        kapt "androidx.hilt:hilt-compiler:1.0.0-alpha01"
}
```

## How Hilt works

Hilt works by code generating and it uses different annotations to know how to provide the required dependencies at runtime. let's take a look at some of those annotations:
- @Inject: is used to define how the Client would be constructed.
- @ViewModelInject: is used to define how the `ViewModel` would be constructed.
- @WorkerInject: is used to define how the `Worker` would be constructed.
- @Provides: this is used in a Module class to define how certain dependency is provided (eg: Retrofit, Room database,..)
- @Module: is a class that acts as a bridge between the provided dependency using `@Provides` and the consumer class.

## Hilt example:

Ok let's take an example by and improve our previous solutions, for the sake of simplicity I will remove the data source classes:

```kotlin
/*
	As you can see here I used @Inject annotation.
	and as you probably have guessed we need a database instance.
*/
class Repository @Inject constructor(
	private val database: MyDatabase
) {

	fun getUser(id: Int): User {
		var user = database.getUser(id)
		return user
	}
}
```

Ok, now how we are going to provide a `MyDatabase` instance? since creating a `Room` database instance is more than just a constructor we will use the `@Provides` annotation:

```kotlin
/* 
	As you can see here I used @Module annotation
	Hilt will use this module to call the provideRoomDb function
	And get a MyDatabase instance
	Nb: we used the object keyword to avoid creating an instance of this module
	Will talk about @InstallIn later.
*/
@Module
@InstallIn(ApplicationComponent::class)
object RoomModule {

	@Provides 
	fun provideRoomDb(application: Application): MyDatabase =
        MyDatabase.getInstance(application)
}
```

Now `Hilt` nows how to provide `MyDatabase` instance and use it to create a `Repository` instance. But did you notice something strange? where will Hilt provide an `application` instance to create a `MyDatabase` instance?

Hilt comes with a set of default bindings that can be injected as dependencies, and `Application` is one of those bindings.

## Components:

In the `RoomModule` above I used `@InstallIn(ApplicationComponent::class)`. what the hell is it?

It tells Hilt where to install the Module meaning where it should be available as a dependency, in this example, we want to be able to use `MyDatabase` across the Application so we use the `ApplicationComponent`. 

There are multiple components and using them depends on how you want to scope the provided dependency:

- ApplicationComponent.
- ServiceComponent.
- ActivityRetainedComponent.
- ActivityComponent.
- FragmentComponent.
- ViewComponent.
- ViewWithFragmentComponent

![Dagger Hilt components](https://dagger.dev/hilt/component-hierarchy.svg)

You can take a look at the [documentation](https://dagger.dev/hilt/components) and read more about each component.

## ViewModel:

Now let's learn something new with `MainViewModel`. As you can see here I used `@ViewModelInject` annotation, this will tell Hilt that is a ViewModel and since ViewModels survive configuration changes, Hilt will make sure to return the same instance after configuration changes.

```kotlin
/* 
	As you can see here I used @ViewModelInject annotation
	it will tell Hilt that is a ViewModel class and since 
	ViewModels survive configuration changes, Hilt will make sure to return
	the same instance after configuration changes.
*/
class MainViewModel @ViewModelInject constructor(
		private val repository: Repository
	): ViewModel() {

	private val remoteDataSource = RemoteDataSource()
	private val localDataSource = LocalDataSource()
	private val repository = Repository(remoteDataSource, localDataSource)

	private val _user: MutableLiveData<User> = MutableLiveData()
	val user: LiveData<User> = _user

	fun getUser(id: Int) {
		viewModelScope.launch {
			_user.value = repository.getUser(id)
		}
	}
}
```

and Now let's see some magic. In order to connect the dots we need to create an Application class and annotate it with `@HiltAndroidApp` :

```kotlin
@HiltAndroidApp
class MyApplication : Application() {

  override fun onCreate() {
	 // Nb: Hilt injects the dependencies in super.onCreate() 
     // if you override onCreate make sure you call super.onCreate()
     super.onCreate()
  }
}
```

## Entry Points:

Now after we told `Hilt` how to provide some dependencies, it's time to use those dependencies, for this example I will need `Hilt` to provide me with the `MainViewModel` instance, `@AndroidEntryPoint` will take care of that, which will take care of injecting the required dependencies for our activity. 

```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject lateinit var viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
	// Nb: Hilt injects the dependencies in super.onCreate() 
       // if you override onCreate make sure you call super.onCreate()

        viewModel.doSomething() // will throw a runtime exeption

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        viewModel.doSomething()
    }

}
```

And we are done. `Hilt` is much bigger than just what I coved in this article, but this is the should get you started.

Dependency Injection libraries can be hard to understand, so if you have any question feel free to reach me out on Twitter [@anesabml](http://twitter.com/anesabml) I will happily answer your questions.
Lastly, if this article has helped you learn something new, feel free to share it with others and help them learn as well.

## Resources:

- [Dagger Hilt documentation.](https://dagger.dev/hilt/)