0
0
Android Kotlinmobile~15 mins

Dependency injection with Hilt in depth in Android Kotlin - Deep Dive

Choose your learning style9 modes available
Overview - Dependency injection with Hilt in depth
What is it?
Dependency Injection with Hilt is a way to provide objects that a class needs, called dependencies, without creating them inside the class. Hilt is a tool that helps Android apps get these dependencies automatically and safely. It makes your code cleaner and easier to test by managing how objects are created and shared. Instead of writing code to build dependencies yourself, Hilt does it for you.
Why it matters
Without dependency injection, classes create their own dependencies, which makes code hard to change, test, and reuse. Hilt solves this by managing dependencies centrally, so you can swap parts easily and avoid bugs. This saves time and effort, especially in big apps where many parts depend on each other. Without Hilt, apps would be more tangled and fragile, making development slower and more error-prone.
Where it fits
Before learning Hilt, you should understand basic Kotlin programming, Android app structure, and what classes and objects are. Knowing about interfaces and testing helps too. After Hilt, you can learn advanced dependency injection patterns, custom scopes, and how to combine Hilt with other Android libraries like Jetpack Compose or Retrofit.
Mental Model
Core Idea
Dependency Injection with Hilt means giving classes the objects they need from a central place, so they don’t have to build or find those objects themselves.
Think of it like...
Imagine a coffee shop where the barista doesn’t have to grow coffee beans or make milk; instead, a supplier delivers everything ready. Hilt is like that supplier, providing all ingredients so the barista can focus on making coffee.
┌───────────────┐       ┌───────────────┐
│   Client      │──────▶│  Dependency   │
│ (Needs obj)   │       │  Provider     │
└───────────────┘       └───────────────┘
         ▲                      ▲
         │                      │
         │          ┌─────────────────────────┐
         └─────────▶│  Hilt Dependency Graph  │
                    └─────────────────────────┘
