Injection de dépendances Android : Hilt vs Koin - Guide complet et questions d'entretien 2026
Comparaison approfondie entre Hilt et Koin pour l'injection de dépendances Android : exemples de code, benchmarks de performance et questions d'entretien technique courantes en 2026.

L'injection de dépendances constitue un pilier fondamental du développement Android moderne. En 2026, deux frameworks se partagent le terrain : Hilt (la solution compile-time de Google construite sur Dagger) en version 2.57.1, et Koin (un service locator léger fonctionnant à l'exécution) en version 4.2.1. Le choix entre ces deux outils influence directement les temps de compilation, les performances au démarrage, la testabilité et la courbe d'apprentissage des équipes.
Hilt valide l'intégralité du graphe de dépendances lors de la compilation et fait échouer le build si une liaison manque. Koin résout les dépendances à l'exécution via un DSL Kotlin, ne détectant les erreurs de configuration que lorsque le chemin de code concerné s'exécute. Cette différence fondamentale conditionne tous les compromis entre les deux frameworks.
Fonctionnement interne de Hilt
Hilt génère des composants Dagger mappés aux classes du cycle de vie Android. L'annotation @HiltAndroidApp déclenche la génération de code au moment de la compilation, produisant des factories et des providers pour chaque liaison déclarée. KSP (Kotlin Symbol Processing) a remplacé KAPT comme processeur d'annotations recommandé à partir de Hilt 2.48, réduisant le temps de traitement d'environ 50 %.
Une configuration Hilt typique comprend trois éléments : un module déclarant les liaisons, un point d'entrée (Activity, Fragment ou ViewModel) et la classe Application annotée avec @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()
}
}
}Le compilateur vérifie que UserRepository dispose d'un provider valide avant même que l'application ne s'exécute. Les liaisons manquantes se manifestent sous forme d'erreurs de compilation, jamais de crashs en production.
Configuration de Koin et approche DSL Kotlin
Koin adopte l'approche inverse : aucune génération de code, aucun traitement d'annotations. Les dépendances sont déclarées en Kotlin pur via un DSL, et le framework les résout à l'exécution grâce à un registre de services global. Koin 4.2 a introduit les modules lazy pour un chargement parallèle au démarrage ainsi qu'un nouveau moteur CoreResolverV2 optimisant la résolution des scopes.
val appModule = module {
// Singleton Retrofit instance
single<Retrofit> {
Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
// Singleton repository bound to its interface
single<UserRepository> {
UserRepositoryImpl(get<Retrofit>().create(UserApi::class.java))
}
// ViewModel with injected repository
viewModel { UserViewModel(get()) }
}class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MyApplication)
modules(appModule)
}
}
}Le DSL se lit naturellement et ne nécessite aucun plugin de traitement d'annotations dans la configuration Gradle. La contrepartie : une faute de frappe dans la résolution get() ne sera détectée qu'à l'exécution, au moment où le point d'injection concerné est sollicité.
Benchmarks de performance : temps de build vs coût au runtime
La question de la performance revient systématiquement en entretien technique Android. Les chiffres concrets l'emportent sur les opinions.
| Métrique | Hilt 2.57 (KSP) | Koin 4.2 | |----------|-----------------|----------| | Surcoût build propre | +8-15s (génération KSP) | ~0s (pas de génération) | | Surcoût build incrémental | +2-4s | ~0s | | Démarrage app (50 bindings) | ~0ms (code pré-généré) | ~5-15ms (résolution du graphe) | | Démarrage app (500 bindings) | ~0ms | ~30-80ms | | Impact taille APK | +200-400 Ko (code généré) | +100 Ko (librairie runtime) | | Détection liaison manquante | Erreur de compilation | Crash runtime |
Pour les projets comptant moins de 100 bindings, le surcoût runtime de Koin reste imperceptible. Au-delà de 300 bindings, le coût au démarrage devient mesurable, même si les modules lazy de Koin 4.2 atténuent ce problème en parallélisant le chargement.
Passer de KAPT à KSP pour le traitement Hilt réduit le temps de traitement des annotations de 40 à 60 %. Tout projet utilisant encore KAPT avec Hilt devrait migrer vers KSP sans attendre. Hilt supporte KSP depuis la version 2.48.
Stratégies de test avec chaque framework
La testabilité est le domaine où les différences architecturales deviennent concrètes. Hilt propose @TestInstallIn pour remplacer les modules de production par des doublures de test au moment de la compilation. Koin offre loadKoinModules() pour redéfinir les liaisons à l'exécution.
// Hilt test module — replaces AppModule bindings in tests
@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [AppModule::class]
)
object FakeAppModule {
@Provides
@Singleton
fun provideUserRepository(): UserRepository {
return FakeUserRepository() // In-memory test double
}
}// Koin test — override at 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()
}
}Les tests Hilt détectent les modules de test mal configurés dès la compilation. Les tests Koin exigent une gestion rigoureuse du cycle de vie (startKoin/stopKoin) mais offrent davantage de souplesse pour les surcharges partielles de modules.
Prêt à réussir tes entretiens Android ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Architecture multi-modules : faire passer le DI à l'échelle
Les projets Android d'envergure répartissent les fonctionnalités en modules Gradle distincts. Le framework de DI doit gérer proprement les frontières entre modules.
Avec Hilt, chaque module fonctionnel déclare son propre @Module annoté avec @InstallIn. Hilt fusionne l'ensemble des modules en une hiérarchie unique de composants au moment de la compilation. L'architecture MVVM s'associe naturellement aux composants scopés de Hilt.
@Module
@InstallIn(ViewModelComponent::class)
object PaymentModule {
@Provides
fun providePaymentGateway(
retrofit: Retrofit // Provided by :app module
): PaymentGateway {
return StripePaymentGateway(retrofit.create(PaymentApi::class.java))
}
}Avec Koin, les modules fonctionnels exportent leurs déclarations module {}, et le module applicatif les charge tous au démarrage. Les modules lazy de Koin 4.2 permettent un chargement différé des modules fonctionnels jusqu'au premier accès.
val paymentModule = module {
factory<PaymentGateway> {
StripePaymentGateway(get<Retrofit>().create(PaymentApi::class.java))
}
}
// app/MyApplication.kt — loads all feature modules
startKoin {
modules(appModule, paymentModule, analyticsModule)
}Hilt impose la visibilité des dépendances via les scopes de composants. Koin repose sur la convention : n'importe quel module peut accéder aux définitions de n'importe quel autre module, ce qui apporte de la flexibilité mais peut engendrer un couplage implicite.
Kotlin Multiplatform et DI
Koin prend nativement en charge Kotlin Multiplatform (KMP). Le même DSL module {} fonctionne sur Android, iOS, Desktop et les cibles Web. Hilt reste exclusivement Android, car il dépend des composants de cycle de vie spécifiques à la plateforme et du processeur d'annotations Dagger.
Pour les projets ciblant plusieurs plateformes, Koin s'impose comme le seul choix viable entre les deux. Les modules de logique métier partagée peuvent déclarer leurs dépendances une seule fois et les injecter sur toutes les cibles.
Questions d'entretien courantes sur le DI Android
Ces questions reviennent régulièrement lors des entretiens techniques Android. Chaque réponse reste concise et se concentre sur ce que les recruteurs attendent.
Q : Quelle est la différence entre l'injection de dépendances compile-time et runtime ?
L'injection compile-time (Hilt/Dagger) génère le code d'injection pendant le build. Le compilateur valide l'intégralité du graphe de dépendances, détectant les liaisons manquantes avant l'exécution. L'injection runtime (Koin) résout les dépendances au moment de leur première utilisation, via un patron de type registre de services. L'injection compile-time produit un démarrage plus rapide mais des builds plus lents ; l'injection runtime n'a aucun impact sur le build mais reporte la détection des erreurs.
Q : Quels scopes de composants Hilt existent et comment correspondent-ils aux cycles de vie Android ?
SingletonComponent persiste pendant toute la durée de vie de l'application. ActivityRetainedComponent survit aux changements de configuration. ViewModelComponent est scopé au cycle de vie d'un ViewModel. ActivityComponent, FragmentComponent et ViewComponent suivent leurs propriétaires de cycle de vie Android respectifs. Des scopes personnalisés peuvent étendre cette hiérarchie.
Q : Comment Koin gère-t-il l'injection de ViewModel dans Jetpack Compose ?
Koin fournit koinViewModel() comme fonction Composable qui crée ou récupère un ViewModel scopé au ViewModelStoreOwner le plus proche. Depuis Koin 4.2, koinNavViewModel() scope les ViewModels aux entrées du graphe de navigation lors de l'utilisation de Jetpack Compose navigation.
Q : Dans quels cas Koin serait-il un meilleur choix que Hilt ?
Koin convient mieux aux projets KMP (Hilt est Android-only), aux applications de petite à moyenne taille où le temps de build prime sur le temps de démarrage, et aux équipes qui préfèrent un DSL Kotlin explicite à une configuration dirigée par les annotations. Les prototypes et les preuves de concept bénéficient également de la configuration minimale de Koin.
Qualifier Koin de "framework d'injection de dépendances" est techniquement imprécis. Koin est un service locator : les dépendances sont récupérées via get() plutôt qu'injectées via le constructeur. Les recruteurs familiers de cette distinction s'attendent à ce que les candidats la reconnaissent. Hilt/Dagger réalise une véritable injection de dépendances par des appels de constructeur générés.
Grille de décision : choisir entre Hilt et Koin
| Critère | Hilt | Koin |
|---------|------|------|
| Validation du graphe | Compile-time | Runtime |
| Impact temps de build | Plus élevé (codegen KSP) | Aucun |
| Coût au démarrage | Quasi nul | Proportionnel à la taille du graphe |
| Courbe d'apprentissage | Plus raide (concepts Dagger) | Douce (DSL Kotlin pur) |
| Support KMP | Non | Oui |
| Intégration Jetpack | Profonde (Google officiel) | Bonne (communautaire) |
| Passage à l'échelle | Plus solide (scopes imposés) | Flexible (basé sur les conventions) |
| Testing | @TestInstallIn (compile-safe) | loadKoinModules (runtime) |
Aucun des deux frameworks n'est universellement supérieur. Le choix dépend de la taille du projet, de l'expérience de l'équipe et des plateformes cibles.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Conclusion
- Hilt 2.57 avec KSP offre une validation du graphe à la compilation et un coût de démarrage quasi nul, en faisant le choix le plus solide pour les grands projets Android mono-plateforme avec des exigences strictes de fiabilité
- Koin 4.2 avec les modules lazy et
CoreResolverV2réduit l'écart de performance au démarrage tout en conservant un impact nul sur le build et une compatibilité KMP complète - Les deux frameworks gèrent les architectures multi-modules, mais Hilt impose les frontières de scope à la compilation tandis que Koin repose sur les conventions de l'équipe
- Pour la préparation aux entretiens Android, comprendre le compromis compile-time vs runtime et savoir expliquer quand chaque framework convient constitue le socle attendu
- Migrer d'un framework à l'autre n'est pas anodin : le choix initial doit prendre en compte la trajectoire à long terme du projet en termes de plateformes et de mise à l'échelle
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tags
Partager
Articles similaires

Kotlin 2.3 pour Android : Destructuration par Nom, KMP et Questions d'Entretien 2026
Questions d'entretien Kotlin 2.3 pour développeurs Android en 2026. Destructuration par nom, KMP, paramètres de contexte, Flow et coroutines avec exemples de code.

Jetpack Compose : Animations avancées pas à pas
Guide complet des animations avancées en Jetpack Compose : transitions, AnimatedVisibility, Animatable, gestures et performance pour des interfaces fluides.

Top 20 questions d'entretien Jetpack Compose en 2026
Les 20 questions les plus posées en entretien sur Jetpack Compose : recomposition, state, navigation, performance et architecture.