Dagger / Hilt: What It Is, DI and Application

Author: IT Sectr Published: 2026-05-03 Reading time: 9 min

Dagger is a dependency injection framework for Java and Kotlin that generates DI code at compile time through annotation processing. Hilt is a wrapper around Dagger for Android that simplifies component setup and lifecycle management. According to Google, 2025, Hilt is used in more than 70% of Android apps from the Google Play Top-100, supporting Activity, Fragment, ViewModel and Service through predefined components. Both frameworks provide compile-time dependency graph verification, eliminating runtime injection errors.

Key Takeaways

  • Dagger — compile-time DI framework with code generation via @Module, @Provides, @Component annotations
  • Hilt — Android wrapper simplifying Dagger through @HiltAndroidApp, @AndroidEntryPoint, @HiltViewModel
  • Component — dependency graph connecting Module with Inject targets through proxy methods
  • Scope — @Singleton, @ViewModelScoped, @ActivityScoped manage the lifetime of injected objects
  • Hilt supports multi-module projects via @InstallIn for isolated dependency graphs

What Is Dagger / Hilt?

Dagger is a dependency injection framework with compile-time code generation. Originally developed at Square and later transferred to Google, Dagger uses the Java APT annotation processor to analyze the dependency graph and generate factory classes. Unlike runtime-DI (Guice, Koin), Dagger does not use reflection — all code is created at compile time, ensuring maximum runtime performance and error detection at build time.

Hilt is a Google library built on top of Dagger and optimized for Android. Hilt provides predefined components corresponding to the Android component lifecycle: @SingletonComponent for Application, @ActivityComponent for Activity, @FragmentComponent for Fragment, @ViewModelComponent for ViewModel. This eliminates the routine Component and Module configuration required in pure Dagger. Hilt also generates the dependency graph for each Android component automatically via @AndroidEntryPoint.

According to Google I/O 2024, Hilt is the recommended solution for DI in Android applications written in Kotlin. Jetpack libraries (Navigation, Room, WorkManager) have built-in integration with Hilt via @HiltViewModel and @HiltWorker. In projects not using Android (pure Java/Kotlin libraries, server applications), pure Dagger is used without the Hilt wrapper.

The Problem of Manual Dependency Injection

Without a DI framework, developers manually create objects through constructors or factories, passing dependencies along the chain. Each new requirement means changing the signatures of all constructors in the chain. Dagger automates this process: you just declare which type is needed (@Inject constructor), and Dagger creates the dependency graph, resolving all nested types. When dependencies change, Dagger updates the generated code automatically — it is impossible to make a mistake in the chain.

Principles of Dependency Injection

Dependency Injection is a pattern where an object receives its dependencies from the outside rather than creating them itself. DI implements the Inversion of Control (IoC) principle: a class is not responsible for creating its own dependencies but declares them through a constructor, method, or field. Constructor injection is considered the most preferable because it guarantees that the object is created in a valid state.

Injection TypeDagger SyntaxWhen to Use
Constructor injection@Inject constructorPrimary method — for all custom classes
Field injection@Inject lateinit varOnly for Android components (Activity, Fragment)
Method injection@Inject fun bind()For post-construct initialization

Advantages of Compile-time DI

The main advantages of DI include testability (dependencies can be replaced with mock objects), loose coupling (classes depend on interfaces, not implementations), and explicit lifecycle management of objects through scopes. Dagger automatically guarantees that an object is created once within its scope and destroyed when the scope is exited.

Dagger Architecture: Component, Module, Provides

Component is the central element of the Dagger dependency graph. It is an interface annotated with @Component that describes the bridge between Module and injection targets. Dagger generates the Component implementation (e.g. DaggerAppComponent) at compile time. The Component determines which types are available for injection through abstract methods returning the required types or through inject methods that accept an object for field injection.

