Android Development

    Android Hilt — Android Developer Reference

    A
    AzeemApr 3, 2026 · 12 min read
    137
    Android Hilt — Android  Developer Reference

    Android Hilt has become the standard dependency injection framework for modern Android development, replacing the complexity of Dagger with a clean, lifecycle-aware, and production-ready approach. This guide serves as a complete developer reference, covering everything from Hilt fundamentals—@Inject, @Module, @Provides, scopes, components, and entry points—to advanced patterns used in real Android apps. Whether you’re building MVVM, Clean Architecture, or multi-module applications, Hilt simplifies object creation, testing, and dependency management. This article helps developers understand how Hilt actually works under the hood, why Google recommends it, and how to implement it efficiently in scalable Android projects.

    1. What is Hilt & Why It Exists

    Dependency Injection (DI) means: instead of a class creating its own dependencies, those dependencies are provided from outside.

    Without DI:

    class UserRepository {
        private val api = Retrofit.Builder().build().create(UserApi::class.java)
    }
    
    kotlin

    With DI:

    class UserRepository(private val api: UserApi)
    
    kotlin

    Android apps usually have many objects — Repository, UseCase, Retrofit, Room Database, DAO, ViewModel, Worker, Navigator, SharedPreferences, Dispatchers. If every class manually creates its own dependencies, code becomes tightly coupled, testing becomes hard, object creation logic gets duplicated, and lifecycle handling becomes messy.

    Hilt solves this by:

    • Creating dependencies in one place

    • Reusing shared objects safely

    • Connecting object lifetime to Android lifecycle

    • Making testing easier

    Hilt answers 3 main questions:

    1. What object do you need?

    2. How should that object be created?

    3. How long should that object live?

    Want to Crack Android Interviews and package of 20LPA-35LPA in android. Join 12 Weeks Android Job Ready Program.

    See More Details

    2. Core Concepts & Terminology

    Dependency — An object another class needs.

    class UserRepository(private val api: UserApi)
    // UserApi is a dependency of UserRepository
    
    kotlin

    Injection — Providing the dependency to a class.

    Constructor Injection — Dependencies are given through the constructor. This is the best and most preferred way.

    class UserRepository @Inject constructor(
        private val api: UserApi
    )
    
    kotlin

    Module — A class where you tell Hilt how to provide objects it cannot construct itself (e.g. Retrofit, Room database, interface implementations, third-party classes).

    Component — A container that holds and provides dependencies for a specific lifecycle.

    Scope — Defines how long the same instance should live.

    Why Hilt instead of plain Dagger? Dagger is powerful but verbose. Hilt simplifies Dagger for Android by generating components for Android classes, reducing boilerplate, and giving lifecycle-aware components out of the box.

    • Dagger = core DI engine

    • Hilt = Android-friendly wrapper over Dagger

    Hilt works at compile time — it reads annotations and generates Dagger components behind the scenes. Errors are caught at build time, and performance is better than reflection-based DI.

    3. Project Setup

    In your app/build.gradle:

    plugins {
        id("com.google.dagger.hilt.android")
        kotlin("kapt")
    }
    
    dependencies {
        implementation("com.google.dagger:hilt-android:VERSION")
        kapt("com.google.dagger:hilt-android-compiler:VERSION")
    }
    
    kotlin

    Your Application class:

    @HiltAndroidApp
    class MyApp : Application()
    
    kotlin

    @HiltAndroidApp tells Hilt to generate the application-level dependency container. It goes only on your Application class and is the starting point for everything Hilt does.


    4. Android Entry Points

    For Hilt to inject dependencies into Android framework classes, mark them with @AndroidEntryPoint:

    @AndroidEntryPoint
    class MainActivity : AppCompatActivity()
    
    @AndroidEntryPoint
    class HomeFragment : Fragment()
    
    kotlin

    Why needed? Because Android creates Activities and Fragments, not you. Hilt hooks into that creation process.

    Can be used on: Activity, Fragment, View, Service, BroadcastReceiver.

    5. Constructor & Field Injection

    Constructor Injection (preferred):

    class UserRepository @Inject constructor(
        private val api: UserApi
    )
    
    kotlin

    Simple, testable, explicit, less boilerplate. Use this whenever possible.

    Field Injection (used in Android framework classes):

    @AndroidEntryPoint
    class MainActivity : AppCompatActivity() {
    
        @Inject lateinit var repository: UserRepository
    }
    
    kotlin

    You need field injection here because you do not control the Activity constructor. Prefer constructor injection everywhere else.

    6. Modules: @Provides & @Binds

    Some classes cannot use constructor injection — Retrofit instances, Room database builder, interface types, third-party SDK classes. For these, use a module.

    @Module
    @InstallIn(SingletonComponent::class)
    object NetworkModule {
    
        @Provides
        fun provideRetrofit(): Retrofit {
            return Retrofit.Builder()
                .baseUrl("<https://example.com/>")
                .build()
        }
    
        @Provides
        fun provideUserApi(retrofit: Retrofit): UserApi {
            return retrofit.create(UserApi::class.java)
        }
    }
    
    kotlin

    @Module — this class provides dependencies. @InstallIn — tells which component gets these dependencies. @Provides — tells how to create the object.

    @Provides vs @Binds

    Use @Provides when you manually create the object, creation needs logic, it's a third-party class, or uses a builder pattern:

    @Provides
    fun provideApi(retrofit: Retrofit): UserApi = retrofit.create(UserApi::class.java)
    
    kotlin

    Use @Binds when binding an interface to its implementation, and the implementation already has @Inject constructor:

    interface Logger
    class LoggerImpl @Inject constructor() : Logger
    
    @Module
    @InstallIn(SingletonComponent::class)
    abstract class LoggerModule {
    
        @Binds
        abstract fun bindLogger(impl: LoggerImpl): Logger
    }
    
    kotlin

    @Binds is better here — less code, more efficient, clearer intent.

    7. Scopes & Lifecycles

    Scope decides whether Hilt creates a new instance every time, or one shared instance for a lifecycle.

    Scope

    Component

    Lifetime

    @Singleton

    SingletonComponent

    Entire app

    @ActivityRetainedScoped

    ActivityRetainedComponent

    Survives rotation

    @ViewModelScoped

    ViewModelComponent

    ViewModel lifetime

    @ActivityScoped

    ActivityComponent

    Activity instance

    @FragmentScoped

    FragmentComponent

    Fragment instance

    @ServiceScoped

    ServiceComponent

    Service lifetime

    @Singleton
    class UserRepository @Inject constructor(
        private val api: UserApi
    )
    
    kotlin

    @Singleton = one instance for the whole app, inside SingletonComponent.

    Why use scopes? Save memory, preserve state where needed, avoid unnecessary object creation.

    Important: Do not make everything @Singleton. Use the smallest correct lifecycle.

    8. Hilt Components

    Hilt creates components tied to Android lifecycles.

    SingletonComponent — Lives as long as the app process. Use for Retrofit, Room database, repositories, app-wide managers.

    ActivityRetainedComponent — Lives across configuration changes. Use for objects that should survive rotation.

    ViewModelComponent — Lives as long as a ViewModel. Use for ViewModel-specific use cases and state holders.

    ActivityComponent — Lives as long as an Activity instance.

    FragmentComponent — Lives as long as a Fragment instance.

    ServiceComponent — Lives as long as a Service instance.

    Key distinction:

    • @InstallIn(SingletonComponent::class) = where the binding lives (which component holds it)

    • @Singleton = how many instances (one shared instance)

    Both are needed together for a true app-wide singleton:

    @Module
    @InstallIn(SingletonComponent::class)
    object NetworkModule {
    
        @Singleton
        @Provides
        fun provideApiService(retrofit: Retrofit): ApiService {
            return retrofit.create(ApiService::class.java)
        }
    }
    
    kotlin

    Without @Singleton, Hilt may create a new instance each time even if the module is installed in SingletonComponent.

    9. Qualifiers — @Named & Custom

    Sometimes multiple objects have the same type. For example, you may need two OkHttpClient instances — one authenticated, one not. Hilt gets confused unless you distinguish them using qualifiers.

    Custom Qualifiers (recommended for production)

    @Qualifier
    @Retention(AnnotationRetention.BINARY)
    annotation class AuthClient
    
    @Qualifier
    @Retention(AnnotationRetention.BINARY)
    annotation class PublicClient
    
    kotlin
    @AuthClient
    @Provides
    fun provideAuthClient(): OkHttpClient = OkHttpClient()
    
    @PublicClient
    @Provides
    fun providePublicClient(): OkHttpClient = OkHttpClient()
    
    kotlin
    class ApiManager @Inject constructor(
        @AuthClient private val client: OkHttpClient
    )
    
    kotlin

    @Named (simple/quick cases)

    @Named is a built-in qualifier using a string key instead of a custom annotation:

    @Provides
    @Named("server")
    fun provideServerRetrofit(): Retrofit {
        return Retrofit.Builder().baseUrl("<https://example.com/>").build()
    }
    
    @Provides
    @Named("google_map")
    fun provideGoogleMapRetrofit(): Retrofit {
        return Retrofit.Builder().baseUrl("<https://maps.google.com/>").build()
    }
    
    kotlin
    class UserRepositoryImpl @Inject constructor(
        @Named("server") private val retrofit: Retrofit
    )
    
    class MapRepository @Inject constructor(
        @Named("google_map") private val retrofit: Retrofit
    )
    
    kotlin

    @Named vs Custom Qualifier

    @Named

    Custom @Qualifier

    Type-safe

    No

    Yes

    Typo-prone

    Yes ("server")

    No

    Readability

    OK

    Better

    Best for

    Quick/simple cases

    Production code

    @AuthClient retrofit: Retrofit is safer than @Named("server") retrofit: Retrofit because Hilt won't help if you mistype a string.

    10. Interface Injection

    Hilt cannot create an interface directly. You must provide a binding.

    Wrong:

    class Repo @Inject constructor(
        private val service: AnalyticsService // interface — Hilt can't build this
    )
    
    kotlin

    Correct:

    interface AnalyticsService
    
    class FirebaseAnalyticsService @Inject constructor() : AnalyticsService
    
    @Module
    @InstallIn(SingletonComponent::class)
    abstract class AnalyticsModule {
    
        @Binds
        abstract fun bindAnalyticsService(
            impl: FirebaseAnalyticsService
        ): AnalyticsService
    }
    
    kotlin

    11. ViewModel Injection & SavedStateHandle

    @HiltViewModel
    class LoginViewModel @Inject constructor(
        private val repository: AuthRepository
    ) : ViewModel()
    
    kotlin
    @AndroidEntryPoint
    class LoginFragment : Fragment() {
    
        private val viewModel: LoginViewModel by viewModels()
    }
    
    kotlin

    @HiltViewModel tells Hilt to create this ViewModel and resolve its dependencies. No custom ViewModelFactory needed.

    SavedStateHandle

    Hilt can inject SavedStateHandle directly into a ViewModel:

    @HiltViewModel
    class DetailViewModel @Inject constructor(
        private val repository: UserRepository,
        private val savedStateHandle: SavedStateHandle
    ) : ViewModel()
    
    kotlin

    Useful for nav arguments and restoring state after process death.

    Full Architecture Chain

    class UserRepository @Inject constructor(
        private val api: UserApi,
        private val dao: UserDao
    )
    
    class GetUsersUseCase @Inject constructor(
        private val repository: UserRepository
    )
    
    @HiltViewModel
    class UserViewModel @Inject constructor(
        private val getUsersUseCase: GetUsersUseCase
    ) : ViewModel()
    
    kotlin

    Hilt resolves the full chain: ViewModel → UseCase → Repository → API/DAO.

    12. Room Database with Hilt

    @Module
    @InstallIn(SingletonComponent::class)
    object DatabaseModule {
    
        @Singleton
        @Provides
        fun provideDatabase(
            @ApplicationContext context: Context
        ): AppDatabase {
            return Room.databaseBuilder(
                context,
                AppDatabase::class.java,
                "app_db"
            ).build()
        }
    
        @Provides
        fun provideUserDao(db: AppDatabase): UserDao {
            return db.userDao()
        }
    }
    
    kotlin

    @ApplicationContext — A built-in Hilt qualifier for the application Context. @ActivityContext — A built-in Hilt qualifier for activity Context.

    Note: The DAO does not need @Singleton because it's a lightweight wrapper — it's the database itself that must be a singleton.

    13. Retrofit / Network with Hilt

    @Module
    @InstallIn(SingletonComponent::class)
    object NetworkModule {
    
        @Singleton
        @Provides
        fun provideOkHttp(): OkHttpClient = OkHttpClient.Builder().build()
    
        @Singleton
        @Provides
        fun provideRetrofit(client: OkHttpClient): Retrofit {
            return Retrofit.Builder()
                .baseUrl("<https://example.com/>")
                .client(client)
                .build()
        }
    
        @Singleton
        @Provides
        fun provideApi(retrofit: Retrofit): UserApi {
            return retrofit.create(UserApi::class.java)
        }
    }
    
    kotlin

    All three are @Singleton because they are expensive to create, reused across the app, and should not be recreated often.

    14. Assisted Injection

    Not every value can be provided by Hilt automatically. Some values are only available at runtime — a userId, orderId, navigation argument, or any dynamic value passed from the UI.

    With Assisted Injection, part of the object is provided by Hilt, and part is supplied manually at runtime.

    class ReportGenerator @AssistedInject constructor(
        private val apiService: ApiService,   // provided by Hilt
        @Assisted private val reportId: String // provided manually at runtime
    )
    
    kotlin

    You also need a factory:

    @AssistedFactory
    interface ReportGeneratorFactory {
        fun create(reportId: String): ReportGenerator
    }
    
    kotlin

    And inject the factory instead:

    class SomeViewModel @Inject constructor(
        private val factory: ReportGeneratorFactory
    ) : ViewModel() {
    
        fun generate(id: String) {
            val generator = factory.create(id)
        }
    }
    
    kotlin

    Simple way to think about it:

    • Constructor Injection = "Everything comes from Hilt"

    • Assisted Injection = "Some things come from Hilt, some things come from me at runtime"

    Use assisted injection when: some dependencies come from Hilt, and some values are dynamic and only known while the app is running.

    A very common use case is @HiltViewModel with assisted injection for passing a dynamic ID to a ViewModel.

    15. Hilt with WorkManager

    Workers need special integration because Context and WorkerParameters come from WorkManager, not Hilt.

    @HiltWorker
    class SyncWorker @AssistedInject constructor(
        @Assisted context: Context,
        @Assisted params: WorkerParameters,
        private val repository: UserRepository
    ) : CoroutineWorker(context, params) {
    
        override suspend fun doWork(): Result {
            // repository is injected by Hilt
            return Result.success()
        }
    }
    
    kotlin

    Also add the HiltWorkerFactory to your Application:

    @HiltAndroidApp
    class MyApp : Application(), Configuration.Provider {
    
        @Inject lateinit var workerFactory: HiltWorkerFactory
    
        override fun getWorkManagerConfiguration() =
            Configuration.Builder()
                .setWorkerFactory(workerFactory)
                .build()
    }
    
    kotlin

    16. Hilt with Jetpack Compose

    In your Activity:

    @AndroidEntryPoint
    class MainActivity : ComponentActivity()
    
    kotlin

    In composables, use hiltViewModel() instead of viewModels():

    @Composable
    fun LoginScreen(
        viewModel: LoginViewModel = hiltViewModel()
    ) {
        // viewModel is Hilt-injected
    }
    
    kotlin

    Hilt still works the same way. Only the UI framework changed.

    Also works with Navigation Compose:

    composable("login") {
        val viewModel: LoginViewModel = hiltViewModel()
        LoginScreen(viewModel)
    }
    
    kotlin

    17. Entry Points (Advanced)

    Sometimes Hilt cannot inject normally into a class — for example, a ContentProvider or a class created outside Hilt's control. Use @EntryPoint for these rare cases.

    @EntryPoint
    @InstallIn(SingletonComponent::class)
    interface AnalyticsEntryPoint {
        fun analytics(): AnalyticsService
    }
    
    kotlin

    Access it via:

    val entryPoint = EntryPointAccessors.fromApplication(
        context,
        AnalyticsEntryPoint::class.java
    )
    val analytics = entryPoint.analytics()
    
    kotlin

    Use only when normal injection is not possible.


    18. Hilt Testing

    One of the biggest reasons teams use Hilt is testability — you can replace production dependencies with fakes in tests.

    @HiltAndroidTest
    class LoginActivityTest {
    
        @get:Rule
        val hiltRule = HiltAndroidRule(this)
    
        @Before
        fun setup() {
            hiltRule.inject()
        }
    }
    
    kotlin

    Replace a module in tests using @UninstallModules and a fake module:

    @UninstallModules(NetworkModule::class)
    @HiltAndroidTest
    class UserViewModelTest {
    
        @Module
        @InstallIn(SingletonComponent::class)
        object FakeNetworkModule {
    
            @Provides
            fun provideUserApi(): UserApi = FakeUserApi()
        }
    }
    
    kotlin

    You can inject fake repositories, mock APIs, and in-memory databases. This makes UI and integration tests much cleaner.

    19. Common Mistakes

    1. Forgetting @AndroidEntryPoint — Injection silently fails in Activity/Fragment.

    2. Using interface without @Binds or @Provides — Hilt cannot instantiate interfaces automatically.

    3. Wrong scope — Making a short-lived object @Singleton unnecessarily wastes memory and can cause leaks.

    4. Injecting Activity context where app context is needed — Can cause memory leaks. Use @ApplicationContext for app-level context.

    5. Using field injection everywhere — Prefer constructor injection except in Android framework classes.

    6. Cyclic dependencies — If A depends on B and B depends on A, Hilt cannot resolve the circular graph.

    7. Installing module in wrong component — If a dependency is needed in a ViewModel but the module is installed in an incompatible component, Hilt will fail at compile time.

    8. Typos with @Named — @Named("servr") and @Named("server") are different; Hilt won't warn you. Prefer custom qualifiers in production.

    20. Best Practices & Summary

    Best practices:

    • Prefer constructor injection always

    • Use @Provides only when necessary (third-party, builders)

    • Use @Binds for interface-to-implementation mappings

    • Scope carefully — use the smallest correct lifecycle

    • Keep modules organized by feature or layer (NetworkModule, DatabaseModule, RepositoryModule)

    • Avoid one giant "everything module"

    • Use qualifiers for same-type dependencies

    • Inject abstractions (interfaces), not concrete types where useful

    • Make repositories and data sources test-friendly from the start

    When to use Hilt: Use Hilt when your app has multiple layers, networking, a database, ViewModels, testing needs, or a medium-to-large codebase. For very tiny apps, manual DI may be enough. For real production apps, Hilt is almost always worth it.

    Quick Reference — Annotations Cheat Sheet

    Annotation

    Purpose

    @HiltAndroidApp

    Starts Hilt; goes on Application class

    @AndroidEntryPoint

    Enables injection in Activity/Fragment/etc

    @Inject

    Marks constructor or field for injection

    @Module

    Marks a class as a provider of dependencies

    @InstallIn

    Specifies which Hilt component a module belongs to

    @Provides

    Tells Hilt how to create an object

    @Binds

    Binds an interface to its implementation

    @Singleton

    One instance for the whole app

    @HiltViewModel

    Enables Hilt injection into a ViewModel

    @ViewModelScoped

    One instance per ViewModel

    @ActivityScoped

    One instance per Activity

    @FragmentScoped

    One instance per Fragment

    @Qualifier

    Creates a custom qualifier annotation

    @Named

    Built-in string-based qualifier

    @ApplicationContext

    Built-in qualifier for app Context

    @ActivityContext

    Built-in qualifier for Activity Context

    @AssistedInject

    For injecting with runtime parameters

    @Assisted

    Marks the runtime-provided parameter

    @AssistedFactory

    Factory interface for assisted injection

    @HiltWorker

    Enables Hilt injection in a Worker

    @EntryPoint

    Manual injection point for non-Hilt classes

    @HiltAndroidTest

    Enables Hilt in instrumented tests

    @UninstallModules

    Replaces a module in tests

    In one sentence: Hilt is a compile-time, lifecycle-aware dependency injection framework for Android that automatically builds and provides object graphs using annotations.

    android
    kotlin
    hilt
    DI
    dependency injection
    Was this helpful?

    Comments (0)

    No comments yet. Be the first to share your thoughts!

    Leave a Comment

    Get the latest updates

    Join our newsletter and never miss an article.

    By subscribing, you agree to our Privacy Policy and consent to receive updates.

    Ready to Start Learning?

    Join thousands of students building their dream careers

    Explore Courses