Android dagger hilt basic set up

Hilt is a dependency injection library for Android that reduces the boilerplate of doing manual dependency injection in your project. Hilt provides a standard way to use DI in your application by providing containers for every Android class in your project and managing their lifecycles automatically. Hilt is built on top of the popular DI library Dagger to benefit from the compile-time correctness, runtime performance, scalability, and Android Studio support that Dagger provides. This post lays out the basic set up for using Hilt, including dependency libraries, dependency module, injecting dependency into activities, fragments and view models.

First, add the hilt-android-gradle-plugin plugin to your project’s root build.gradle file:

buildscript {
    ...
    dependencies {
        ...
        classpath("com.google.dagger:hilt-android-gradle-plugin:2.38.1")
    }
}

Then, apply the Gradle plugin and add these dependencies in your app/build.gradle file:

plugins {
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

android {
    ...
}

dependencies {
    implementation "com.google.dagger:hilt-android:2.38.1"
    kapt("com.google.dagger:hilt-android-compiler:2.38.1")
}

// Allow references to generated code
kapt {
 correctErrorTypes = true
}

All apps that use Hilt must contain an Application class that is annotated with @HiltAndroidApp.

@HiltAndroidApp triggers Hilt’s code generation, including a base class for your application that serves as the application-level dependency container.

@HiltAndroidApp
class ExampleApplication : Application() { ... }

In order for hilt to inject dependencies into activities and fragments, the activity and fragment must include the annotation @AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity() { 
  @Inject lateinit var analytics: AnalyticsAdapter

  ... 
}

@AndroidEntryPoint
class SlideshowFragment : Fragment() { 
  @Inject lateinit var analytics: AnalyticsAdapter

  ... 
}

In order to inject any dependency into activities, the dependency must be defined in a module and installed in ActivityComponent. This ties the lifecycle of the dependency to the lifecycle of the activity.

@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    // Potential dependencies of this type
    @ApplicationContext context: Context
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

In order to inject any dependency into fragments, the dependency must be defined in a module and installed in ActivityFragment or FragmentComponent. If it is installed in ActivityFragment, the dependencies can be injected into the activities and fragments. If it is installed in FragmentComponent, dependencies can be injected into Fragments only, and it will be tied to the fragment lifecycle.

@Module
@InstallIn(FragmentComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    // Potential dependencies of this type
    @ApplicationContext context: Context
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

In order for hilt to inject dependencies into view models, the view model has to be annotated with @HiltViewModel, and the constructor must be annotated with @Inject

@HiltViewModel
class SlideshowViewModel @Inject constructor(
    private val repository: TasksRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() { ... }

To inject a repository into a view model, the repository must be defined in a module and installed in ViewModelComponent scope, it ties the lifecycle of the repository with the view model lifecycle.

import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent

@Module
@InstallIn(ViewModelComponent::class)
interface RepositoryModules {
    @Binds
    fun provideTasksRepository(repository: DefaultTasksRepository): TasksRepository
}

In order to bind the repository using the @Binds annotation, and if the repository require other dependencies. The other dependencies must be defined in a module and installed in ViewModelComponent.

@InstallIn(ViewModelComponent::class)
@Module
object TaskModule {
    @Provides
    fun provideTasksLocalDataSource(@ApplicationContext context: Context): TasksDataSource {
        val database = createDataBase(context)
        return TasksLocalDataSource(database.taskDao())
    }

    @Provides
    fun provideCoroutineDispatcher(): CoroutineDispatcher {
        return Dispatchers.IO
    }
}

@VisibleForTesting
fun createDataBase(
    context: Context,
    inMemory: Boolean = false
): NotebookDatabase {
    val result = if (inMemory) {
        // Use a faster in-memory database for tests
        Room.inMemoryDatabaseBuilder(context.applicationContext, NotebookDatabase::class.java)
            .allowMainThreadQueries()
            .build()
    } else {
        // Real database using SQLite
        Room.databaseBuilder(
            context.applicationContext,
            NotebookDatabase::class.java, "Tasks.db"
        ).build()
    }

    return result
}

The dependencies defined in the module for instantiating the repository, they need to be annotated with @Inject in the Repository’s constructor.

class DefaultTasksRepository @Inject constructor(
    private val tasksRemoteDataSource: TasksDataSource,
    private val tasksLocalDataSource: TasksDataSource,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository { ... }

Search within Codexpedia

Custom Search

Search the entire web

Custom Search