kotlin
// Module: provides dependencies that Dagger cannot create on its own
@Module
class NetworkModule {
    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .build()
    }

    @Provides
    @Singleton
    fun provideApiService(client: OkHttpClient): ApiService {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

// Component: connects Module and Injection targets
@Component(modules = [NetworkModule::class])
interface AppComponent {
    fun inject(activity: MainActivity)
    fun getApiService(): ApiService
}

@Module is a class containing methods with @Provides that return instances of dependencies. Module is used for types that Dagger cannot create automatically: third-party libraries (OkHttp, Retrofit), objects with constructor parameters, interfaces with implementation selection. @Binds is an alternative to @Provides for cases when a method returns an interface and accepts a single implementation: Dagger generates a direct cast without calling the method.

@Scope defines the lifetime of an object in the dependency graph. @Singleton — the object is created once for the entire application. @ActivityScoped — the object lives as long as the Activity lives. @FragmentScoped — as long as the Fragment lives. Without a scope, Dagger creates a new instance on every injection. @Reusable — a scope for objects that do not have to be singletons but are expensive to create — Dagger may cache the instance but does not guarantee it.

Hilt for Android: @HiltAndroidApp and @AndroidEntryPoint

Hilt simplifies Dagger configuration for Android through predefined components and automatic base graph generation. The @HiltAndroidApp annotation on the Application class triggers Hilt component generation. Without this annotation, Hilt does not work — it is mandatory for any Android application using Hilt. @HiltAndroidApp creates the parent SingletonComponent component, from which all other application components inherit.

kotlin
@HiltAndroidApp
class MyApplication : Application()

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject lateinit var apiService: ApiService

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // apiService already injected before onCreate call
    }
}

@Module
@InstallIn(SingletonComponent::class)
class AppModule {
    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext ctx: Context): AppDatabase {
        return Room.databaseBuilder(ctx, AppDatabase::class.java, "app.db").build()
    }
}

@AndroidEntryPoint is an annotation for Activity, Fragment, Service, BroadcastReceiver and View. It generates a Hilt component for each type: @AndroidEntryPoint on an Activity creates an ActivityComponent that inherits from SingletonComponent. The child component automatically receives all parent dependencies. Field injection with @Inject lateinit var is only available in classes annotated with @AndroidEntryPoint — in regular classes, constructor injection is used.

@InstallIn specifies which Hilt component a module is installed into. NetworkModule with @InstallIn(SingletonComponent::class) is available throughout the application. A Module with @InstallIn(ActivityComponent::class) is only available in Activity. This isolates dependency graphs: Activity-specific modules are not visible in Fragment and ViewModel, preventing accidental use of invalid dependencies. @ApplicationContext is a built-in Hilt qualifier for getting the application Context.

Qualifier: @Named and Custom Qualifiers

When two different implementations of the same interface need to be injected, qualifiers are used. Hilt supports @Named for string identifiers and custom annotations with @Qualifier. For example, @Named("baseUrl") and @Named("imageBaseUrl") for different string configurations. Custom qualifiers are preferable to @Named because of compile-time checking — an incorrect string name will not be detected until runtime.

Hilt ViewModel: @HiltViewModel and @Inject constructor

@HiltViewModel is an annotation that replaces the manual ViewModelProvider.Factory. A class annotated with @HiltViewModel with @Inject constructor automatically receives all dependencies through Dagger. Hilt generates a ViewModelFactory used by Jetpack ViewModelProvider. Without Hilt, the developer must write the factory manually, passing each parameter from the Activity or fragment.

kotlin
@HiltViewModel
class MainViewModel
    @Inject constructor(
        private val apiService: ApiService,
        private val database: AppDatabase
    ) : ViewModel() {

    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users: StateFlow<List<User>> = _users.asStateFlow()

    fun loadUsers() {
        viewModelScope.launch {
            _users.value = apiService.getUsers()
        }
    }
}

// In Activity — Hilt automatically creates ViewModel
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels()
}

ViewModelScoped is a Hilt scope for dependencies that live as long as the ViewModel lives. If two ViewModels of the same type inject the same @ViewModelScoped dependency, each gets its own instance. This distinguishes @ViewModelScoped from @ActivityScoped, where one Activity gets one instance for all fragments. For ViewModel-specific dependencies (e.g. SavedStateHandle), @HiltViewModel with @Inject constructor(savedStateHandle: SavedStateHandle) is used.

