Implementing Dependency Injection with Hilt in Android
Course: Android App Development
Topic: Implementing Dependency Injection with Hilt
Course Link: Android App Development Course
Demo Link: Register for a Free Course Demo
JustAcademy Website: Visit JustAcademy
1. Introduction to Dependency Injection
Dependency Injection (DI) is a software design technique in which an object receives the dependencies it needs from an external source instead of creating those dependencies itself. In Android development, DI helps organize application components, reduce tight coupling, improve testability, and simplify maintenance.
For example, a ViewModel may need a repository to retrieve user information. Without dependency injection, the ViewModel might create the repository directly. With DI, the repository is provided to the ViewModel, making the application easier to test and modify.
What Is a Dependency?
A dependency is an object or service that another class requires to perform its work. Common Android dependencies include repositories, API clients, database access objects, shared preferences wrappers, and business logic services.
class UserRepository {
fun getUserName(): String {
return "Android Developer"
}
}
class UserViewModel {
private val repository = UserRepository()
fun loadUserName(): String {
return repository.getUserName()
}
}
In this example, UserViewModel creates UserRepository directly. This creates a strong relationship between the two classes and makes replacing the repository during testing more difficult.
Benefits of Dependency Injection
- Loose coupling: Classes depend less on concrete implementations.
- Testability: Dependencies can be replaced with fake or mock implementations.
- Maintainability: Changes to object creation can be managed separately from business logic.
- Reusability: Shared dependencies can be provided to multiple components.
- Lifecycle management: Dependency scopes help control how long objects are reused.
- Cleaner architecture: ViewModels, repositories, and services become easier to organize.
2. What Is Hilt?
Hilt is a dependency injection library built on top of Dagger for Android applications. It provides predefined components, annotations, and lifecycle-aware integration to reduce the amount of dependency injection configuration developers must write manually.
Hilt manages the creation and delivery of dependencies according to the application's dependency graph. It can provide objects to Android components such as Application, Activity, Fragment, Service, and ViewModel.
Why Use Hilt in Android?
- It reduces repetitive dependency injection setup.
- It integrates with common Android lifecycle components.
- It supports constructor injection, field injection, and module-based provisioning.
- It provides scopes for controlling object lifetimes.
- It supports dependency replacement and testing configurations.
- It works well with modern Android architecture patterns such as MVVM and repository-based designs.
Hilt Compared with Manual Dependency Injection
| Feature |
Manual DI |
Hilt |
| Object creation |
Managed through manually written code |
Generated dependency graph manages creation |
| Configuration |
Can become repetitive as projects grow |
Uses annotations and modules |
| Android integration |
Must be designed manually |
Provides Android-specific components |
| Lifecycle scopes |
Implemented by the developer |
Supports predefined scopes |
| Testing |
Possible with custom object construction |
Supports test-specific dependency replacement |
3. Understanding the Hilt Dependency Graph
A dependency graph describes which objects require other objects and how those dependencies are created. Hilt uses this graph to determine how to construct and provide objects throughout an application.
Consider an application that displays information retrieved from a remote API. Its dependency flow may look like this:
UserActivity
|
v
UserViewModel
|
v
UserRepository
|
v
ApiService
|
v
Remote API
In this structure, UserViewModel depends on UserRepository, while UserRepository depends on ApiService. Hilt resolves these relationships when the required objects are requested.
Main Hilt Annotations
| Annotation |
Purpose |
| @HiltAndroidApp |
Initializes Hilt for the Android application. |
| @AndroidEntryPoint |
Enables injection into supported Android framework classes. |
| @Inject |
Marks constructors or fields for dependency injection. |
| @Module |
Defines a class containing dependency provisioning methods. |
| @InstallIn |
Specifies the Hilt component where a module is installed. |
| @Provides |
Provides an object using a method inside a module. |
| @Binds |
Maps an interface or abstract type to its implementation. |
| @Singleton |
Scopes a dependency to the SingletonComponent. |
| @ActivityScoped |
Scopes a dependency to an ActivityComponent. |
| @ActivityRetainedScoped |
Scopes a dependency to the ActivityRetainedComponent. |
| @ViewModelScoped |
Scopes a dependency to a ViewModelComponent. |
| @ApplicationContext |
Qualifies an injected application Context. |
4. Configuring Hilt in an Android Project
Before using Hilt, configure the Android project with the required Gradle plugin and dependencies. The exact syntax depends on whether the project uses Kotlin DSL or Groovy and whether it uses KAPT or KSP.
Step 1: Add the Hilt Plugin
In a Kotlin DSL project, configure the Hilt plugin in the root build.gradle.kts file using the version selected for your project:
plugins {
id("com.google.dagger.hilt.android") version "YOUR_HILT_VERSION" apply false
}
Replace YOUR_HILT_VERSION with a compatible Hilt release version. The plugin version should be aligned with the dependency version.
Step 2: Apply the Plugin and Dependencies
In the app-level build.gradle.kts file, apply the plugin and add the Hilt runtime and compiler dependencies. This example uses KSP; use the KSP plugin version compatible with your Kotlin version.
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android")
}
dependencies {
implementation("com.google.dagger:hilt-android:YOUR_HILT_VERSION")
ksp("com.google.dagger:hilt-android-compiler:YOUR_HILT_VERSION")
}
Use a compatible Kotlin, Android Gradle Plugin, Hilt, and KSP combination. If the project uses KAPT instead, configure the KAPT plugin and use the corresponding compiler configuration rather than mixing the two approaches without a specific reason.
Step 3: Synchronize Gradle
- Save the Gradle configuration files.
- Click Sync Now in Android Studio.
- Resolve any plugin or dependency compatibility errors.
- Build the project to verify that Hilt's annotation processing is configured correctly.
5. Initializing Hilt with @HiltAndroidApp
The @HiltAndroidApp annotation is applied to the Application class. It triggers the Hilt code generation process and creates the application-level dependency container.
Example: Application Class
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApplication : Application()
Register the custom Application class in AndroidManifest.xml:
<application
android:name=".MyApplication"
android:allowBackup="true"
android:label="@string/app_name"
android:theme="@style/Theme.MyApplication">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
Without the correct Application class registration, Hilt may not initialize as expected at runtime.
6. Constructor Injection with @Inject
Constructor injection is the preferred approach when a class's dependencies can be created through its constructor. It makes the class's requirements explicit and allows Hilt to construct the object automatically.
Example: Creating a Repository
import javax.inject.Inject
class UserRepository @Inject constructor() {
fun getUserName(): String {
return "Android Developer"
}
}
The @Inject constructor annotation tells Hilt how to create UserRepository. When another injectable class requests UserRepository, Hilt can supply an instance.
Example: Constructor Injection with a Dependency
import javax.inject.Inject
class Logger @Inject constructor() {
fun log(message: String) {
println(message)
}
}
class UserRepository @Inject constructor(
private val logger: Logger
) {
fun getUserName(): String {
logger.log("Loading user")
return "Android Developer"
}
}
Here, UserRepository depends on Logger. Hilt can construct Logger first and then provide it to UserRepository because both classes expose injectable constructors.
When to Use Constructor Injection
- When you control the source code of the class.
- When the class's dependencies can be constructed by Hilt.
- When you want dependencies to be explicit and easy to test.
- When building repositories, services, use cases, and other application classes.
7. Field Injection with @AndroidEntryPoint
Some Android framework classes are created by the Android system, so developers cannot directly control their constructors. Hilt supports field injection for supported Android components.
Example: Injecting a Dependency into an Activity
import android.os.Bundle
import androidx.activity.ComponentActivity
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var userRepository: UserRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val userName = userRepository.getUserName()
println(userName)
}
}
The @AndroidEntryPoint annotation enables Hilt injection in MainActivity. The repository field is populated by Hilt before the Activity's onCreate method runs.
Important Field Injection Rules
- The Android component must be supported by Hilt and annotated appropriately.
- Injected fields generally cannot be private because Hilt-generated code needs access to them.
- Do not access a lateinit injected field before injection has occurred.
- Prefer constructor injection for ordinary classes whenever possible.
- Field injection is commonly used at Android framework entry points, while other classes receive dependencies through constructors.
8. Injecting Dependencies into ViewModels
ViewModels store and manage UI-related state. A ViewModel often needs a repository to retrieve data or execute business logic. Hilt can provide dependencies to ViewModels using constructor injection and the @HiltViewModel annotation.
Example: Hilt ViewModel
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class UserViewModel @Inject constructor(
private val userRepository: UserRepository
) : ViewModel() {
fun loadUserName(): String {
return userRepository.getUserName()
}
}
Hilt generates the required integration so the ViewModel can receive UserRepository. The ViewModel should not manually construct its own repository.
Using the ViewModel in an Activity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val name = viewModel.loadUserName()
println(name)
}
}
For this example, the AppCompat dependency must be configured. If your Activity extends ComponentActivity, use the appropriate AndroidX Activity ViewModel integration instead. Hilt must be initialized through the Application class.
Why Use Hilt with ViewModels?
- Repositories are provided automatically.
- ViewModels do not need to know how repositories are constructed.
- Dependencies can be substituted for testing.
- ViewModel creation remains integrated with AndroidX lifecycle management.
9. Providing Dependencies with @Module and @Provides
Constructor injection works well when Hilt knows how to construct a class. However, some objects cannot be created through an injectable constructor, such as third-party library classes or objects that require a specific configuration.
In these cases, a Hilt module defines how the dependency should be provided.
Example: Providing an API Client
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
class ApiClient {
fun baseUrl(): String {
return "https://api.example.com/"
}
}
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideApiClient(): ApiClient {
return ApiClient()
}
}
The module tells Hilt how to create ApiClient. The @Provides annotation marks the provider method, and @InstallIn(SingletonComponent::class) places the binding in the application-level component.
The @Singleton annotation indicates that Hilt should reuse the provided instance within the SingletonComponent's lifetime.
When to Use @Provides
- When creating third-party library objects.
- When construction requires configuration values.
- When a builder or factory method is needed.
- When you cannot add an @Inject constructor to the class.
- When the dependency needs a specific setup process.
10. Binding Interfaces with @Binds
Applications often define interfaces so that high-level classes depend on abstractions rather than concrete implementations. The @Binds annotation tells Hilt which implementation should satisfy a requested interface.
Example: Repository Interface
interface UserDataSource {
fun getUserName(): String
}
class LocalUserDataSource @Inject constructor() : UserDataSource {
override fun getUserName(): String {
return "Local User"
}
}
Create a module to bind the implementation to the interface:
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
abstract class DataSourceModule {
@Binds
abstract fun bindUserDataSource(
implementation: LocalUserDataSource
): UserDataSource
}
Now any injectable class that requests UserDataSource can receive LocalUserDataSource.
Using the Interface
class UserRepository @Inject constructor(
private val dataSource: UserDataSource
) {
fun getUserName(): String {
return dataSource.getUserName()
}
}
Because UserRepository depends on the interface, the underlying implementation can be replaced with a remote or fake data source without changing the repository's constructor.
@Provides vs. @Binds
| Feature |
@Provides |
@Binds |
| Definition |
Uses a method that returns an object |
Maps an implementation to an abstraction |
| Typical use |
Configuring clients or library objects |
Binding interfaces to implementations |
| Module type |
Often an object module with provider methods |
Abstract module with abstract binding methods |
| Object creation |
Provider method constructs or obtains the object |
Hilt constructs the implementation using its binding |
11. Understanding Hilt Components and Scopes
Hilt components define where dependencies are available and how long scoped objects are retained. Selecting an appropriate scope prevents unnecessary object recreation while avoiding accidental retention of objects longer than intended.
| Component |
Typical Scope |
Lifetime |
| SingletonComponent |
@Singleton |
Application lifetime |
| ActivityRetainedComponent |
@ActivityRetainedScoped |
Retained across Activity recreation |
| ViewModelComponent |
@ViewModelScoped |
Associated ViewModel lifetime |
| ActivityComponent |
@ActivityScoped |
Activity instance lifetime |
| FragmentComponent |
@FragmentScoped |
Fragment instance lifetime |
| ServiceComponent |
@ServiceScoped |
Service instance lifetime |
Example: Singleton Repository
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class UserRepository @Inject constructor() {
fun getUserName(): String {
return "Android Developer"
}
}
When requested through the same SingletonComponent, the scoped repository instance is reused. A scope does not automatically make every class in the application a singleton; it must be applied appropriately to the dependency.
Scope Selection Best Practices
- Use @Singleton for application-wide objects that are safe to share.
- Use @ViewModelScoped for objects intended to be shared within one ViewModel's dependency graph.
- Use @ActivityScoped for dependencies whose lifetime should match an Activity instance.
- Avoid storing Activity or View references inside application-scoped objects.
- Do not add scopes unnecessarily when a new instance for each request is sufficient.
12. Injecting Android Context
Some dependencies require an Android Context, for example, to access application resources, preferences, or system services. Hilt provides qualifiers that distinguish between application and Activity contexts.
Example: Using Application Context
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class PreferencesManager @Inject constructor(
@ApplicationContext private val context: Context
) {
fun getApplicationName(): String {
return context.applicationInfo.loadLabel(
context.packageManager
).toString()
}
}
The @ApplicationContext qualifier requests the application-level Context. It is appropriate for dependencies that should not retain an Activity instance.
Application Context vs. Activity Context
| Context Type |
Purpose |
Important Consideration |
| Application Context |
Application-wide resources and services |
Suitable for long-lived dependencies that do not require an Activity |
| Activity Context |
Activity-specific UI, themed resources, and dialogs |
Should not be retained beyond the Activity lifecycle |
13. Using Hilt with the Repository Pattern
The repository pattern separates data access from UI and business logic. A repository can coordinate local storage, remote APIs, caching, and data transformation. Hilt simplifies the process of providing repositories to ViewModels.
Example: Repository and ViewModel
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class UserRepository @Inject constructor() {
fun fetchUser(): String {
return "Welcome to Android Development"
}
}
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
fun getWelcomeMessage(): String {
return repository.fetchUser()
}
}
In this example, Hilt constructs HomeViewModel and provides UserRepository. The ViewModel focuses on the UI-facing operation rather than the details of repository construction.
Recommended Architecture
UI Layer
|
v
ViewModel
|
v
Repository
|
+---- Remote Data Source
|
+---- Local Data Source
|
v
API / Database
Hilt handles dependency creation and delivery across this structure. It does not replace the responsibilities of each architectural layer: the repository still manages data access, and the ViewModel still manages UI-related state.
14. Qualifiers for Multiple Implementations
Sometimes an application needs multiple dependencies of the same type. For example, a networking layer may require separate clients for public and authenticated API requests. Hilt qualifiers identify which binding should be injected.
Example: Named API Clients
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class PublicClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthenticatedClient
Use the qualifiers in a module:
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
class ApiClient(val name: String)
@Module
@InstallIn(SingletonComponent::class)
object ApiClientModule {
@Provides
@Singleton
@PublicClient
fun providePublicClient(): ApiClient {
return ApiClient("Public")
}
@Provides
@Singleton
@AuthenticatedClient
fun provideAuthenticatedClient(): ApiClient {
return ApiClient("Authenticated")
}
}
Inject the correct instance by applying the matching qualifier:
class ApiRepository @Inject constructor(
@PublicClient private val publicClient: ApiClient,
@AuthenticatedClient private val authenticatedClient: ApiClient
)
Qualifiers eliminate ambiguity when multiple bindings share the same type. Without qualifiers or another distinct binding key, Hilt may report a duplicate binding error.
15. Testing Android Applications with Hilt
Dependency injection improves testability because a class can receive alternative implementations without changing its business logic. Hilt provides testing support that allows dependencies to be replaced in appropriate test configurations.
Why Replace Dependencies During Tests?
- Tests should not depend unnecessarily on live network services.
- Fake repositories can return predictable data.
- Tests become faster and more reliable.
- Error conditions can be simulated without manipulating real production systems.
Example: Fake Repository
interface UserSource {
fun getUserName(): String
}
class RealUserSource @Inject constructor() : UserSource {
override fun getUserName(): String {
return "Real User"
}
}
class FakeUserSource @Inject constructor() : UserSource {
override fun getUserName(): String {
return "Test User"
}
}
For production, bind UserSource to RealUserSource. In a test configuration, replace that binding with FakeUserSource using Hilt's testing facilities, such as @TestInstallIn or @UninstallModules where appropriate.
Example: Replacing a Module in Tests
// Production module
@Module
@InstallIn(SingletonComponent::class)
abstract class UserSourceModule {
@Binds
abstract fun bindUserSource(
implementation: RealUserSource
): UserSource
}
A test can uninstall the production module and install a test module that binds UserSource to FakeUserSource. The test application must also be configured according to Hilt's testing requirements, and Android instrumented tests commonly use HiltAndroidRule.
Testing Best Practices
- Keep interfaces focused on the operations consumers actually need.
- Use fake implementations for deterministic unit tests.
- Use Hilt integration tests when validating the real dependency graph.
- Do not rely on live APIs in ordinary unit tests.
- Keep test-specific bindings separate from production bindings.
16. Common Hilt Errors and Troubleshooting
| Error or Problem |
Possible Cause |
Solution |
| Missing binding |
Hilt does not know how to create a requested type |
Add an injectable constructor, @Provides method, or @Binds mapping. |
| Duplicate binding |
Multiple bindings provide the same type and qualifier |
Remove the duplicate or define distinct qualifiers. |
| Injection does not occur |
Missing @AndroidEntryPoint or incorrect Application setup |
Check annotations, manifest registration, and generated configuration. |
| ViewModel creation failure |
Missing @HiltViewModel, @Inject constructor, or dependency binding |
Check the ViewModel declaration and all constructor dependencies. |
| Gradle build failure |
Incompatible plugin, Kotlin, KSP, or Hilt versions |
Align compatible versions and inspect compiler errors. |
| Lateinit property not initialized |
Property accessed before injection or component setup |
Verify the injection entry point and access the property after injection. |
| Incorrect object lifetime |
Scope does not match the dependency's intended lifetime |
Select the correct Hilt component and scope. |
| Context-related memory leak |
Long-lived object retains an Activity Context |
Use @ApplicationContext when Activity-specific behavior is unnecessary. |
Debugging Checklist
- Confirm that @HiltAndroidApp is applied to the Application class.
- Verify that the custom Application class is registered in AndroidManifest.xml.
- Check that every injected type has a valid binding.
- Confirm that modules use the correct @InstallIn component.
- Check that scopes are compatible with the component where a binding is installed.
- Review the complete compiler error because it often identifies the missing dependency path.
- Sync Gradle and rebuild after correcting configuration issues.
17. Best Practices for Implementing Hilt
- Prefer constructor injection: Use @Inject constructors for application-owned classes when possible.
- Keep modules focused: Group related bindings logically, such as network, database, and repository bindings.
- Use interfaces thoughtfully: Abstract dependencies where implementation replacement or separation of concerns is useful.
- Choose scopes deliberately: Do not make every dependency a singleton by default.
- Avoid context leaks: Do not retain Activity references in application-scoped dependencies.
- Use qualifiers when needed: Distinguish multiple instances of the same type clearly.
- Keep UI logic separate: Hilt provides dependencies; it should not become a substitute for a well-designed application architecture.
- Test the dependency graph: Verify that production and test configurations provide the intended implementations.
- Maintain compatible dependencies: Keep Gradle plugins and annotation processors aligned with the project's Kotlin and Android Gradle Plugin versions.
- Use descriptive names: Names such as NetworkModule, DatabaseModule, and RepositoryModule make the configuration easier to understand.
18. Practical Project: User Profile Application Using Hilt
Project objective: Build a simple Android screen that displays a user's name through a ViewModel and repository, with Hilt managing the dependency graph.
Project Structure
com.example.userprofile
|
+-- MyApplication.kt
+-- MainActivity.kt
|
+-- data
| +-- UserRepository.kt
|
+-- ui
+-- UserViewModel.kt
Step 1: Create the Repository
package com.example.userprofile.data
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class UserRepository @Inject constructor() {
fun getUserName(): String {
return "Alex Johnson"
}
fun getUserRole(): String {
return "Android Developer"
}
}
Step 2: Create the ViewModel
package com.example.userprofile.ui
import androidx.lifecycle.ViewModel
import com.example.userprofile.data.UserRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
fun getProfile(): String {
return "${repository.getUserName()} - ${repository.getUserRole()}"
}
}
Step 3: Initialize Hilt
package com.example.userprofile
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApplication : Application()
Register MyApplication in AndroidManifest.xml using android:name=".MyApplication".
Step 4: Use the ViewModel in an Activity
package com.example.userprofile
import android.os.Bundle
import android.widget.TextView
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.example.userprofile.ui.UserViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val textView = TextView(this)
textView.text = viewModel.getProfile()
setContentView(textView)
}
}
Ensure that AndroidX Activity, AppCompat, Lifecycle ViewModel, Hilt, and their compatible build plugins are configured in the project. The example displays a basic profile string; a production application should use suitable UI state and asynchronous data handling for operations such as network requests.
Expected Output
Alex Johnson - Android Developer
What This Project Demonstrates
- Hilt initialization through the Application class.
- Constructor injection into a ViewModel.
- Automatic repository creation and delivery.
- Separation of UI and data responsibilities.
- Reduced coupling between the ViewModel and repository construction.
19. Practice Exercises
- Create a ProductRepository class using constructor injection.
- Inject ProductRepository into a ProductViewModel with @HiltViewModel.
- Create a NetworkModule that provides an API client using @Provides.
- Define a Storage interface and bind it to a LocalStorage implementation using @Binds.
- Create a singleton configuration manager and explain why its lifetime is application-wide.
- Use a qualifier to provide separate authenticated and unauthenticated clients.
- Create a fake repository for testing a ViewModel.
- Diagnose a missing binding error and explain how to resolve it.
- Compare constructor injection with field injection in an Android application.
- Build a simple notes application using Hilt, a repository, a ViewModel, and a local database.
20. Interview Questions and Answers
Q1. What is Dependency Injection?
Answer: Dependency Injection is a design technique in which a class receives the objects it requires from an external provider instead of creating those objects directly.
Q2. What is Hilt?
Answer: Hilt is an Android-focused dependency injection library built on Dagger. It provides standard components, annotations, and generated code for managing dependencies.
Q3. What is the purpose of @HiltAndroidApp?
Answer: It marks the Application class as the entry point for Hilt initialization and triggers generation of the application-level dependency graph.
Q4. What does @AndroidEntryPoint do?
Answer: It enables Hilt injection in supported Android framework classes, such as Activities and Fragments.
Q5. What is constructor injection?
Answer: Constructor injection supplies dependencies through a class constructor. In Hilt, an @Inject constructor tells the framework how to construct the class and resolve its dependencies.
Q6. When should @Provides be used?
Answer: Use @Provides when a dependency requires a provider method to construct or configure it, particularly for third-party classes or objects that do not expose an injectable constructor.
Q7. What is the difference between @Provides and @Binds?
Answer: @Provides defines a method that returns a dependency. @Binds declares which implementation should satisfy an interface or abstract type.
Q8. What is a Hilt scope?
Answer: A scope controls how a dependency instance is reused within a component's lifetime. For example, @Singleton scopes a dependency to the application-level SingletonComponent.
Q9. How does Hilt support ViewModels?
Answer: A ViewModel can use @HiltViewModel and an @Inject constructor. Hilt then resolves its dependencies when the ViewModel is created through the supported AndroidX integration.
Q10. What is a qualifier?
Answer: A qualifier distinguishes bindings that share the same type, such as two API clients with different authentication configurations.
Q11. What causes a missing binding error?
Answer: A missing binding error occurs when Hilt cannot determine how to provide a requested dependency. The solution is to add a valid injectable constructor, provider method, or interface binding.
Q12. Why is dependency injection useful for testing?
Answer: It allows production dependencies to be replaced by fake or mock implementations, helping tests run independently of external services and unpredictable data.
Q13. What is the difference between application and Activity Context?
Answer: Application Context is associated with the application lifetime, while Activity Context is associated with a particular Activity. Long-lived dependencies should avoid retaining Activity Context unnecessarily.
Q14. Does Hilt replace the repository pattern?
Answer: No. Hilt manages dependency creation and delivery. The repository pattern remains an architectural technique for separating data access from other application layers.
Q15. What are the main advantages of Hilt over manual DI?
Answer: Hilt provides standardized Android components, generated dependency wiring, scopes, module support, and integration with AndroidX components, reducing repetitive manual setup.
21. Quick Revision Checklist
- Understand dependency injection and loose coupling.
- Know how Hilt initializes through @HiltAndroidApp.
- Understand the purpose of @AndroidEntryPoint.
- Use @Inject for constructors and eligible fields.
- Use @Module and @InstallIn to organize dependency configuration.
- Use @Provides for dependencies requiring explicit construction.
- Use @Binds to connect interfaces with implementations.
- Understand component lifetimes and scopes.
- Inject repositories into ViewModels using @HiltViewModel.
- Use qualifiers to distinguish dependencies of the same type.
- Know how Hilt supports test-specific dependencies.
- Recognize and resolve missing binding and duplicate binding errors.
22. Conclusion
Implementing Dependency Injection with Hilt is an important skill for building maintainable Android applications. Hilt simplifies dependency management through constructor injection, modules, bindings, qualifiers, and lifecycle-aware scopes.
By integrating Hilt with ViewModels and repositories, developers can separate UI logic from data access, improve testability, and reduce tight coupling. A strong understanding of Hilt helps Android developers build applications that are easier to extend, debug, and maintain as project requirements grow.
Useful Course Links