Hilt — A Standard Way to Implement Dependency Injection in Android
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 (likeSingletonComponent
,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 yourApplication
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.
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 inApplication
class - Implementing
HasAndroidInjector
if needed - Injection trigger (
AndroidInjection.inject(this)
orComponent.inject(this)
Activity/Service/FragmentBindingModule
declaration
In addition to that, Hilt also provides :
- Pre-defined
Scope
to use with pre-definedComponent
- 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.
Original post @ Iced Tea Labs.
Thanks for reading and happy coding! 💻