Why Dependency Injection Matters in Android

Decoupling. It allows you to swap a RealDatabase for a FakeDatabase in tests instantly. It manages object lifecycles for you. Dependency Injection is fundamental to modern Android architecture.

Benefits of DI:

  • Testability: Replace dependencies with mocks in tests
  • Flexibility: Swap implementations without changing consuming code
  • Lifecycle Management: Automatic cleanup when components are destroyed
  • Code Reuse: Share instances across components
  • Maintainability: Clear dependencies make code easier to understand

Without DI, you'd need to manually create dependencies, manage their lifecycles, and modify code to swap implementations. With DI, Hilt handles all of this automatically.

Dependency Injection is not optional for modern Android apps—it's essential for testability and maintainability.

Hilt vs Dagger: Understanding the Evolution

Hilt is built on Dagger. It provides standard containers (Application, Activity, Fragment) so you don't have to define complex Components manually. Hilt is Google's opinionated wrapper around Dagger that simplifies Android DI:

Hilt advantages:

  • Less Boilerplate: Pre-configured components for Android
  • Standard Scopes: Built-in scopes for Application, Activity, Fragment
  • Automatic Integration: Works out of the box with Android lifecycle
  • Easier Setup: Less configuration than raw Dagger
  • Code Generation: Compile-time safety with generated code

When to use Hilt vs Dagger:

  • Use Hilt: For Android apps—it's purpose-built for Android
  • Use Dagger: For non-Android projects or custom component hierarchies

If you're building an Android app, Hilt is the clear choice. It provides all of Dagger's power with Android-specific conveniences that save significant development time.

Setup and Annotation: Getting Started with Hilt

Add @HiltAndroidApp to your Application class. Add @AndroidEntryPoint to Activities/Fragments. Hilt creates the graph automatically. The setup is remarkably simple:

Step 1: Application Class

  • Add @HiltAndroidApp annotation to your Application class
  • This generates the dependency injection component for your app
  • Hilt handles all the code generation automatically

Step 2: Android Components

  • Add @AndroidEntryPoint to Activities, Fragments, Views, Services
  • This enables dependency injection in these components
  • Hilt automatically provides dependencies through @Inject

Step 3: Inject Dependencies

  • Use @Inject on constructor parameters
  • Hilt automatically provides instances
  • Works for your classes and third-party libraries (via Modules)

That's it! Hilt handles the rest through compile-time code generation. No manual Component creation or Provider setup needed.

Modules and Provides: Integrating Third-Party Libraries

For classes you don't own (Retrofit, Room), use Modules. Annotate functions with @Provides to tell Hilt how to build them. Modules bridge Hilt with libraries you can't modify:

Creating Modules:

  • @Module: Marks a class as a Hilt module
  • @InstallIn: Specifies where module is available (ApplicationComponent, ActivityComponent, etc.)
  • @Provides: Marks a function as a dependency provider
  • @Singleton: Ensures one instance for app lifetime

Common Module Patterns:

  • Retrofit Module: Provides API service instances
  • Database Module: Provides Room database instance
  • Repository Module: Provides repository implementations
  • Preferences Module: Provides SharedPreferences instance

Modules are where you configure third-party libraries. Hilt uses these providers to create instances when needed. This separation keeps your code clean and testable.

ViewModel Injection: Simplifying ViewModels

Add @HiltViewModel and @Inject constructor. No more ViewModelFactories! Hilt handles the factory creation internally. This is one of Hilt's biggest quality-of-life improvements:

Before Hilt (Manual Factory):

  • Create ViewModelFactory for each ViewModel
  • Manually pass dependencies to factory
  • Use factory in Activity/Fragment
  • Significant boilerplate code

With Hilt:

  • Add @HiltViewModel to ViewModel
  • Use @Inject constructor for dependencies
  • Inject directly: hiltViewModel() in Compose or ViewModelProvider
  • No factory needed—Hilt generates it

Hilt automatically creates the factory and manages ViewModel lifecycle. This eliminates ~20-30 lines of boilerplate per ViewModel and reduces bugs from manual factory management.

Scopes and Components: Managing Object Lifecycles

@Singleton lives with App. @ActivityScoped lives with Activity. Hilt manages destroying instances when the scope dies. Understanding scopes is crucial for proper lifecycle management:

Available Scopes:

  • @Singleton: One instance for entire app lifetime
  • @ActivityRetainedScoped: Survives Activity recreation (for ViewModels)
  • @ActivityScoped: One instance per Activity
  • @FragmentScoped: One instance per Fragment
  • @ViewScoped: One instance per View
  • @ViewWithFragmentScoped: Shared between Fragment and View
  • @ServiceScoped: One instance per Service

Scope Hierarchy:

  • Singleton outlives all other scopes
  • ActivityScoped outlives FragmentScoped and ViewScoped
  • Instances are destroyed when their scope is destroyed

Choose the narrowest scope that makes sense. Use @Singleton for truly app-wide data (database, API client). Use ActivityScoped for Activity-specific data. Hilt automatically cleans up instances when scopes are destroyed, preventing memory leaks.

Testing with Hilt: Swapping Dependencies

Use @HiltAndroidTest. You can easily swap modules to inject mock network clients during UI tests. Testing is where DI shines:

Unit Testing:

  • Don't need Hilt—just create instances manually
  • Inject mocks directly into classes
  • Fast and simple for testing business logic

UI Testing with Hilt:

  • Use @HiltAndroidTest on test class
  • Create @TestModule to replace production modules
  • Use @UninstallModules to remove production modules
  • Hilt uses test modules instead of production ones

Testing Strategies:

  • Mock Network: Replace Retrofit with mock API responses
  • Fake Database: Use in-memory database for tests
  • Test Repositories: Inject test implementations
  • Isolated Tests: Each test gets fresh instances

Hilt's testing support is excellent. You can swap any dependency for testing without modifying production code. This enables true integration tests where you test your app with real UI but mocked backend.

Remember: the power of DI is most evident in testing. Without DI, testing would require extensive mocking frameworks or real infrastructure. With Hilt, testing becomes straightforward and reliable.