Android Dependency Injection: Hilt vs Koin – Vollständiger Leitfaden und Interviewfragen 2026

Umfassender Vergleich von Hilt und Koin für Android Dependency Injection mit Codebeispielen, Benchmarks und häufigen Interviewfragen für 2026.

Android Dependency Injection: Hilt vs Koin Vergleich

Dependency Injection (DI) gehört zu den fundamentalen Architekturprinzipien in der Android-Entwicklung. Anstatt Abhängigkeiten direkt innerhalb einer Klasse zu erzeugen, werden sie von außen bereitgestellt – das verbessert Testbarkeit, Wartbarkeit und Modularität. Im Jahr 2026 dominieren zwei Frameworks diesen Bereich: Hilt in Version 2.57.1, Googles offizielle Compile-Time-Lösung auf Basis von Dagger, und Koin in Version 4.2.1, ein leichtgewichtiger Runtime-Service-Locator mit reiner Kotlin-DSL. Die Wahl zwischen beiden beeinflusst Build-Zeiten, Startup-Performance, Testbarkeit und die Einarbeitungszeit neuer Teammitglieder erheblich.

Compile-Time vs Runtime DI

Hilt validiert den gesamten Abhängigkeitsgraphen während der Kompilierung und lässt den Build fehlschlagen, wenn eine Bindung fehlt. Koin löst Abhängigkeiten zur Laufzeit über eine Kotlin-DSL auf und erkennt Konfigurationsfehler erst, wenn der betroffene Codepfad ausgeführt wird. Dieser grundlegende Unterschied bestimmt sämtliche Kompromisse zwischen den beiden Frameworks.

Wie Hilt Dependency Injection unter der Haube funktioniert

Hilt generiert Dagger-Komponenten, die auf Android-Lifecycle-Klassen abgebildet werden. Die Annotation @HiltAndroidApp löst zur Compile-Time Codegenerierung aus und erzeugt Factories sowie Provider für jede deklarierte Bindung. KSP (Kotlin Symbol Processing) hat KAPT als empfohlenen Annotationsprozessor seit Hilt 2.48 abgelöst und reduziert die Verarbeitungszeit um circa die Hälfte.

Ein typisches Hilt-Setup besteht aus drei Teilen: einem Modul mit Bindungsdeklarationen, einem Einstiegspunkt (Activity, Fragment oder ViewModel) und der Application-Klasse mit der Annotation @HiltAndroidApp.

AppModule.ktkotlin
@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))
    }
}
UserViewModel.ktkotlin
@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()
        }
    }
}

Der Compiler überprüft, dass für UserRepository ein gültiger Provider existiert, bevor die App jemals ausgeführt wird. Fehlende Bindungen erscheinen als Build-Fehler, nicht als Runtime-Abstürze.

Koin-Setup und der Kotlin-DSL-Ansatz

Koin verfolgt den entgegengesetzten Ansatz: keine Codegenerierung, kein Annotation Processing. Abhängigkeiten werden in purem Kotlin mittels DSL deklariert, und das Framework löst sie zur Laufzeit über eine globale Service-Registry auf. Koin 4.2 führte Lazy Modules für paralleles Laden beim Start ein sowie eine neue CoreResolverV2-Engine, die die Scope-Auflösung optimiert.

AppModule.ktkotlin
val appModule = module {
    // Singleton Retrofit-Instanz
    single<Retrofit> {
        Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    // Singleton-Repository gebunden an sein Interface
    single<UserRepository> {
        UserRepositoryImpl(get<Retrofit>().create(UserApi::class.java))
    }

    // ViewModel mit injiziertem Repository
    viewModel { UserViewModel(get()) }
}
MyApplication.ktkotlin
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MyApplication)
            modules(appModule)
        }
    }
}

Die DSL liest sich natürlich und benötigt kein Annotation-Processing-Plugin in der Gradle-Konfiguration. Der Nachteil: Ein Tippfehler in der get()-Auflösung zeigt sich erst zur Laufzeit, wenn der betroffene Injektionspunkt ausgeführt wird.

Performance-Benchmarks: Build-Zeit vs Laufzeitkosten

Die Performance-Frage taucht in fast jedem Android-Interview auf. Konkrete Zahlen sind aussagekräftiger als Meinungen.

| Metrik | Hilt 2.57 (KSP) | Koin 4.2 | |--------|-----------------|----------| | Clean-Build-Overhead | +8-15s (KSP-Codegenerierung) | ~0s (keine Codegenerierung) | | Inkrementeller Build-Overhead | +2-4s | ~0s | | App-Start (50 Bindungen) | ~0ms (vorgenerierter Code) | ~5-15ms (Graph-Auflösung) | | App-Start (500 Bindungen) | ~0ms | ~30-80ms | | APK-Größe | +200-400 KB (generierter Code) | +100 KB (Runtime-Bibliothek) | | Erkennung fehlender Bindungen | Compile-Fehler | Runtime-Crash |

