Mastering Android
Dependency
Injection – 2025
Interview Edition
🧪 Hilt • Koin • Manual DI • Testing • Scoping
🧱 Modular Architecture
💉 Constructor Injection
⚙️ Lifecycle Scopes
🔄 ViewModel Integration
Elevate code quality and maintainability with DI
techniques used across modern Android apps,
scalable fintech platforms, and enterprise-grade
architectures
Q1. What is Dependency 1/16
Injection (DI)? Why do we use
it?
A:
Dependency Injection is a design pattern where objects
receive their dependencies from external sources rather
than creating them internally.
Benefits:
Loose coupling between classes
Easier unit testing (can inject mocks)
Promotes modularity and clean architecture
Reusability and flexibility in app design
Better testability - isolate classes during testing
Follows SOLID principles (Dependency Inversion)
Quick example: Instead of UserRepository creating its own
ApiService, the DI framework injects it via constructor
using @Inject.
2/16
Q2. What are the types of DI?
A:
1. Constructor Injection – Preferred in most cases
(dependencies passed in the constructor) ✅
Immutable dependencies, fail-fast if missing
2. Field Injection – Injects directly into class fields (used
with @Inject annotations in Hilt/Dagger) ⚠️ Can't use
dependencies in constructor
3. Method Injection – Dependencies passed via method
calls (rare in Android, used for optional dependencies)
Best Practice: Use Constructor Injection for required
dependencies, Field Injection only when constructor
injection isn't possible (like Activities/Fragments).
Q3. What are the common 3/16
DI frameworks used in
Android?
A:
Hilt (Google-supported) - Current recommendation, built
on Dagger
Koin (Kotlin-native, DSL-based) - Lightweight, easy setup
Dagger (compile-time, feature-rich) - Enterprise apps,
complex graphs
Manual DI (no library) - Small projects, learning
When to choose:
Hilt: New projects, Jetpack integration needed
Koin: Kotlin-first teams, quick prototypes
Dagger: Large apps, performance critical
Manual: Simple apps, educational purposes
Q4. What is Hilt and why 4/16
should we use it?
A:
Hilt is Google's recommended dependency injection library
built on top of Dagger. It simplifies setup and integrates
seamlessly with Android Jetpack libraries.
Key Advantages:
Less boilerplate - Reduces Dagger's complexity by 70%
Lifecycle-aware scoping - Automatic cleanup with Android
components
Easy ViewModel injection - Built-in support with
@HiltViewModel
Better tooling and testing - IDE support, simplified test
setup
Jetpack integration - WorkManager, Navigation, Compose
ready
Compile-time safety - Catches DI errors at build time, not
runtime
Why choose Hilt: Google's current standard for new Android
projects, active development, and extensive
documentation.
Q5. Key annotations used in 5/16
Hilt?
A:
Setup:
@HiltAndroidApp – Entry point in Application class
@AndroidEntryPoint – Annotate Activities, Fragments,
Services, etc.
Injection:
@Inject – Inject constructor or field
@HiltViewModel – For ViewModel injection with lifecycle
awareness
Dependency Provision:
@Module and @InstallIn – Provide dependencies to
specific components
@Provides – Used inside modules to supply complex
objects
@Binds – Bind interface to implementation (more efficient
than @Provides)
Scoping:
@Singleton, @ActivityScoped, @FragmentScoped –
Control instance lifecycle
6/16
Q6. How do you inject
dependencies in a
ViewModel using Hilt?
A:
Use @HiltViewModel and constructor injection to let Hilt
provide the dependencies:
Then, inside a Composable:
val viewModel: MyViewModel = hiltViewModel()
7/16
Q7. What are Hilt scopes?
A:
@Singleton – Single instance for entire app
@ActivityRetainedScoped – Retained across config
changes
@ViewModelScoped – One per ViewModel instance
@ActivityScoped, @FragmentScoped – Per lifecycle of
component
Q8. How to test with Hilt? 8/16
A:
Setup:
Use @HiltAndroidTest on test classes
Use HiltTestRule for proper initialisation
Override Dependencies:
Use @UninstallModules to remove production modules
Create test modules with @TestInstallIn to provide
mocks/fakes
Alternative: Use @BindValue for simple replacements
Key Benefits:
Replace real APIs with mock implementations
Inject test doubles without changing production code
Maintain proper scoping in the test environment
Example use case: Replace NetworkRepository with
FakeRepository to test offline scenarios without network
calls.
Q9. What is Koin? 9/16
A:
Koin is a Kotlin-native dependency injection framework using a
DSL instead of annotations. It's lightweight, uses runtime
resolution, and requires no code generation.
Key Characteristics:
Pure Kotlin - No Java interop, idiomatic Kotlin code
Runtime DI - Dependencies resolved at runtime, not
compile-time
DSL-based - Declarative syntax with module { }, single { },
factory { }
No code generation - Faster build times, simpler debugging
Lightweight - Minimal setup, quick to learn
When to choose Koin:
Kotlin-first projects with small to medium complexity
Rapid prototyping and quick setup are needed
Teams preferring runtime flexibility over compile-time
safety
Projects where build time is more critical than runtime
performance
10/16
Q10. Core Koin components?
A:
Declaration:
module { } – Declare DI container with dependencies
single { } – Singleton provider (one instance app-wide)
factory { } – Creates a new instance every time requested
scoped { } – Instance tied to scope lifecycle
Retrieval:
get() – Direct dependency retrieval
by inject() – Lazy property delegation
inject() – Lazy injection (alternative syntax to by inject())
Advanced:
bind<Interface>() – Bind implementation to interface
named("qualifier") – Distinguish between same-type
dependencies
Q11. How to define and start 11/16
Koin?
A:
Setup Process:
Define modules - Create modules with single{} for
singletons or factory{} for new instances
Start Koin in Application - Use startKoin{} with
androidContext() and modules()
Inject dependencies - Use get() for direct retrieval or by
inject() for lazy injection
Key Points:
Modules define your dependency graph
startKoin{} initialises the DI container in the Application
class
Pass Android context and module list to make
dependencies available app-wide
Once started, dependencies are accessible via get() or
inject() throughout the app
Q12. How to inject ViewModel 12/16
in Koin?
A:
Step 1: Define in Module
Use viewModel{} DSL specifically designed for
ViewModel injection
Koin handles ViewModel lifecycle automatically
Constructor dependencies are resolved automatically
Step 2: Inject in Activity/Fragment
Use by viewModel() delegate for lazy injection
Use getViewModel() for immediate retrieval
Koin provides proper ViewModel scope
(Activity/Fragment lifecycle)
Key Benefits:
Automatic lifecycle management - no manual cleanup
needed
Constructor injection works seamlessly
Scoped to Activity/Fragment automatically
13/16
Q13. Difference between
single, factory, and scoped?
These are Koin's lifecycle definitions for how dependencies
are provided:
single: Creates one shared instance for the entire app.
It behaves like a singleton. Best used for classes like
repositories, database clients, or network handlers.
factory: Creates a new instance every time the
dependency is requested. Use this for stateless or
short-lived objects.
scoped: Ties the instance to a specific Koin scope,
such as an Activity or Fragment. The object will exist
only as long as the scope is alive.
✅ Summary:
Use single for long-lived shared objects
Use factory when you always need a fresh instance
Use scoped when the object lifecycle should match a
UI component or screen
14/16
Q14. Koin vs Hilt Comparison
A:
Koin and Hilt are both popular dependency injection
frameworks used in Android, but they differ in key ways:
Koin is a runtime DI framework written in pure Kotlin. It
uses a DSL for declaring dependencies and requires no
code generation. It's easy to learn and quick to set up,
but has slightly lower performance due to runtime
resolution.
Hilt is built on top of Dagger, which uses compile-time
DI. It relies on annotations and generates code at build
time, making it more efficient and scalable for large
apps. It integrates tightly with Jetpack libraries and
supports advanced scoping.
✅ In short:
Use Koin for quick setups, prototypes, or small/medium
apps
Use Hilt for production apps, complex architectures, or
when you want Jetpack integration and compile-time
safety
15/16
Q15. Testing in Koin?
A:
Koin makes testing easy by allowing you to replace
production dependencies with mocks or fakes using
declare() or declareMock().
You can either:
Define separate test modules
Or override real dependencies directly in the test
scope
Example (verbal form):
You can replace MyRepository with a FakeRepository
during tests using declare { single<MyRepository> {
FakeRepository() } }.
This ensures your tests use isolated and controlled
dependencies without changing the production code.
✅ This approach keeps tests clean, fast, and
independent of real implementations.
16/16
🔹 Bonus: When to use Manual
DI?
A:
Manual Dependency Injection can be a good choice when:
✅ The project is very small or simple, and introducing a
DI framework is overkill
✅ You want to avoid the overhead of setup and build-
time processing
✅ You need full control over object creation and
lifecycle without relying on external libraries
✅ You’re in a learning environment, debugging core
concepts, or building a prototype
🧠 Manual DI is simple but not scalable — it’s ideal when
clarity and minimalism outweigh flexibility and
automation.
Thank You for Reading!
Ready to dive deeper into Android, Kotlin, and
Clean Architecture?
I regularly share hands-on tutorials and
practical code tips that you can apply directly
to your projects.
Got questions or want to see specific topics
covered? Drop a comment below or send me
a DM - I'd love to hear what you're working on!