Dependency Injection

Decouple components, improve testability, and manage dependencies with Hilt

DI Basics Hilt Modules @Inject

Table of Contents

DI Overview

Dependency Injection (DI) provides objects with their dependencies from the outside instead of creating them internally. On Android, Hilt (built on Dagger) is the recommended DI framework — it generates boilerplate code and integrates with Activities, Fragments, and ViewModels.

Without DI With Hilt ──────────── ───────── ViewModel creates Hilt injects └── Repository └── Repository └── RetrofitClient └── Retrofit (singleton) Tight coupling Loose coupling, easy to test
BenefitDescription
TestabilitySwap real Repository with fake in unit tests
ReusabilityShare Retrofit, Room, Repository across app
MaintainabilityChange implementation in one module
LifecycleHilt scopes match Activity, ViewModel, Singleton

1Dependency Injection Basics

A dependency is any object a class needs to function — a Repository in a ViewModel, Retrofit in a Repository, or a DAO in a Repository. DI passes these in rather than letting classes construct them directly.

Problem — tight coupling

Kotlin — Without DI (bad)class TaskViewModel : ViewModel() { // ViewModel creates its own dependency — hard to test, hard to swap private val repository = TaskRepository( api = RetrofitClient.api, dao = AppDatabase.getInstance().taskDao() ) fun loadTasks() { viewModelScope.launch { repository.getTasks() } } }

Solution — constructor injection

Kotlin — With DI (good)class TaskViewModel( private val repository: TaskRepository // injected from outside ) : ViewModel() { fun loadTasks() { viewModelScope.launch { repository.getTasks() } } } class TaskRepository( private val api: ApiService, private val dao: TaskDao ) { suspend fun getTasks(): Result<List<Task>> { /* ... */ } }

Three types of injection

TypeHowPreferred?
ConstructorDependencies passed via constructorYes — Hilt default
Field@Inject lateinit var on fieldAndroid framework classes only
Method@Inject on methodRare — use constructor instead

DI in the Android architecture

@HiltViewModel TaskViewModel ← injects TaskRepository ← injects ApiService + TaskDao ← provided by @Module classes

2Hilt Setup

Hilt simplifies Dagger for Android. Add plugins and dependencies, annotate your Application and Activities, and Hilt generates the dependency graph at compile time.

Project-level Gradle

Kotlin — build.gradle.kts (project root)plugins { id("com.google.dagger.hilt.android") version "2.50" apply false }

App-level Gradle

Kotlin — app/build.gradle.ktsplugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("com.google.dagger.hilt.android") id("kotlin-kapt") } dependencies { implementation("com.google.dagger:hilt-android:2.50") kapt("com.google.dagger:hilt-android-compiler:2.50") }

Application class

Kotlin — MyApplication.kt@HiltAndroidApp class MyApplication : Application()
XML — AndroidManifest.xml<application android:name=".MyApplication" ... >

Annotate Activity and Fragment

Kotlin — MainActivity.kt@AndroidEntryPoint class MainActivity : AppCompatActivity() { private val viewModel: TaskViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) } } @AndroidEntryPoint class TaskFragment : Fragment() { private val viewModel: TaskViewModel by viewModels() }

Hilt setup checklist

  1. Add Hilt plugin and dependencies (with kapt)
  2. Create @HiltAndroidApp Application class
  3. Register Application in AndroidManifest
  4. Add @AndroidEntryPoint to Activities and Fragments
  5. Create @Module classes for third-party objects
  6. Use @HiltViewModel on ViewModels

3Modules

Hilt modules tell Hilt how to create objects you don't own — Retrofit instances, OkHttp clients, Room databases, and third-party library classes. Use @Provides in a @Module class.

Network module

Kotlin — NetworkModule.kt@Module @InstallIn(SingletonComponent::class) object NetworkModule { private const val BASE_URL = "https://jsonplaceholder.typicode.com/" @Provides @Singleton fun provideOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() .addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }) .connectTimeout(30, TimeUnit.SECONDS) .build() } @Provides @Singleton fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { return Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build() } @Provides @Singleton fun provideApiService(retrofit: Retrofit): ApiService { return retrofit.create(ApiService::class.java) } }

Database module