Bei Projekten mit weniger als 100 Bindungen bleibt der Laufzeit-Overhead von Koin unmerklich. Ab 300 Bindungen wird der Startup-Aufwand messbar, wobei Koins Lazy Modules dies durch parallelisiertes Laden abmildern.

KSP vs KAPT

Der Wechsel von KAPT zu KSP für die Hilt-Verarbeitung reduziert die Annotation-Processing-Zeit um 40-60 %. Jedes Projekt, das noch KAPT mit Hilt verwendet, sollte umgehend auf KSP migrieren. Hilt unterstützt KSP seit Version 2.48.

Teststrategien mit beiden Frameworks

Testbarkeit ist der Bereich, in dem die architektonischen Unterschiede praktisch spürbar werden. Hilt stellt @TestInstallIn bereit, um Produktionsmodule zur Compile-Time durch Test-Doubles zu ersetzen. Koin bietet loadKoinModules(), um Definitionen zur Laufzeit zu überschreiben.

kotlin
// Hilt-Testmodul — ersetzt AppModule-Bindungen in Tests
@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [AppModule::class]
)
object FakeAppModule {

    @Provides
    @Singleton
    fun provideUserRepository(): UserRepository {
        return FakeUserRepository() // In-Memory-Test-Double
    }
}
kotlin
// Koin-Test — Überschreibung zur Laufzeit
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()
    }
}

Hilt-Tests erkennen fehlkonfigurierte Testmodule zur Compile-Time. Koin-Tests erfordern sorgfältiges Lifecycle-Management (startKoin/stopKoin), bieten aber mehr Flexibilität für partielle Modul-Überschreibungen.

Bereit für deine Android-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Multi-Modul-Architektur: DI über Feature-Grenzen hinweg skalieren

Große Android-Projekte teilen Features in separate Gradle-Module auf. Das DI-Framework muss Modulgrenzen sauber unterstützen.

Mit Hilt deklariert jedes Feature-Modul sein eigenes @Module, annotiert mit @InstallIn. Hilt fügt alle Module zur Compile-Time in eine einzige Komponentenhierarchie zusammen. Die MVVM-Architektur harmoniert natürlich mit Hilts Scoped Components.

feature-payments/PaymentModule.ktkotlin
@Module
@InstallIn(ViewModelComponent::class)
object PaymentModule {

    @Provides
    fun providePaymentGateway(
        retrofit: Retrofit // Bereitgestellt vom :app-Modul
    ): PaymentGateway {
        return StripePaymentGateway(retrofit.create(PaymentApi::class.java))
    }
}

Mit Koin exportieren Feature-Module ihre module {}-Deklarationen, und das App-Modul lädt sie alle beim Start. Koins Lazy Modules in Version 4.2 ermöglichen verzögertes Laden von Feature-Modulen bis zum ersten Zugriff.

feature-payments/paymentModule.ktkotlin
val paymentModule = module {
    factory<PaymentGateway> {
        StripePaymentGateway(get<Retrofit>().create(PaymentApi::class.java))
    }
}

// app/MyApplication.kt — lädt alle Feature-Module
startKoin {
    modules(appModule, paymentModule, analyticsModule)
}

Hilt erzwingt Abhängigkeitssichtbarkeit durch Component Scopes. Koin setzt auf Konventionen — jedes Modul kann auf die Definitionen jedes anderen Moduls zugreifen, was Flexibilität bietet, aber zu impliziter Kopplung führen kann.

Kotlin Multiplatform: Plattformübergreifende Überlegungen

Koin unterstützt Kotlin Multiplatform (KMP) nativ. Dieselbe module {}-DSL funktioniert auf Android, iOS, Desktop und Web. Hilt ist ausschließlich für Android konzipiert – es basiert auf Android-spezifischen Lifecycle-Komponenten und dem Dagger-Annotationsprozessor.

Für Projekte mit mehreren Zielplattformen ist Koin die einzige praktikable Option der beiden. Geteilte Business-Logic-Module können ihre Abhängigkeiten einmal deklarieren und sie über alle Targets hinweg injizieren.

Häufige Interviewfragen zu Android Dependency Injection

Diese Fragen tauchen regelmäßig in technischen Android-Interviews auf. Jede Antwort bleibt prägnant und fokussiert auf das, was Interviewer erwarten.