Build-Up - 7 Steps
1
FoundationWhat is Dependency Injection
🤔
Concept: Dependency Injection means giving a class the objects it needs instead of making them inside the class.
In Android, classes often need other objects to work, like a database or a network client. Without injection, each class creates these objects itself, which can cause problems. Dependency Injection solves this by letting another part of the app provide these objects.
Result
Classes become simpler and easier to change because they don’t create their own dependencies.
Understanding that classes should not build their own dependencies helps keep code clean and flexible.
2
FoundationIntroduction to Hilt
🤔
Concept: Hilt is a tool that automates dependency injection in Android apps using annotations.
Hilt uses special annotations to tell the app how to provide dependencies. It builds a graph of dependencies and injects them where needed. This removes the need to write manual code for creating and passing dependencies.
Result
You can add @Inject to constructors or fields, and Hilt will provide the needed objects automatically.
Knowing that Hilt handles the creation and sharing of dependencies reduces boilerplate and errors.
3
IntermediateUsing @Inject and @Module Annotations
🤔Before reading on: do you think @Inject alone is enough to provide all dependencies in Hilt? Commit to yes or no.
Concept: Hilt uses @Inject to mark constructors for injection and @Module to define how to provide objects it cannot create by itself.
You can add @Inject to a class constructor to tell Hilt how to create it. For classes without @Inject constructors, you create a @Module with @Provides functions that return the needed objects. Hilt combines these to build the dependency graph.
Result
Hilt knows how to create and supply all dependencies, even complex ones, by combining @Inject and @Module.
Understanding the difference between @Inject and @Module helps you provide dependencies that are not simple to construct.
4
IntermediateScopes and Component Lifecycles
🤔Before reading on: do you think all dependencies in Hilt live as long as the app? Commit to yes or no.
Concept: Hilt uses scopes to control how long dependencies live, matching Android component lifecycles like activities or fragments.
You can annotate dependencies with scopes like @Singleton for app-wide single instances or @ActivityScoped for objects that live only during an activity. This helps manage memory and behavior correctly.
Result
Dependencies are created and destroyed at the right times, avoiding leaks and unnecessary work.
Knowing scopes lets you control resource use and app behavior precisely.
5
IntermediateInjecting into Android Classes
🤔
Concept: Hilt can inject dependencies directly into Android classes like Activities, Fragments, and ViewModels.
By annotating Android classes with @AndroidEntryPoint, Hilt can provide dependencies automatically. For ViewModels, you use @HiltViewModel and inject dependencies via constructor. This integrates Hilt smoothly with Android lifecycle.
Result
Your Android components receive dependencies without manual wiring, making code cleaner and safer.
Understanding how Hilt integrates with Android lifecycles simplifies app architecture.
6
AdvancedCustom Qualifiers and Multi-bindings
🤔Before reading on: can Hilt distinguish between two dependencies of the same type without extra help? Commit to yes or no.
Concept: Hilt uses qualifiers to differentiate between multiple dependencies of the same type and supports multi-bindings to provide collections of dependencies.
You create custom annotations with @Qualifier to tell Hilt which dependency to inject when types are the same. Multi-bindings let you provide sets or maps of objects, useful for plugins or strategies.
Result
You can manage complex dependency scenarios with clarity and precision.
Knowing qualifiers and multi-bindings unlocks advanced dependency management in large apps.
7
ExpertHilt’s Generated Code and Performance
🤔Before reading on: do you think Hilt uses reflection at runtime to inject dependencies? Commit to yes or no.
Concept: Hilt generates code at compile time to create and inject dependencies, avoiding runtime reflection for better performance.
Hilt’s annotation processor builds a dependency graph and generates classes that create and supply dependencies. This means injection is fast and safe, with errors caught early during compilation.
Result
Apps using Hilt have efficient startup and runtime behavior with clear error messages if dependencies are missing.
Understanding Hilt’s compile-time code generation explains why it is both fast and reliable compared to older injection methods.
Under the Hood
Hilt uses annotation processing during app compilation to scan for @Inject, @Module, and other annotations. It builds a graph of all dependencies and generates code that knows how to create and provide each object. At runtime, this generated code is called to supply dependencies without using slow reflection. Hilt also integrates with Android lifecycle components by generating specialized components and subcomponents that match lifecycles like Application, Activity, and Fragment.
Why designed this way?
Hilt was designed to simplify Dagger’s complex setup by providing standard components and lifecycle integration for Android. It avoids runtime reflection to improve performance and catches errors at compile time to help developers fix issues early. The design balances automation with explicit control, making dependency injection easier and safer for Android developers.
┌───────────────┐
│ Source Code   │
│ (@Inject,    │
│  @Module)    │
└──────┬────────┘
       │ Annotation Processor
       ▼
┌───────────────┐
│ Generated     │
│ Dependency    │
│ Graph & Code  │
└──────┬────────┘
       │ Runtime Injection
       ▼
