Dependency Injection with Hilt

Reviewed & published by Brayan K

Hilt is Google's opinionated dependency injection library for Android, built on Dagger. It wires your object graph at compile time so classes receive their dependencies instead of constructing them.

Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end you'll set up @HiltAndroidApp, use @Inject constructors, and write a @Module.

What You'll Learn in This Lesson

1️⃣ Enabling Hilt

Annotate your Application with @HiltAndroidApp to create the root container, then mark each Android class that should receive dependencies with @AndroidEntryPoint.

import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import dagger.hilt.android.AndroidEntryPoint
import androidx.appcompat.app.AppCompatActivity

// 1) Enable Hilt for the whole app. This creates the root container.
@HiltAndroidApp
class MyApp : Application()

// 2) Mark Android classes that should RECEIVE injected dependencies.
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    // Hilt will inject these fields for you.
}

These two annotations connect your Android entry points to the dependency graph Hilt generates.

2️⃣ @Inject Constructors

For classes you own, an @Inject constructor tells Hilt how to build them — and Hilt supplies their dependencies automatically. Add @Singleton for a single shared instance.

import javax.inject.Inject
import javax.inject.Singleton

// An @Inject constructor tells Hilt HOW to build this class
// and what it depends on.
class UserRepository @Inject constructor(
    private val api: ApiService,   // Hilt supplies this automatically
) {
    suspend fun load() = api.fetchUser()
}

// @Singleton => one shared instance for the whole app.
@Singleton
class AnalyticsTracker @Inject constructor() {
    fun track(event: String) { /* ... */ }
}

UserRepository never builds its own ApiService — Hilt hands it over.

3️⃣ Modules with @Provides / @Binds

When you can't annotate a constructor — interfaces, library types — a @Module supplies the instructions. @Provides runs a factory method; @Binds maps an interface to an implementation. @InstallIn picks the component and scope.

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

// You cannot put @Inject on an interface or a library type,
// so a @Module supplies the instructions instead.
@Module
@InstallIn(SingletonComponent::class)  // app-wide lifetime
object NetworkModule {

    @Provides
    @Singleton
    fun provideApi(): ApiService =
        Retrofit.Builder()
            .baseUrl("https://api.example.com")
            .build()
            .create(ApiService::class.java)
}

Your turn. Replace the TODOs, then compare.

// 🎯 YOUR TURN — replace each TODO.
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject

// 1) Enable Hilt on the Application class.
TODO                       // 👉 @HiltAndroidApp
class MyApp : Application()

// 2) Let Hilt build this class by annotating the constructor.
class GreetingService TODO constructor() {   // 👉 @Inject
    fun greet() = "Hello from Hilt!"
}

Mini-Challenge: Inject a ViewModel

Bind an interface, inject it into a @HiltViewModel, and retrieve it with hiltViewModel().

// 🎯 MINI-CHALLENGE: inject a ViewModel.
//
// 1. Create an interface Clock { fun now(): Long }.
// 2. Provide a real implementation with a @Module:
//    - @Binds (or @Provides) it into SingletonComponent.
// 3. Build a @HiltViewModel class HomeViewModel
//    @Inject constructor(private val clock: Clock).
// 4. In a composable, obtain it with hiltViewModel().
//
// ✅ The ViewModel receives a Clock without ever calling 'new'.

// your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Hilt

ConceptHilt API
Enable Hilt@HiltAndroidApp class MyApp
Injectable entry point@AndroidEntryPoint
Buildable class@Inject constructor(...)
Provide a type@Module @Provides / @Binds
Choose scope@InstallIn(...), @Singleton
Inject a ViewModelhiltViewModel()

🎉 Lesson Complete!

Practice quiz

Which dependency injection framework is Hilt built on top of?

  • Dagger
  • Guice
  • Spring
  • Koin

Answer: Dagger. Hilt is a layer over Dagger that adds Android-specific, opinionated conventions to reduce boilerplate.

Which annotation goes on your Application subclass to enable Hilt?

  • @HiltApp
  • @EnableHilt
  • @HiltAndroidApp
  • @InjectApp

Answer: @HiltAndroidApp. @HiltAndroidApp triggers Hilt's code generation and creates the application-level dependency container.

Which annotation lets an Activity or Fragment receive injected dependencies?

  • @HiltView
  • @AndroidEntryPoint
  • @Inject
  • @Component