Kotlin — DatabaseModule.kt@Module @InstallIn(SingletonComponent::class) object DatabaseModule { @Provides @Singleton fun provideDatabase(@ApplicationContext context: Context): AppDatabase { return Room.databaseBuilder( context, AppDatabase::class.java, "app_database" ).build() } @Provides fun provideTaskDao(database: AppDatabase): TaskDao { return database.taskDao() } }

Hilt components and scopes

ComponentScope AnnotationLifetime
SingletonComponent@SingletonApp lifetime
ActivityRetainedComponent@ActivityRetainedScopedViewModel lifetime
ActivityComponent@ActivityScopedActivity lifetime
ViewModelComponent@ViewModelScopedViewModel lifetime
FragmentComponent@FragmentScopedFragment lifetime

@Binds for interfaces

Kotlin — Bind interface to implementation// Use @Binds when you have an interface + implementation (more efficient than @Provides) @Module @InstallIn(SingletonComponent::class) abstract class RepositoryModule { @Binds @Singleton abstract fun bindTaskRepository( impl: TaskRepositoryImpl ): TaskRepository } interface TaskRepository { suspend fun getTasks(): Result<List<Task>> } class TaskRepositoryImpl @Inject constructor( private val api: ApiService, private val dao: TaskDao ) : TaskRepository { override suspend fun getTasks(): Result<List<Task>> { /* ... */ } }

4Inject Annotation

The @Inject annotation tells Hilt how to create an instance of a class. Mark constructors for automatic injection, or use field injection for Android framework classes that Hilt cannot construct.

Constructor injection — preferred

Kotlin — @Inject constructorclass TaskRepository @Inject constructor( private val api: ApiService, private val dao: TaskDao ) { suspend fun getTasks(): Result<List<Task>> { return try { val remote = api.getTasks() dao.insertAll(remote) Result.success(remote) } catch (e: Exception) { Result.failure(e) } } }

@HiltViewModel

Kotlin — ViewModel with @Inject@HiltViewModel class TaskViewModel @Inject constructor( private val repository: TaskRepository ) : ViewModel() { private val _tasks = MutableStateFlow<List<Task>>(emptyList()) val tasks: StateFlow<List<Task>> = _tasks.asStateFlow() init { loadTasks() } fun loadTasks() { viewModelScope.launch { repository.getTasks() .onSuccess { _tasks.value = it } } } } // In @AndroidEntryPoint Activity: private val viewModel: TaskViewModel by viewModels()

Field injection — Android classes

Kotlin — Field injection in Activity@AndroidEntryPoint class MainActivity : AppCompatActivity() { @Inject lateinit var analyticsTracker: AnalyticsTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // analyticsTracker is injected by Hilt before onCreate analyticsTracker.logScreenView("MainActivity") } }

@ApplicationContext and @ActivityContext

Kotlin — Inject Context safelyclass PreferencesManager @Inject constructor( @ApplicationContext private val context: Context ) { private val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE) fun saveToken(token: String) { prefs.edit().putString("auth_token", token).apply() } } class FragmentHelper @Inject constructor( @ActivityContext private val context: Context ) { fun showDialog(message: String) { AlertDialog.Builder(context).setMessage(message).show() } }

What Hilt can and cannot inject

Class TypeInjection Method
Your Repository, UseCase@Inject constructor
ViewModel@HiltViewModel @Inject constructor
Activity, Fragment@AndroidEntryPoint + field injection
Retrofit, Room, OkHttp@Module @Provides
Interface → Implementation@Binds in abstract module

5Singleton Pattern

A singleton ensures only one instance of a class exists app-wide. Hilt's @Singleton scope replaces manual double-checked locking — Retrofit, Room, and Repository instances are typically singletons.

Manual singleton vs Hilt

Kotlin — Manual singleton (avoid with Hilt)// Old pattern — boilerplate, hard to test class RetrofitClient private constructor() { companion object { @Volatile private var instance: Retrofit? = null fun getInstance(): Retrofit { return instance ?: synchronized(this) { instance ?: buildRetrofit().also { instance = it } } } } }
Kotlin — Hilt @Singleton (preferred)@Module @InstallIn(SingletonComponent::class) object AppModule { @Provides @Singleton fun provideRetrofit(): Retrofit { /* ... */ } @Provides @Singleton fun provideApiService(retrofit: Retrofit): ApiService { return retrofit.create(ApiService::class.java) } } // Hilt creates one instance for the entire app lifetime class TaskRepository @Inject constructor( private val api: ApiService // same singleton everywhere )

