Kahibaro
Discord Login Register

23.2 Hilt Basics

Why Use Hilt in Android Apps

Hilt is a dependency injection library built on top of Dagger, designed specifically for Android. It removes a lot of boilerplate and gives you prebuilt integrations with common Android classes such as Activity, Fragment, ViewModel, and WorkManager.

Hilt helps you automatically create and provide objects that your app needs, such as repositories, network clients, or database instances. Instead of manually constructing these objects and passing them around, you declare how to build them, and Hilt does the wiring for you.

Hilt is opinionated. It enforces a clear structure for where dependencies live and how long they should exist. This structure matches the Android component lifecycles, which reduces bugs related to memory leaks and incorrect object reuse.

Hilt manages object creation and lifetimes for you. You declare what to provide and where it can be injected. Hilt is responsible for the actual construction and sharing of these objects.

Adding Hilt to an Android Project

To use Hilt, you must configure Gradle and apply Hilt plugins and dependencies. This is a one time setup per project, and after that you can use Hilt throughout your code.

Open the top level build.gradle or settings.gradle.kts file and make sure you use the Hilt Gradle plugin dependency in the buildscript or plugins section. For a Kotlin based project, you usually apply the plugin in the app module.

In your app module build.gradle or build.gradle.kts, add the Hilt plugin and Hilt dependencies. A typical Kotlin DSL setup looks like this:

plugins {
    id("com.android.application")
    kotlin("android")
    kotlin("kapt")
    id("com.google.dagger.hilt.android")
}
dependencies {
    implementation("com.google.dagger:hilt-android:2.52")
    kapt("com.google.dagger:hilt-android-compiler:2.52")
}

After updating Gradle files, you must sync the project. Hilt uses annotation processing, so the kapt plugin and the hilt-android-compiler are required to generate the necessary code.

The Hilt Application Class

Hilt needs a starting point in your app to create and keep the dependency container. This starting point is an Application class annotated with @HiltAndroidApp.

If your project does not already have a custom Application class, create one. Then, annotate it with @HiltAndroidApp and register it in the AndroidManifest.xml.

Example Application class:

@HiltAndroidApp
class MyApp : Application()

In the manifest, ensure that this class is set as the application name:

<application
    android:name=".MyApp"
    ... >
    ...
</application>

By doing this, Hilt initializes at app startup, creates the root dependency container, and attaches it to the application lifecycle.

Always annotate a single Application class with @HiltAndroidApp. This annotation is required for Hilt to function in your app.

Hilt Annotations Overview

Hilt uses annotations to define what can receive dependencies, what can provide them, and how long they should live.

There are three main groups of Hilt annotations that you use frequently.

First, entry point annotations identify Android classes that can receive injected dependencies. Examples are @AndroidEntryPoint for Activity, Fragment, Service, and others, and @HiltViewModel for view models.

Second, binding annotations tell Hilt how to create a type. The most common are @Inject on constructors and @Provides functions in modules. These annotations describe how an instance of a class should be built when requested.

Third, scope annotations such as @Singleton specify the lifetime of a dependency. Scoped objects are reused for as long as their container exists. For example, a @Singleton object lives as long as the app process and is shared everywhere it is injected.

Injecting Dependencies into Android Components

To let Hilt inject dependencies into an Android class, you annotate that class with @AndroidEntryPoint. Hilt then generates code behind the scenes and wires the dependencies into the component when the system creates it.

For an Activity, you might write:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var analytics: AnalyticsService
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        analytics.trackScreen("Main")
    }
}

The @AndroidEntryPoint annotation tells Hilt that this Activity requires injection support. The @Inject annotation on the property asks Hilt to provide an instance of AnalyticsService.

You can use @AndroidEntryPoint on Activity, Fragment, View, Service, and certain other Android classes that Hilt supports. Every component that needs injection must be annotated directly. If a fragment is annotated with @AndroidEntryPoint, its hosting activity must also be an entry point, or the app will crash at runtime.

Any Android class that wants field injection from Hilt must be annotated with @AndroidEntryPoint. For fragments, the parent activity must also be an entry point.