Hilt supports assisted injection through the Hilt Extensions library. Assisted injection allows passing parameters to the constructor at injection time when some dependencies are only known at runtime (e.g. user ID from an intent). For assisted injection, @AssistedInject is used in combination with @Assisted parameters. Hilt generates an AssistedFactory that can be injected in the standard way.

Dagger vs Hilt: Comparison and Migration

Pure Dagger requires manual creation of Component, defining scopes and configuring injection into each Android component. The developer creates AppComponent, ActivityComponent, FragmentComponent, and manages their relationships through @Subcomponent. This approach gives maximum control but requires significant boilerplate code. Dagger is used in large projects that require a non-standard DI architecture, or in non-Android Java/Kotlin projects.

Hilt automates the boilerplate: one @HiltAndroidApp, one @AndroidEntryPoint for each component, predefined scopes. Hilt is recommended by Google for all new Android projects. Migration from Dagger to Hilt includes replacing Component with @InstallIn, replacing @Subcomponent with predefined Hilt components, and replacing the manual ViewModelProvider.Factory with @HiltViewModel. Most @Module classes are migrated with the addition of @InstallIn without changing @Provides methods.

CharacteristicDaggerHilt
SetupManual: Component, Subcomponent, BuilderAuto: @HiltAndroidApp, @AndroidEntryPoint
Android componentsNo predefined12+ built-in components
ViewModelManual factory@HiltViewModel + @Inject constructor
MultimoduleVia @Component(dependencies)Via @InstallIn + aggregation
ComplexityHigh — experience neededLow — intuitively understandable
FlexibilityMaximumStandard (covers 95% of scenarios)

Limitations of Hilt: the library only supports Android (not suitable for pure server-side Java projects), imposes a certain component structure (difficult to override), and adds a dependency on android.hilt:hilt-navigation-compose for Jetpack Compose projects. For Compose applications, Hilt provides @HiltViewModel accessible in Composable through hiltViewModel() — without manual ViewModel provision from Activity.

Frequently Asked Questions

What is the difference between Dagger and Hilt?

Dagger is a basic compile-time DI framework with manual Component and Module configuration. Hilt is an Android wrapper that automates component creation and integration with the lifecycle of Activity, Fragment, ViewModel, Service and BroadcastReceiver.

Why is @HiltAndroidApp needed?

@HiltAndroidApp enables Hilt component generation for Application. Without this annotation, Hilt cannot create the base SingletonComponent from which all ActivityComponent, FragmentComponent and ViewModelComponent inherit. The annotation is mandatory for any Hilt project.

How does Hilt work with Jetpack Navigation?

Hilt Navigation provides @HiltViewModel for ViewModel in NavBackStackEntry and hiltNavGraphViewModels() for scoping ViewModel within the navigation graph. The android.hilt:hilt-navigation-fragment library automatically creates a ViewModel for each NavBackStackEntry.

How to inject Context in Hilt?

Use @ApplicationContext for the application context or @ActivityContext for the Activity context. Hilt provides these qualifiers built-in in the android.hilt:hilt-android library. @ActivityContext is only available in modules installed in ActivityComponent.

What is @Binds and when to use it?

@Binds is an efficient alternative to @Provides when a method accepts exactly one parameter and returns its type as an interface. @Binds generates a direct cast without calling the method, reducing the amount of generated code and improving injection performance.

Summary

  • Dagger — compile-time DI framework with @Module, @Provides, @Component annotations and code generation via APT
  • Hilt — Android wrapper over Dagger with @HiltAndroidApp, @AndroidEntryPoint, @InstallIn and predefined components
  • Component manages the dependency graph, Module provides third-party classes, Provides provides object factories
  • Scope (@Singleton, @ViewModelScoped, @ActivityScoped) defines the object lifetime in the Dagger graph
  • @HiltViewModel automates ViewModel creation, eliminating manual ViewModelProvider.Factory factories
  • @InstallIn isolates modules by components, preventing dependency leakage between application layers
  • Hilt is recommended by Google for all new Android projects, Dagger for non-Android and custom DI architectures

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also