Answer: @AndroidEntryPoint. @AndroidEntryPoint marks an Android class (Activity, Fragment, Service) as a target for field/member injection.

When you cannot annotate a constructor (e.g. an interface or library type), what do you use?

  • A second Application class
  • Reflection at runtime
  • Nothing — Hilt guesses
  • A @Module with @Provides or @Binds

Answer: A @Module with @Provides or @Binds. A @Module supplies instructions: @Provides for a factory method, @Binds to map an interface to its implementation.

What is the difference between @Provides and @Binds?

  • @Provides runs a method body to create the instance; @Binds maps an interface to an implementation with no body
  • They are identical
  • @Provides only works for Singletons
  • @Binds is for primitives only

Answer: @Provides runs a method body to create the instance; @Binds maps an interface to an implementation with no body. @Provides executes a method to build the object; @Binds is an abstract mapping from an interface to a concrete type, which Hilt resolves more cheaply.

How do you tell Hilt how to construct a class you own?

  • Add it to a list in the Application
  • Annotate its constructor with @Inject
  • Implement an interface
  • Register it in the manifest

Answer: Annotate its constructor with @Inject. An @Inject constructor tells Hilt exactly how to build the class and what its dependencies are.

What does @InstallIn specify on a module?

  • The package name
  • The minimum SDK
  • The Gradle module
  • Which Hilt component (and thus lifecycle/scope) the bindings live in

Answer: Which Hilt component (and thus lifecycle/scope) the bindings live in. @InstallIn(SingletonComponent::class), for example, installs the bindings into a component, tying their lifetime to it.

Which scope makes a single shared instance live for the entire app?

  • @ViewModelScoped
  • @ActivityScoped
  • @Singleton
  • @FragmentScoped

Answer: @Singleton. @Singleton in the SingletonComponent yields one instance for the whole application lifetime.

How do you obtain a Hilt-injected ViewModel inside a composable?

  • viewModel()
  • hiltViewModel()
  • MyViewModel.get()
  • new MyViewModel()

Answer: hiltViewModel(). hiltViewModel() retrieves a ViewModel whose dependencies are provided by Hilt.

Which is described as a lighter-weight alternative to Hilt/Dagger?

  • Koin
  • Glide
  • OkHttp
  • Retrofit

Answer: Koin. Koin is a popular service-locator-style DI library that avoids annotation processing, trading some compile-time safety for simplicity.

Continue this course

Frequently asked questions

What is dependency injection and why should I care?

Dependency injection (DI) is a simple idea: instead of a class creating the objects it needs, those objects are handed to it from the outside. A ViewModel that needs a repository doesn't build one with 'new'; it receives one through its constructor. This decouples object creation from object use, which buys you three big wins: testability (inject a fake repository in tests), reuse (the same class works with different implementations), and a single, clear place where your object graph is wired together rather than scattered through the codebase.

How does Hilt relate to Dagger?

Dagger is a powerful, fast, compile-time DI framework, but it has a steep learning curve and a lot of boilerplate on Android. Hilt is Google's opinionated layer on top of Dagger that bakes in the standard Android components and lifecycles for you. You annotate your Application with @HiltAndroidApp, mark Activities/Fragments with @AndroidEntryPoint, and Hilt generates the Dagger components and wiring automatically. You get Dagger's compile-time safety and performance with far less ceremony.

When do I use @Inject versus a @Module?

Use an @Inject constructor whenever you own the class and can annotate it — that's the simplest path and tells Hilt exactly how to build it. Use a @Module when you can't annotate the constructor: interfaces, classes from libraries (like a Retrofit service), or objects that need custom construction logic. Inside a module, @Provides runs a method body to create the instance, while @Binds is a lightweight abstract mapping from an interface to its implementation. Every module declares @InstallIn to say which component — and therefore which scope and lifetime — its bindings belong to.

What about scopes, hiltViewModel, and lighter alternatives?

Scopes control how long an instance lives and how widely it is shared. @Singleton in the SingletonComponent gives one instance for the whole app; @ViewModelScoped ties an instance to a ViewModel's lifetime; there are activity- and fragment-scoped variants too. To get a Hilt-managed ViewModel in Compose you call hiltViewModel(), which resolves all its constructor dependencies for you. If Hilt feels heavy for a small project, Koin is a popular lighter-weight alternative: it uses a Kotlin DSL and a service-locator approach with no annotation processing, trading a little compile-time safety for faster setup.