Android Dependency Injection: Hilt vs Koin – Guida Completa e Domande da Colloquio 2026
Confronto completo tra Hilt e Koin per la Dependency Injection su Android con esempi di codice, benchmark e domande frequenti nei colloqui tecnici 2026.

La Dependency Injection (DI) rappresenta uno dei principi architetturali fondamentali nello sviluppo Android. Invece di creare le dipendenze direttamente all'interno di una classe, queste vengono fornite dall'esterno, migliorando testabilità, manutenibilità e modularità del codice. Nel 2026, due framework dominano questo ambito: Hilt alla versione 2.57.1, la soluzione ufficiale di Google basata su Dagger con validazione a compile-time, e Koin alla versione 4.2.1, un service locator leggero che utilizza una DSL Kotlin pura per la risoluzione a runtime. La scelta tra i due influenza tempi di build, performance all'avvio, testabilità e velocità di onboarding dei nuovi membri del team.
Hilt valida l'intero grafo delle dipendenze durante la compilazione e blocca il build se manca un binding. Koin risolve le dipendenze a runtime attraverso una DSL Kotlin, intercettando gli errori di configurazione solo quando il percorso di codice interessato viene eseguito. Questa differenza fondamentale determina ogni compromesso tra i due framework.
Come funziona Hilt sotto il cofano
Hilt genera componenti Dagger mappati sulle classi del lifecycle Android. L'annotazione @HiltAndroidApp attiva la generazione di codice a compile-time, producendo factory e provider per ogni binding dichiarato. KSP (Kotlin Symbol Processing) ha sostituito KAPT come processore di annotazioni raccomandato a partire da Hilt 2.48, dimezzando approssimativamente i tempi di elaborazione.
Un setup Hilt tipico coinvolge tre elementi: un modulo che dichiara i binding, un entry point (Activity, Fragment o ViewModel) e la classe Application annotata con @HiltAndroidApp.
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideUserRepository(
retrofit: Retrofit
): UserRepository {
return UserRepositoryImpl(retrofit.create(UserApi::class.java))
}
}@HiltViewModel
class UserViewModel @Inject constructor(
private val userRepository: UserRepository
) : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users.asStateFlow()
fun loadUsers() {
viewModelScope.launch {
_users.value = userRepository.getUsers()
}
}
}Il compilatore verifica che esista un provider valido per UserRepository prima che l'app venga mai eseguita. I binding mancanti emergono come errori di build, non come crash a runtime.
Setup di Koin e l'approccio DSL Kotlin
Koin adotta l'approccio opposto: zero generazione di codice, zero elaborazione di annotazioni. Le dipendenze vengono dichiarate in Kotlin puro usando una DSL, e il framework le risolve a runtime attraverso un registro di servizi globale. Koin 4.2 ha introdotto i Lazy Modules per il caricamento parallelo all'avvio e un nuovo motore CoreResolverV2 che ottimizza la risoluzione degli scope.
val appModule = module {
// Istanza Singleton di Retrofit
single<Retrofit> {
Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
// Repository singleton legato alla sua interfaccia
single<UserRepository> {
UserRepositoryImpl(get<Retrofit>().create(UserApi::class.java))
}
// ViewModel con repository iniettato
viewModel { UserViewModel(get()) }
}class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MyApplication)
modules(appModule)
}
}
}La DSL si legge in modo naturale e non richiede alcun plugin di annotation processing nella configurazione Gradle. Il compromesso: un errore di battitura nella risoluzione get() si manifesta solo a runtime quando quel punto di iniezione specifico viene eseguito.
Benchmark delle performance: tempo di build vs costo a runtime
La questione delle performance emerge in quasi ogni colloquio Android. I numeri concreti contano più delle opinioni.
| Metrica | Hilt 2.57 (KSP) | Koin 4.2 | |---------|-----------------|----------| | Overhead clean build | +8-15s (generazione codice KSP) | ~0s (nessuna generazione) | | Overhead build incrementale | +2-4s | ~0s | | Avvio app (50 binding) | ~0ms (codice pre-generato) | ~5-15ms (risoluzione grafo) | | Avvio app (500 binding) | ~0ms | ~30-80ms | | Impatto dimensione APK | +200-400 KB (codice generato) | +100 KB (libreria runtime) | | Rilevamento binding mancanti | Errore di compilazione | Crash a runtime |
Per progetti con meno di 100 binding, l'overhead a runtime di Koin resta impercettibile. Oltre i 300 binding, il costo all'avvio diventa misurabile, sebbene i Lazy Modules di Koin 4.2 mitighino questo aspetto parallelizzando il caricamento dei moduli.
Passare da KAPT a KSP per l'elaborazione Hilt riduce i tempi di annotation processing del 40-60%. Qualsiasi progetto che utilizza ancora KAPT con Hilt dovrebbe migrare immediatamente a KSP. Hilt supporta KSP dalla versione 2.48.
Strategie di testing con ciascun framework
La testabilità è l'ambito in cui le differenze architetturali diventano concrete. Hilt fornisce @TestInstallIn per sostituire i moduli di produzione con test double a compile-time. Koin offre loadKoinModules() per sovrascrivere le definizioni a runtime.
// Modulo di test Hilt — sostituisce i binding di AppModule nei test
@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [AppModule::class]
)
object FakeAppModule {
@Provides
@Singleton
fun provideUserRepository(): UserRepository {
return FakeUserRepository() // Test double in memoria
}
}// Test Koin — sovrascrittura a runtime
class UserViewModelTest : KoinTest {
@Before
fun setUp() {
startKoin {
modules(
module {
single<UserRepository> { FakeUserRepository() }
viewModel { UserViewModel(get()) }
}
)
}
}
@Test
fun `loads users from repository`() {
val viewModel: UserViewModel = get()
viewModel.loadUsers()
assertEquals(3, viewModel.users.value.size)
}
@After
fun tearDown() {
stopKoin()
}
}I test con Hilt intercettano moduli di test mal configurati a compile-time. I test con Koin richiedono una gestione attenta del lifecycle (startKoin/stopKoin), ma offrono maggiore flessibilità per sovrascritture parziali dei moduli.
Pronto a superare i tuoi colloqui su Android?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Architettura multi-modulo: scalare la DI attraverso le feature
I grandi progetti Android suddividono le feature in moduli Gradle separati. Il framework DI deve supportare i confini tra moduli in modo pulito.
Con Hilt, ogni feature module dichiara il proprio @Module annotato con @InstallIn. Hilt unifica tutti i moduli in una singola gerarchia di componenti a compile-time. L'architettura MVVM si integra naturalmente con i componenti con scope di Hilt.
@Module
@InstallIn(ViewModelComponent::class)
object PaymentModule {
@Provides
fun providePaymentGateway(
retrofit: Retrofit // Fornito dal modulo :app
): PaymentGateway {
return StripePaymentGateway(retrofit.create(PaymentApi::class.java))
}
}Con Koin, i feature module esportano le loro dichiarazioni module {}, e il modulo app le carica tutte all'avvio. I Lazy Modules di Koin 4.2 consentono il caricamento differito dei feature module fino al primo accesso.
val paymentModule = module {
factory<PaymentGateway> {
StripePaymentGateway(get<Retrofit>().create(PaymentApi::class.java))
}
}
// app/MyApplication.kt — carica tutti i feature module
startKoin {
modules(appModule, paymentModule, analyticsModule)
}Hilt impone la visibilità delle dipendenze attraverso gli scope dei componenti. Koin si affida alle convenzioni — qualsiasi modulo può accedere alle definizioni di qualsiasi altro modulo, il che offre flessibilità ma può portare a un accoppiamento implicito.
Considerazioni su Kotlin Multiplatform
Koin supporta Kotlin Multiplatform (KMP) nativamente. La stessa DSL module {} funziona su Android, iOS, Desktop e Web. Hilt è esclusivamente per Android — dipende da componenti lifecycle specifici di Android e dal processore di annotazioni Dagger.
Per i progetti che mirano a più piattaforme, Koin è l'unica opzione praticabile tra i due. I moduli di business logic condivisi possono dichiarare le proprie dipendenze una sola volta e iniettarle su tutti i target.
Domande frequenti nei colloqui tecnici sulla DI Android
Queste domande emergono regolarmente nei colloqui tecnici Android. Ogni risposta resta concisa e focalizzata su ciò che gli intervistatori si aspettano.
D: Qual è la differenza tra dependency injection a compile-time e a runtime?
La DI a compile-time (Hilt/Dagger) genera il codice di iniezione durante il build. Il compilatore valida l'intero grafo delle dipendenze, intercettando i binding mancanti prima del runtime. La DI a runtime (Koin) risolve le dipendenze quando vengono richieste per la prima volta, usando un pattern service registry. La DI a compile-time produce un avvio più rapido ma build più lenti; la DI a runtime non ha overhead sul build ma rinvia il rilevamento degli errori.
D: Quali scope dei componenti Hilt esistono e come si mappano ai lifecycle Android?
SingletonComponent vive per l'intera durata dell'applicazione. ActivityRetainedComponent sopravvive ai cambiamenti di configurazione. ViewModelComponent è legato al lifecycle di un ViewModel. ActivityComponent, FragmentComponent e ViewComponent seguono i rispettivi lifecycle owner Android. Scope personalizzati possono estendere questa gerarchia.
D: Come gestisce Koin l'iniezione dei ViewModel in Jetpack Compose?
Koin fornisce koinViewModel() come funzione Composable che crea o recupera un ViewModel con scope al ViewModelStoreOwner più vicino. Da Koin 4.2, koinNavViewModel() vincola i ViewModel alle voci del navigation graph quando si utilizza Jetpack Compose Navigation.
D: Quando Koin sarebbe una scelta migliore rispetto a Hilt?
Koin si adatta meglio ai progetti KMP (Hilt è solo per Android), alle app di piccole e medie dimensioni dove il tempo di build conta più del tempo di avvio, e ai team che preferiscono una DSL Kotlin esplicita rispetto alla configurazione basata su annotazioni. Anche i progetti di prototyping e proof-of-concept beneficiano del setup minimale di Koin.
Definire Koin come un "framework di dependency injection" è tecnicamente impreciso. Koin è un service locator — le dipendenze vengono estratte tramite get() anziché iniettate tramite constructor injection. Gli intervistatori che conoscono questa distinzione si aspettano che i candidati la riconoscano. Hilt/Dagger esegue una vera dependency injection attraverso chiamate al costruttore generate.
Schema decisionale: scegliere tra Hilt e Koin
| Fattore | Hilt | Koin |
|---------|------|------|
| Validazione del grafo | Compile-time | Runtime |
| Impatto sul tempo di build | Maggiore (codegen KSP) | Nessuno |
| Costo di avvio | Quasi zero | Scala con la dimensione del grafo |
| Curva di apprendimento | Più ripida (concetti Dagger) | Dolce (pura DSL Kotlin) |
| Supporto KMP | No | Sì |
| Integrazione Jetpack | Profonda (ufficiale Google) | Buona (community) |
| Scalabilità del team | Più forte (scope imposti) | Flessibile (basato su convenzioni) |
| Testing | @TestInstallIn (compile-safe) | loadKoinModules (runtime) |
Nessuno dei due framework è universalmente superiore. La scelta dipende dalla dimensione del progetto, dall'esperienza del team e dalle piattaforme target.
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Conclusione
- Hilt 2.57 con KSP offre validazione del grafo a compile-time e costo di avvio quasi nullo, rendendolo la scelta più solida per grandi progetti Android single-platform con requisiti di affidabilità rigorosi
- Koin 4.2 con Lazy Modules e
CoreResolverV2colma il divario nelle performance di avvio mantenendo zero overhead di build e piena compatibilità KMP - Entrambi i framework gestiscono architetture multi-modulo, ma Hilt impone i confini degli scope a compile-time mentre Koin si affida alle convenzioni del team
- Per la preparazione ai colloqui Android, comprendere il compromesso compile-time vs runtime e saper articolare quando ciascun framework è più adatto rappresenta la base attesa
- Migrare tra i due framework non è banale — la scelta iniziale dovrebbe tenere conto della traiettoria di piattaforma e scalabilità a lungo termine del progetto
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Tag
Condividi
Articoli correlati

Jetpack Compose: Animazioni Avanzate Passo dopo Passo
Guida completa alle animazioni avanzate in Compose: transizioni, AnimatedVisibility, Animatable, gesture e prestazioni per interfacce Android fluide.

Le 20 domande più frequenti su Jetpack Compose nei colloqui 2026
Le 20 domande più frequenti su Jetpack Compose nei colloqui tecnici: recomposition, gestione dello stato, navigazione, performance e pattern architetturali.

Kotlin Coroutines per Android: Guida Completa 2026
Guida approfondita alle coroutine Kotlin per lo sviluppo Android: funzioni suspend, scope, dispatcher, Flow e pattern avanzati.