androidengineers.Book a session

Advanced Architecture & Modularization

Advanced Dagger Hilt / Koin

article55 minHard

Dependency injection frameworks do more than just wire objects together. They manage object lifetimes, enforce scoping rules, and make large apps testable. Understanding the internals of Hilt and Koin turns you from a user into someone who can debug DI problems and design injection hierarchies for large codebases.

Hilt Component Hierarchy

Hilt generates a set of components that form a hierarchy. Each component has a corresponding scope annotation.

SingletonComponent      → @Singleton
ActivityRetainedComponent → @ActivityRetainedScoped
ActivityComponent       → @ActivityScoped
FragmentComponent       → @FragmentScoped
ViewModelComponent      → @ViewModelScoped
ServiceComponent        → @ServiceScoped

A @Singleton binding is created once per app process. A @ViewModelScoped binding is created once per ViewModel and destroyed when the ViewModel is cleared.

Providing Interfaces

You cannot annotate an interface with @Inject. Use a @Module to bind an implementation to an interface.

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {

    @Binds
    @Singleton
    abstract fun bindUserRepository(
        impl: UserRepositoryImpl
    ): UserRepository
}

@Binds is preferred over @Provides when you are binding an interface to a class Hilt can already construct. It generates less code.

Use @Provides when construction requires custom logic:

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideRetrofit(
        okHttpClient: OkHttpClient
    ): Retrofit = Retrofit.Builder()
        .baseUrl(BuildConfig.BASE_URL)
        .client(okHttpClient)
        .addConverterFactory(MoshiConverterFactory.create())
        .build()
}

Qualifiers

When you need two different instances of the same type, use a qualifier.

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttp

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class LoggingInterceptorOkHttp
@Provides
@AuthInterceptorOkHttp
fun provideAuthClient(authInterceptor: AuthInterceptor): OkHttpClient =
    OkHttpClient.Builder().addInterceptor(authInterceptor).build()

Custom Entry Points

To access Hilt bindings from code that Hilt does not directly inject (such as content providers or non-Hilt-managed classes), use @EntryPoint.

@EntryPoint
@InstallIn(SingletonComponent::class)
interface AnalyticsEntryPoint {
    fun analyticsTracker(): AnalyticsTracker
}

val tracker = EntryPointAccessors.fromApplication(
    context,
    AnalyticsEntryPoint::class.java
).analyticsTracker()

Testing with Hilt

Replace production modules with test fakes using @TestInstallIn:

@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [RepositoryModule::class]
)
@Module
abstract class FakeRepositoryModule {

    @Binds
    @Singleton
    abstract fun bindUserRepository(
        fake: FakeUserRepository
    ): UserRepository
}

For individual test overrides, use @UninstallModules and provide a fresh module inside the test class.

Koin as an Alternative

Koin is a runtime DI framework built with Kotlin DSL. It has no code generation, making it simpler to understand but with runtime instead of compile-time safety.

val networkModule = module {
    single { OkHttpClient.Builder().build() }
    single { Retrofit.Builder().client(get()).build() }
}

val repositoryModule = module {
    single<UserRepository> { UserRepositoryImpl(get()) }
}

val viewModelModule = module {
    viewModel { ProfileViewModel(get()) }
}

Start Koin in Application.onCreate:

startKoin {
    androidContext(this@App)
    modules(networkModule, repositoryModule, viewModelModule)
}

Inject in a ViewModel or composable with by inject() or koinViewModel().

Hilt vs Koin

HiltKoin
SafetyCompile-timeRuntime
PerformanceBetter (generated code)Slight overhead at startup
BoilerplateMore annotationsLess
Testing@TestInstallIn, @UninstallModulesModule overrides
Best forLarge teams, strict boundariesSmaller teams, fast iteration

Both are valid. Hilt is the Google-recommended choice for most Android apps.

Practice

Create a @Singleton Retrofit module, a @ViewModelScoped use case, and write a test that replaces the repository module with a fake using @TestInstallIn. Verify the ViewModel receives the fake without changing production code.

Summary

Hilt's component hierarchy controls object lifetimes. Use @Binds for interfaces, @Provides for custom construction, qualifiers for multiple instances, and @TestInstallIn for clean test fakes. Koin trades compile-time safety for simplicity — choose based on team size and risk tolerance.

YOUR LEARNING JOURNEY

0 of 17 available lessons completed

Progress saved in this browser. No account needed.
Advanced Dagger Hilt / Koin | Senior Android Developer | Android Engineers