Constructor Injection with `@Inject`

Hilt prefers constructor injection. This means you add @Inject to a class constructor so that Hilt knows how to build that class.

For example, consider a simple repository class:

class UserRepository @Inject constructor(
    private val api: UserApi,
    private val dao: UserDao
) {
    fun getUser(id: String) { ... }
}

Here, UserRepository declares @Inject on its primary constructor. Hilt can now create instances of UserRepository whenever another class asks for it.

If all constructor parameters can also be provided (either by their own @Inject constructors or by modules), then Hilt can resolve the whole dependency chain.

Constructor injection is usually the most straightforward and maintainable way to define dependencies in Hilt.

Hilt Modules and `@InstallIn`

Not all classes can be annotated with @Inject on the constructor. For example, third party classes or interfaces require a different approach. In these cases, you use a Hilt module.

A Hilt module is a class annotated with @Module and @InstallIn, which contains functions annotated with @Provides. Each @Provides function tells Hilt how to create a specific type.

Here is a basic example of a module that provides a Retrofit instance and an API interface:

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://example.com")
            .build()
    }
    @Provides
    fun provideUserApi(retrofit: Retrofit): UserApi {
        return retrofit.create(UserApi::class.java)
    }
}

The @InstallIn(SingletonComponent::class) annotation defines where the module is installed and how long the provided dependencies can live. The SingletonComponent is tied to the application lifecycle, so @Singleton scoped dependencies from this module are shared across the entire app.

Modules can be object singletons or regular classes. For simple provider functions, declaring them inside an object is common because it avoids the need to instantiate the module.

Use @Module with @InstallIn and @Provides when you cannot or should not use @Inject on a constructor, for example interfaces or third party classes.

Scopes and Components in Hilt

Hilt links scopes to components. A component in Hilt represents a container for dependencies with a specific lifetime.

For beginners, the most commonly used component is SingletonComponent. Objects scoped with @Singleton and installed in this component have a lifetime equal to the application. They are created once and reused everywhere.

Other components exist that correspond to different Android lifecycles, such as ActivityComponent, FragmentComponent, and ViewModelComponent. You install modules into these components when you want dependencies that live only as long as those Android components.

In practice, you decide which scope to use based on how long you want the dependency to exist. For example, a Retrofit instance is usually @Singleton, while a presenter or controller that only makes sense for a single screen might be scoped to ActivityComponent or FragmentComponent.

If you do not specify a scope, each injection will receive a new instance, as long as Hilt can construct it.

Injecting into ViewModels with Hilt

Hilt integrates tightly with ViewModel. To create a Hilt managed view model, you annotate the class with @HiltViewModel and use @Inject on its constructor.

Example view model:

@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {
    fun loadUser(id: String) { ... }
}

In an Activity or Fragment that has @AndroidEntryPoint, you obtain this view model using standard Jetpack ViewModel helpers:

@AndroidEntryPoint
class MainFragment : Fragment(R.layout.fragment_main) {
    private val viewModel: MainViewModel by viewModels()
    ...
}

Hilt connects the ViewModel to the ViewModelComponent behind the scenes. Dependencies like UserRepository are resolved from the components where they are installed.

Common Pitfalls and Practical Tips

Hilt requires strict setup. There are a few frequent issues that you should be careful about.

First, always create a single Application class annotated with @HiltAndroidApp and register it in the manifest. Without this, nothing else will work.

Second, annotate every Android component that needs injected fields with @AndroidEntryPoint. If a child component such as a fragment uses @AndroidEntryPoint, the parent activity must also be annotated.

Third, remember that Hilt uses generated code. Any time you change annotations like @Module, @Inject, or @HiltAndroidApp, make sure the project compiles so that Hilt can generate updated classes. If you see mysterious compile time errors, try a clean build.

Finally, keep simple classes with constructor injection and use modules only when necessary. This keeps your dependency graph much easier to read and maintain.

If an Android component uses injected fields but is missing @AndroidEntryPoint, your app will compile but will crash at runtime when that component is created.

Views: 1

Comments

Please login to add a comment.

Don't have an account? Register now!