Hilt — A Standard Way to Implement Dependency Injection in Android

Hilt — A Standard Way to Implement Dependency Injection in Android

·

4 min read

Why using Hilt?

When it comes to Dependencies Injection in Android development, we don’t have many choices:

  • Build the DI from scratch manually. Personally, this is not my f̶i̶r̶s̶t̶ choice🤷‍♂️. But if you want to know how to build it, check it out here.

  • Use a 3rd party library like Dagger or Koin

Despite Dagger's power of auto-generating code for DI, the learning curve is still a big problem for new learners.

Understand that pain point; Hilt was built on top of Dagger to simplify the configuration (which is quite much and complex) and let the developers focus on declare dependencies definition and usages only.

How to use Hilt in your Android project?

Setup

Add this Gradle dependency into the project’s root build.gradle

Then, include those plugins and dependencies into the app’s build.gradle

Lastly, enable Java 8:

Sync the project, and you’re ready to get your hands dirty with Hilt! 👻

Hilt Application

Hilt Application is mandatory — achieved by adding the@HiltAndroidApp annotation into your Application class.

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

@HiltAndroidApp — this god annotation will trigger the essential code generation.

“But, what will be generated?” you wonder… 🤔

  • Pre-define Hilt Component classes (like SingletonComponent , ActivityComponent , FragmentComponent ,…) that will be automatically integrated into the various lifecycles of an Android application.

  • A singleton component attaches to the application lifecycle and hosts all the singleton scoped dependencies. This component also provides dependencies to all other sub-components.

  • Finally, a custom Application extends from your Application class. This will be the container for all application-level dependencies.

Within your Application class, you can access the dependencies after super.onCreate(); got called! 🎉

Hilt Components

As I mentioned above, Hilt automatically generates some pre-defined Component classes. Those out-of-the-box components should cover most use cases when developing your Android app. We don’t need to create a Component manually anymore.

Pre-defined components

Checking out the auto-generated code (in HiltSampleApp_HiltComponents), you’ll see that for each Android class, Hilt’ll generate a corresponding Component :

Component lifecycle

Understanding the lifecycle of Hilt Component is important because it allows you to know when to access the injected fields.

Generally, the Component lifecycle bonds with the Android class instance’s lifecycle.

Component hierarchy

The diagram below shows the hierarchy of Hilt Components .

Basically, the binding in a Component can have dependencies on binding within the same Component or its ancestors.

Photo on https://developer.android.com/

Declare dependencies in Hilt Modules

Similar to Dagger Module , Hilt Module is where you declare all the needed dependencies for your application.

There’s a slight difference with Hilt that you have to specify which Component that the Module will be installed in using the @InstallIn annotation.

For example, I have the ApiModule that provides all the dependencies related to API in my application (AppApi for specific). By declaring @InstallIn(SingletonComponent::class) , all the dependencies declared inside ApiModule are available globally within the application.

I also have the UseCaseModule which provides all the use cases for my application. This time, I want all the use case dependencies should be available to all the Activities . So I make the UseCaseModule to be installed in the ActivityComponent instead.

If you noticed, the GetUserProfileUseCaseImpl depends on AppApi (which is located in the SingletonComponent). Thanks to the component hierarchy, Hilt can provide those dependencies easily.

There are also 2 ways to declare dependencies in a Module like Dagger. You can use either @Binds or @Provides .

Inject dependencies into Android classes

Now, you have done setting up theComponent and Module . It’s time to get those dependencies injected!

And it’ll be easier than ever with 2 simple steps:

  • Put @AndroidEntryPoint annotation on the top of the Android class

  • @Inject the properties like using Dagger

Hilt currently supports the following Android classes, which can cover most of the cases:

  • Application (by using @HiltAndroidApp)

  • ViewModel (by using @HiltViewModel)

  • Activity

  • Fragment

  • View

  • Service

  • BroadcastReceiver

Inject dependencies into non-Android classes

Even though Hilt supports most of the Android classes, at some point, you’ll need to inject dependencies into non-supported Android classes.

For instance, Hilt doesn’t support inject dependencies into Worker directly. To do so, you have to create a custom @EntryPoint .

In that custom Entry Point , you specify the component and dependencies that can be accessed:

Then, access the needed dependencies via the EntryPointAccessors :

Compare Hilt and Dagger

Like I mentioned from the start of this post, Hilt’s mission is to provide a standard way to apply Dagger into your application easier.

Let’s revise to see which steps from Dagger got trimmed in Hilt:

  • Component class

  • Application scoped Component instancing in Application class

  • Implementing HasAndroidInjector if needed

  • Injection trigger (AndroidInjection.inject(this) or Component.inject(this)

  • Activity/Service/FragmentBindingModule declaration

In addition to that, Hilt also provides :

  • Pre-defined Scope to use with pre-defined Component

  • Pre-defined bindings for Android classes like Application , Activity

  • Pre-defined Qualifier like @ApplicationContext and @ActivityContext

More than that, you can use both Dagger and Hilt in the same codebase. So the migration can happen gradually.

Thanks for reading and happy coding! 💻