If you're involved in Android app development, you've probably encountered the need to better organize your classes to prevent the project from descending into complete chaos. This is where the... dependency injection, a design pattern that, although it may seem like black magic at first, is basically the way to give each class the objects it needs without the class having to manufacture them itself.
Hilt arrives to rescue us from the complexity of Dagger, offering a top layer that standardizes dependency insertionBy integrating with Jetpack, Hilt automatically manages containers and lifecycles, allowing us to focus on business logic rather than moving objects around like we're moving house.
Initial Environment Configuration
To start using Hilt, the first thing is to prepare the terrain in Gradle. It is essential to add the hilt-android-gradle-plugin in the project-level file. Then, in the application module file, we must apply the plugin and add the corresponding dependencies, making sure that the compiler (either using kapt or KSP) is correctly configured.
One detail we cannot overlook is that, for everything to run smoothly, the project must be configured to use Java 17 (or Java 8 in older versions), as it is an essential requirement for Hilt and Jet Pack Compose work in harmony. If you notice that the program doesn't compile when you activate the plugin, check that the Kotlin and KSP versions match exactly, as even a small discrepancy can break the entire compilation process.
The Heart of Hilt: The Application Class
Any app that wants to benefit from this library must have a custom Application class. It's not enough to simply create it; we must also mark it with the annotation. @HiltAndroidAppThis line triggers Hilt's code generation and creates the application's root container.
This top component is vital because it is linked to the overall lifecycle of the app and serves as the foundation for all other components They can access the dependencies defined at this level. Of course, don't forget to register this class in the AndroidManifest.xml file using the name attribute, or Hilt will go completely unnoticed.
Injecting Dependencies into Android Components
Once the application is ready, we can begin injecting dependencies into activities, fragments, views, or services. To achieve this, we use the annotation @AndroidEntryPointThis tells Hilt that this class is an entry point and that it should generate a specific component for it.
When we want to obtain a specific dependency within an activity, we use the field injection using the @Inject annotation. It is very important to remember that These fields cannot be private.If you try to use the `private` modifier, the compiler will throw an error without hesitation. In the case of Jetpack Compose, simply annotating the root Activity allows you to access the ViewModels directly within the composable functions.
Strategies for Defining Linkages
Hilt needs to know how to manufacture the objects we request. The most direct way is... builder injectionwhere we put @Inject right before the class constructor. This way, Hilt analyzes the parameters and finds how to provide each of those dependencies.
But of course, there are cases where we don't have complete control over the class, such as when we use external libraries like Retrofit or OkHttpClient. For this, we have... Hilt ModulesA module is a class annotated with @Module and must include @InstallIn to indicate which component should have that dependency.
- Use of @Provides: This is used when the class is third-party or requires a complex construction pattern. Here we define a function that returns the necessary instance.
- Using @Binds: This is the ideal option when we want to link an interface to a concrete implementation. It is defined as an abstract function that tells Hilt: "when someone requests this interface, give them this implementation."
Scope and Life Cycle Management
By default, Hilt creates a new instance each time a dependency is requested. However, sometimes we need an object to remain the same throughout the lifetime of the app or a screen. For that, we use... scope of the components.
If we mark a class with @Singleton And if we install it in the SingletonComponent, we'll have a single global instance. If we prefer that the object only exists for the duration of the activity, we use @ActivityScoped within the ActivityComponent. It is crucial not to overuse scope, as keeping objects in memory for too long can ultimately affect application performance.
Special Cases and Qualifiers
Sometimes we need to inject two different implementations of the same interface. To avoid overwhelming the compiler, we use qualifiersA qualifier is basically a custom annotation used to label a specific binding, allowing us to differentiate, for example, between an HTTP client for authentication and one for general requests.
Furthermore, Hilt makes our lives easier with predefined qualifiers such as @ApplicationContext and @ActivityContext, which allow us to obtain the Android context without having to configure manual modules for it. Finally, for those classes that Hilt does not natively support (such as ContentProviders), we can use the annotation @EntryPoint, creating a manual bridge to extract dependencies from the Hilt graph. Share the information and more users will learn about the topic.