Popular Searches
Popular Course Categories
Popular Courses

Implementing Dependency Injection with Hilt

Implementing Dependency Injection with Hilt

Android Architecture and State Management

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

  1. Create a ProductRepository class using constructor injection.
  2. Inject ProductRepository into a ProductViewModel with @HiltViewModel.
  3. Create a NetworkModule that provides an API client using @Provides.
  4. Define a Storage interface and bind it to a LocalStorage implementation using @Binds.
  5. Create a singleton configuration manager and explain why its lifetime is application-wide.
  6. Use a qualifier to provide separate authenticated and unauthenticated clients.
  7. Create a fake repository for testing a ViewModel.
  8. Diagnose a missing binding error and explain how to resolve it.
  9. Compare constructor injection with field injection in an Android application.
  10. 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

Previous Next
whatsapp