Getting Started with Dagger-Hilt Dependency Injection

Getting Started with Dagger-Hilt Dependency Injection

Hilt provides a more simplified and standard way of using dependency injection in android applications by providing containers for every android class and also managing their lifecycles. Built on top of dagger , Hilt aims for simplicity, standard set of components and scope to ease setup , readability and code sharing between apps and provide an easy way to provision different bindings to various build types like testing, debugging and release as highlighted in the documentation.

Setup

The hilt-android-gradle-plugin is required in the project's level build.gradle file

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}

In the app's level build.gradle file apply the plugin and the dependency

...
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"
}

The reason for applying this plugin is to make it easy to use the @AndroidEntryPoint and @HiltAndroidApp annotations easier. The plugin can be left out but the base class has to be specified in the annotation and also must extend the generated class, which will only result to boilerplate code.

Since Hilt uses Java 8 features, it must be enabled in the project.

android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

The following dependency makes it easy to perform dependency injection in viewmodels

// Activity KTX for viewModels()
    implementation "androidx.activity:activity-ktx:1.1.0"

Application Class

Apps that use Hilt must contain an application class annotated with @HiltAndroidApp which triggers Hilt's code generation.The Java files generated are responsible for handling the dependency injection.The annotation basically informs the app that we are going to use Hilt for dependency injection.

@HiltAndroidApp
class BaseApplication:Application() {
    override fun onCreate() {
        super.onCreate()
        Timber.plant(Timber.DebugTree())
    }
}

Injecting Dependencies to classes (RoomDatabase)

The first step is to create the app database. Dagger Hilt will handle the singleton functionalities of RoomDatabase so I don't have to do the implementation in this class

@Database(
    entities = [Task::class],
    version = 1
)
@TypeConverters(Converters::class)
abstract class AppDatabase :RoomDatabase(){
    abstract fun getDao():TaskDao
}

In order to inject the AppDatabase a module is necessary in order to let Hilt know how to create the AppDatabase so that it is injected in a repository.Hilt modules are standard dagger modules

@Module
@InstallIn(ApplicationComponent::class)
object AppModule {
    @Singleton
    @Provides
    fun provideAppDatabase(@ApplicationContext app:Context) = Room.databaseBuilder(
        app,
        AppDatabase::class.java,
        APP_DATABASE_NAME
    ).build()
    @Singleton
    @Provides
    fun provideTaskDao(db:AppDatabase) = db.getDao()
}

The @Module informs Hilt how to provide instances of certain types . The @InstallIn(ApplicationComponent::class) means that the module will be installed in the ApplicatonComponent class and will exist whole lifetime of the app. Installing a module into a component allows that binding to be accessed as dependency of other bindings in that component or any child components below it in the component hierarchy .The fun provideAppDatabase(@ApplicationContext app:Context) annotated with @Provides means that the result of the function can be injected into other classes and also create other dependencies. @Singleton ensures we only have one instance of the AppDatabase in the entire app.

Now inorder to inject the taskDao in our repository we annotate the repository with @Inject constructor () and pass taskDao as parameter

class MainRepository @Inject constructor(
    val runDao: RunDao
) {
    suspend fun insertTask(task: Task) = taskdao.insertTask(task)
    suspend fun deleteRun(task: Task) = taskdao.deleteRun(task)
}

References