F: Was ist der Unterschied zwischen Compile-Time und Runtime Dependency Injection?

Compile-Time DI (Hilt/Dagger) generiert Injektionscode während des Builds. Der Compiler validiert den gesamten Abhängigkeitsgraphen und erkennt fehlende Bindungen vor der Laufzeit. Runtime DI (Koin) löst Abhängigkeiten auf, wenn sie erstmals angefordert werden, mittels eines Service-Registry-Patterns. Compile-Time DI liefert schnelleren Start, aber langsamere Builds; Runtime DI hat keinen Build-Overhead, verschiebt aber die Fehlererkennung.

F: Welche Hilt-Component-Scopes gibt es und wie ordnen sie sich den Android-Lifecycles zu?

SingletonComponent lebt für die gesamte Anwendungsdauer. ActivityRetainedComponent überlebt Konfigurationsänderungen. ViewModelComponent ist an den Lifecycle eines ViewModels gebunden. ActivityComponent, FragmentComponent und ViewComponent folgen ihren jeweiligen Android-Lifecycle-Ownern. Benutzerdefinierte Scopes können diese Hierarchie erweitern.

F: Wie behandelt Koin ViewModel-Injection in Jetpack Compose?

Koin stellt koinViewModel() als Composable-Funktion bereit, die ein ViewModel erstellt oder abruft, das auf den nächsten ViewModelStoreOwner beschränkt ist. Seit Koin 4.2 begrenzt koinNavViewModel() ViewModels auf Navigations-Graph-Einträge bei Verwendung von Jetpack Compose Navigation.

F: Wann wäre Koin die bessere Wahl gegenüber Hilt?

Koin eignet sich besser für KMP-Projekte (Hilt ist nur für Android), kleine bis mittlere Apps, bei denen Build-Zeit wichtiger ist als Startup-Zeit, und Teams, die eine explizite Kotlin-DSL gegenüber annotationsgesteuerter Konfiguration bevorzugen. Prototyping und Proof-of-Concept-Projekte profitieren ebenfalls von Koins minimalem Setup.

Häufiger Interview-Fehler

Koin als "Dependency-Injection-Framework" zu bezeichnen ist technisch unpräzise. Koin ist ein Service Locator — Abhängigkeiten werden über get() gezogen, anstatt über Constructor Injection bereitgestellt zu werden. Interviewer, die mit dieser Unterscheidung vertraut sind, erwarten, dass Kandidaten dies anerkennen. Hilt/Dagger führt echte Dependency Injection durch generierte Konstruktor-Aufrufe durch.

Entscheidungsrahmen: Hilt oder Koin wählen

| Faktor | Hilt | Koin | |--------|------|------| | Graph-Validierung | Compile-Time | Runtime | | Build-Zeit-Auswirkung | Höher (KSP-Codegen) | Keine | | Startup-Kosten | Nahe Null | Skaliert mit Graph-Größe | | Lernkurve | Steiler (Dagger-Konzepte) | Sanft (pure Kotlin-DSL) | | KMP-Unterstützung | Nein | Ja | | Jetpack-Integration | Tief (offiziell von Google) | Gut (Community) | | Team-Skalierbarkeit | Stärker (erzwungene Scopes) | Flexibel (konventionsbasiert) | | Testing | @TestInstallIn (compile-sicher) | loadKoinModules (Runtime) |

Keines der beiden Frameworks ist universell überlegen. Die Wahl hängt von Projektgröße, Teamerfahrung und Zielplattformen ab.

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Fazit

  • Hilt 2.57 mit KSP liefert Compile-Time-Graph-Validierung und nahezu keine Startup-Kosten – damit ist es die stärkere Wahl für große, plattformspezifische Android-Projekte mit strengen Zuverlässigkeitsanforderungen
  • Koin 4.2 mit Lazy Modules und CoreResolverV2 schließt die Startup-Performance-Lücke bei gleichzeitig null Build-Overhead und voller KMP-Kompatibilität
  • Beide Frameworks bewältigen Multi-Modul-Architekturen, wobei Hilt Scope-Grenzen zur Compile-Time erzwingt, während Koin auf Team-Konventionen setzt
  • Für die Vorbereitung auf Android-Interviews ist das Verständnis des Compile-Time-vs-Runtime-Kompromisses und die Fähigkeit, zu erklären, wann welches Framework passt, die erwartete Grundlage
  • Eine Migration zwischen den Frameworks ist nicht trivial – die anfängliche Entscheidung sollte die langfristige Plattform- und Skalierungsstrategie des Projekts berücksichtigen

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Tags

#android
#kotlin
#dependency-injection
#hilt
#koin

Teilen

Verwandte Artikel