@Singleton on @Inject class

Kotlin — Singleton class with @Inject@Singleton class UserSessionManager @Inject constructor( @ApplicationContext private val context: Context ) { private val prefs = context.getSharedPreferences("session", Context.MODE_PRIVATE) fun saveUserId(id: Int) = prefs.edit().putInt("user_id", id).apply() fun getUserId(): Int = prefs.getInt("user_id", -1) fun clearSession() = prefs.edit().clear().apply() }

Scoped vs singleton

ScopeInstancesExample
@SingletonOne per appRetrofit, Room, Repository
@ActivityScopedOne per ActivityActivity-specific helper
@ViewModelScopedOne per ViewModelViewModel-assisted factory
No scope (unscoped)New instance each injectionLightweight, stateless objects

Complete DI graph example

@HiltAndroidApp MyApplication └── SingletonComponent ├── NetworkModule → OkHttp, Retrofit, ApiService (@Singleton) ├── DatabaseModule → AppDatabase, TaskDao (@Singleton) ├── TaskRepository @Inject constructor └── TaskViewModel @HiltViewModel @Inject constructor └── injected into @AndroidEntryPoint MainActivity

6Hands-On Exercises

1

Refactor a ViewModel from manual dependency creation to constructor injection. Understand the DI benefit.

2

Complete Hilt setup: plugin, @HiltAndroidApp, manifest, and @AndroidEntryPoint on Activity.

3

Create a NetworkModule with @Provides for OkHttp, Retrofit, and ApiService as singletons.

4

Add a DatabaseModule providing Room database and DAO. Inject DAO into Repository.

5

Annotate Repository with @Inject constructor and ViewModel with @HiltViewModel. Use by viewModels().

6

Verify singleton behavior — log instance hash codes; Retrofit and Repository should be the same instance app-wide.

7

Bonus: Use @Binds to bind a TaskRepository interface to TaskRepositoryImpl for easier testing.

Dependency Injection MCQ Practice

10 Basic MCQs 10 Advanced MCQs

10 Basic Dependency Injection MCQs

1

Dependency Injection means?

AAll static fields
BDependencies provided externally not created inside class
CNo constructors
DGradle only
Explanation: DI inverts control — caller/container supplies dependencies.
2

Hilt is built on?

ARetrofit
BRoom
CDagger
DCompose
Explanation: Hilt is Google's recommended DI wrapper around Dagger for Android.
3

@HiltAndroidApp annotates?

AManifest
BActivity only
CFragment only
DApplication class
Explanation: Triggers Hilt code generation for the app component tree.
4

@AndroidEntryPoint used on?

AActivity, Fragment, Service, etc.
BData classes only
CRoom entities
DLayouts
Explanation: Enables member injection in Android entry points.
5

@Module in Hilt?

ASQL table
BClass providing dependency bindings
CMenu XML
DIntent filter
Explanation: Modules tell Hilt how to construct types.
6

@Provides method?

AInflates layout
BDeletes dependency
CSupplies instance of a type
DStarts coroutine
Explanation: Factory method in @Module returning dependency.
7

@Inject constructor?

AProGuard rule
BDeprecated
CManifest only
DRequests constructor injection
Explanation: Hilt/Dagger injects parameters automatically.
8

@Singleton in Hilt?

AOne instance per app component scope
BOne per Activity always
CGlobal JVM static
DThread local
Explanation: Scoped to SingletonComponent — app-wide single instance.
9

@HiltViewModel?

ALiveData wrapper
BViewModel with injected constructor via Hilt
CRoom DAO
DService
Explanation: Use with by viewModels() in @AndroidEntryPoint Activity.
10

DI improves testability by?

ABlocking network
BRemoving interfaces
CSwap real deps with fakes/mocks
DMore statics
Explanation: Inject test doubles into class under test.

10 Advanced Dependency Injection MCQs

11

@Binds vs @Provides?

