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)
}