┌───────────────┐
│ Android App   │
│ Components    │
│ (Activities,  │
│  ViewModels)  │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does Hilt create dependencies at runtime using reflection? Commit yes or no.
Common Belief:Hilt uses reflection at runtime to find and inject dependencies.
Tap to reveal reality
Reality:Hilt generates all injection code at compile time, so no reflection is used at runtime.
Why it matters:Believing in runtime reflection can lead to wrong assumptions about app performance and debugging.
Quick: Can you inject dependencies into any class without annotations? Commit yes or no.
Common Belief:You can inject dependencies into any class without marking it or its constructor.
Tap to reveal reality
Reality:Classes must have @Inject constructors or be provided by @Module functions for Hilt to inject them.
Why it matters:Missing annotations cause injection failures and runtime crashes, confusing beginners.
Quick: Are all dependencies in Hilt singletons by default? Commit yes or no.
Common Belief:All dependencies provided by Hilt live as singletons throughout the app.
Tap to reveal reality
Reality:Dependencies have no scope by default and are recreated each time unless annotated with a scope like @Singleton.
Why it matters:Assuming singleton scope can cause unexpected behavior and resource waste.
Quick: Does Hilt automatically inject dependencies into custom classes without Android lifecycle? Commit yes or no.
Common Belief:Hilt can inject dependencies into any class automatically, even if it’s not an Android component.
Tap to reveal reality
Reality:Hilt only injects Android components automatically; for other classes, you must request injection explicitly or use @Inject constructors.
Why it matters:Expecting automatic injection everywhere leads to confusion and errors in app design.
Expert Zone
1
Hilt’s component hierarchy mirrors Android lifecycles, allowing fine-grained control over dependency lifetimes beyond simple singleton or unscoped instances.
2
Using entry points, experts can access Hilt dependencies in classes not directly supported by @AndroidEntryPoint, enabling advanced integration.
3
Hilt supports assisted injection for cases where some constructor parameters are provided at runtime, a subtle but powerful feature for complex scenarios.
When NOT to use
Hilt is not ideal for pure Kotlin projects without Android or for apps requiring dynamic runtime dependency graphs. In such cases, manual injection or other DI frameworks like Koin may be better suited.
Production Patterns
In production, Hilt is used with modularized apps where each module provides its own dependencies. It is combined with ViewModel injection, Retrofit for networking, and Room for databases, ensuring clean separation and testability.
Connections
Inversion of Control (IoC)
Dependency Injection is a form of IoC where control of creating dependencies is inverted from the class to an external system.
Understanding IoC helps grasp why DI frameworks like Hilt improve code modularity and testability.
Service Locator Pattern
Both provide dependencies, but Service Locator requires classes to ask for dependencies, while DI injects them automatically.
Knowing the difference clarifies why DI leads to cleaner, more testable code than Service Locator.
Supply Chain Management
Like managing parts delivery in a factory, Hilt manages supplying objects to classes efficiently and on time.
Seeing DI as supply chain management highlights the importance of timing, scope, and reuse in software design.
Common Pitfalls
#1Forgetting to annotate Android classes with @AndroidEntryPoint.
Wrong approach:class MainActivity : AppCompatActivity() { @Inject lateinit var repo: UserRepository }
Correct approach:@AndroidEntryPoint class MainActivity : AppCompatActivity() { @Inject lateinit var repo: UserRepository }
Root cause:Hilt requires @AndroidEntryPoint to generate necessary code for injection in Android components.
#2Providing dependencies without scope but expecting singleton behavior.
Wrong approach:@Module @InstallIn(SingletonComponent::class) object NetworkModule { @Provides fun provideApi(): ApiService = ApiService() }
Correct approach:@Module @InstallIn(SingletonComponent::class) object NetworkModule { @Provides @Singleton fun provideApi(): ApiService = ApiService() }
Root cause:Without @Singleton, Hilt creates a new instance each time, not a shared singleton.
#3Trying to inject dependencies into classes without @Inject constructor or @Module provider.
Wrong approach:class Logger { // no @Inject constructor } @Inject class UserManager(val logger: Logger) {}
Correct approach:class Logger @Inject constructor() {} @Inject class UserManager(val logger: Logger) {}
Root cause:Hilt needs a way to create dependencies, either via @Inject constructor or @Provides method.
Key Takeaways
Dependency Injection with Hilt simplifies how Android apps get the objects they need by automating creation and supply.
Hilt uses annotations to build a graph of dependencies at compile time, avoiding runtime reflection and improving performance.
Scopes in Hilt control how long dependencies live, matching Android lifecycles to manage resources efficiently.
Understanding qualifiers and modules lets you handle complex cases where multiple dependencies share the same type.
Proper use of @AndroidEntryPoint and @Inject annotations is essential to enable Hilt’s automatic injection in Android components.