ASame
B@Binds abstract method for interface impl; @Provides factory method
C@Binds for Retrofit
D@Provides deprecated
Explanation: @Binds more efficient when binding interface to implementation.
12

Qualifiers like @Named?

ALayout id
BSQL column name
CDisambiguate multiple bindings of same type
DPermission
Explanation: @Named("api") Retrofit vs @Named("cdn") Retrofit.
13

@InstallIn(SingletonComponent::class)?

ANo scope
BActivity scope only
CFragment only
DModule lives in app singleton scope
Explanation: Choose component matching lifecycle: ActivityComponent, ViewModelComponent, etc.
14

AssistedInject used for?

AFactory-created objects with runtime + injected params
BRoom only
CCompose
DGPS
Explanation: ViewModel with SavedStateHandle uses @AssistedInject pattern.
15

Component hierarchy in Hilt?

AFlat only
BSingleton → ActivityRetained → Activity → Fragment
CNo hierarchy
DGradle modules
Explanation: Scopes nest — child can access parent scoped deps.
16

Manual Dagger vs Hilt?

AHilt no codegen
BManual always better
CHilt generates Android boilerplate and standard components
DIdentical
Explanation: Hilt reduces setup for Activities, ViewModels, WorkManager.
17

@EntryPoint interface?

ALint
BSQL entry
CManifest
DAccess Hilt deps from non-@AndroidEntryPoint class
Explanation: EntryPointAccessors.fromApplication for ContentProvider etc.
18

Testing with Hilt?

A@HiltAndroidTest + test modules replacing bindings
BCannot test
CEspresso only
DNo fakes
Explanation: Uninstall/replace modules with @TestInstallIn for fakes.
19

Lazy injection?

AEager always
BLazy defers creation until first get()
CMain thread only
DBanned
Explanation: Useful for expensive deps not always needed.
20

Provider vs Lazy?

AProvider singleton
BSame
CProvider creates new instance each get() typically; Lazy caches first
DLazy new each time
Explanation: Provider for new instance per injection site if unscoped.
Click an option to select, then check answers.

Dependency Injection Interview Q&A

15 topic-focused questions for interviews and revision.

1What is dependency injection?easy
Answer: Supplying a class's dependencies from outside rather than constructing them internally — improves modularity and testing.
2Why use Hilt on Android?easy
Answer: Reduces Dagger boilerplate, standard scopes for Activity/Fragment/ViewModel, compile-time safety, Google recommended.
3Hilt setup steps.medium
Answer: Apply hilt plugin, @HiltAndroidApp on Application, @AndroidEntryPoint on Activity, @HiltViewModel on ViewModel, create @Module classes.
4@Provides example for Retrofit.medium
Answer: @Module @InstallIn(SingletonComponent::class) object NetworkModule { @Provides @Singleton fun provideRetrofit(): Retrofit = ... }
5@Inject constructor example.easy
Answer: class UserRepository @Inject constructor(private val api: ApiService, private val dao: UserDao)
6Difference @Singleton and unscoped.medium
Answer: Singleton one instance per component scope; unscoped may create new instance per injection.
7@Binds example.hard
Answer: @Binds abstract fun bindRepo(impl: UserRepositoryImpl): UserRepository in abstract @Module.
8Inject ViewModel in Activity.easy
Answer: @AndroidEntryPoint Activity; private val vm: MyViewModel by viewModels()
9Why avoid ServiceLocator pattern?medium
Answer: Hidden dependencies, global state, harder testing vs explicit constructor injection.
10Module scopes choice.medium
Answer: Network/database SingletonComponent; Activity-specific deps ActivityComponent.
11Testing repository with fake API.hard
Answer: @TestInstallIn replaces NetworkModule with FakeApiModule providing mock ApiService.
12Multiple Retrofit instances.hard
Answer: Use @Qualifier custom annotation or @Named on @Provides methods and injection sites.
13Hilt and WorkManager.hard
Answer: @HiltWorker + AssistedInject for Worker with HiltWorkerFactory in Application.
14Common Hilt error missing @AndroidEntryPoint.easy
Answer: Injection fields null in Activity — must annotate entry point class.
15DI in multi-module project.hard
Answer: Each module can have @Module; app module aggregates; api vs impl module pattern.