Wstrzykiwanie zależności na Androidzie: Hilt vs Koin - kompletny przewodnik z pytaniami rekrutacyjnymi 2026
Porównanie Hilt i Koin dla Android DI z praktycznymi przykładami kodu, benchmarkami wydajności i typowymi pytaniami rekrutacyjnymi. Aktualizacja dla Hilt 2.60 i Koin 4.2.

Wstrzykiwanie zależności (Dependency Injection, DI) stanowi fundament dobrze zaprojektowanej aplikacji Android. Wzorzec ten determinuje sposób, w jaki klasy otrzymują swoje zależności, eliminując konieczność ich bezpośredniego tworzenia wewnątrz obiektów. W 2026 roku ekosystem Android oferuje dwa dominujące frameworki DI: Hilt w wersji 2.60.1, oficjalne rozwiązanie Google zbudowane na bazie Daggera, realizujące walidację grafu zależności w czasie kompilacji, oraz Koin w wersji 4.2.2, lekki framework oparty na DSL Kotlina, rozwiązujący zależności w runtime. Wybór jednego z nich ma bezpośredni wpływ na czas budowania projektu, wydajność startu aplikacji, testowalność kodu i tempo onboardingu nowych członków zespołu.
Hilt weryfikuje cały graf zależności podczas kompilacji, brakujący binding powoduje błąd buildu, zanim aplikacja zostanie uruchomiona. Koin rozwiązuje zależności w runtime za pomocą DSL Kotlina, wykrywając błędy konfiguracji dopiero w momencie wykonania danej ścieżki kodu. Ta fundamentalna różnica determinuje wszystkie kompromisy między oboma frameworkami.
Jak działa Hilt pod maską
Hilt generuje komponenty Daggera mapowane na klasy cyklu życia Androida. Adnotacja @HiltAndroidApp uruchamia proces generowania kodu w czasie kompilacji, tworząc fabryki i providery dla każdego zadeklarowanego bindingu. Od wersji 2.48 zalecanym procesorem adnotacji jest KSP (Kotlin Symbol Processing), który zastąpił KAPT i skrócił czas przetwarzania adnotacji o blisko połowę.
Typowa konfiguracja Hilt składa się z trzech elementów: modułu deklarującego bindingi, punktu wejścia (Activity, Fragment lub ViewModel) oraz klasy Application oznaczonej adnotacją @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()
}
}
}Kompilator weryfikuje, czy UserRepository posiada prawidłowego providera, zanim aplikacja zostanie uruchomiona. Brakujące bindingi ujawniają się jako błędy kompilacji, a nie awarie w trakcie działania aplikacji.
Konfiguracja Koin i podejście oparte na DSL Kotlina
Koin przyjmuje diametralnie odmienne podejście: zero generowania kodu, zero przetwarzania adnotacji. Zależności deklarowane są w czystym Kotlinie za pomocą DSL, a framework rozwiązuje je w runtime poprzez globalny rejestr usług. Wersja 4.2 wprowadza lazy modules umożliwiające równoległe ładowanie modułów podczas startu aplikacji oraz nowy silnik CoreResolverV2 optymalizujący rozwiązywanie zakresów.
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)
}
}
}DSL czyta się naturalnie i nie wymaga żadnej konfiguracji pluginu przetwarzania adnotacji w Gradle. Kompromisem jest fakt, że literówka w wywołaniu get() ujawni się dopiero w runtime, gdy dana ścieżka wstrzykiwania zostanie wykonana.
Benchmarki wydajności: czas budowania vs koszt runtime
Kwestia wydajności pojawia się praktycznie na każdej rozmowie rekrutacyjnej z Androida. Konkretne liczby mają większą wartość niż subiektywne opinie.
| Metryka | Hilt 2.60 (KSP) | Koin 4.2 |
|---|---|---|
| Narzut czystego buildu | +8-15s (generowanie kodu KSP) | ~0s (brak generowania kodu) |
| Narzut buildu przyrostowego | +2-4s | ~0s |
| Start aplikacji (50 bindingów) | ~0ms (wygenerowany kod) | ~5-15ms (rozwiązywanie grafu) |
| Start aplikacji (500 bindingów) | ~0ms | ~30-80ms |
| Wpływ na rozmiar APK | +200-400 KB (wygenerowany kod) | +100 KB (biblioteka runtime) |
| Wykrywanie brakujących bindingów | Błąd kompilacji | Crash w runtime |
| Minimalne SDK | API 23 | API 23 |
W projektach z liczbą bindingów poniżej 100 narzut runtime Koin pozostaje niezauważalny dla użytkownika. Powyżej 300 bindingów koszt startu staje się mierzalny, choć mechanizm lazy modules w Koin 4.2 łagodzi ten problem przez równoległe ładowanie modułów.
Dagger 2.60 usunął wsparcie dla multidex i podniósł minSDK do 23, dopasowując się do wymagań AndroidX. Projekty celujące w API 21-22 nie mogą już korzystać z Hilt. Ta sama wersja wymaga AGP 9 oraz Gradle 9.1+ przy użyciu pluginu Hilt Gradle.
Strategie testowania w obu frameworkach
Testowalność to obszar, w którym różnice architektoniczne przekładają się na praktykę codziennej pracy. Hilt oferuje adnotację @TestInstallIn do podmiany modułów produkcyjnych na testowe w czasie kompilacji. Koin udostępnia mechanizm loadKoinModules() do nadpisywania definicji w runtime.
// 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()
}
}Testy Hilt wykrywają błędnie skonfigurowane moduły testowe na etapie kompilacji. Testy Koin wymagają starannego zarządzania cyklem życia kontenera (startKoin/stopKoin), ale oferują większą elastyczność przy częściowym nadpisywaniu modułów.
Gotowy na rozmowy o Android?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Architektura wielomodułowa: skalowanie DI między feature modules
Duże projekty Android dzielą funkcjonalności na oddzielne moduły Gradle. Framework DI musi poprawnie obsługiwać granice między modułami, zachowując izolację i czytelność grafu zależności. Oba frameworki naturalnie współgrają z nowoczesną architekturą modularną Android.
W przypadku Hilt każdy feature module deklaruje własny moduł oznaczony adnotacją @Module i @InstallIn. Hilt scala wszystkie moduły w pojedynczą hierarchię komponentów w czasie kompilacji. Architektura MVVM naturalnie współgra ze scopowanymi komponentami Hilt.
@Module
@InstallIn(ViewModelComponent::class)
object PaymentModule {
@Provides
fun providePaymentGateway(
retrofit: Retrofit // Provided by :app module
): PaymentGateway {
return StripePaymentGateway(retrofit.create(PaymentApi::class.java))
}
}W Koin feature modules eksportują swoje deklaracje module {}, a moduł aplikacji ładuje je wszystkie podczas startu. Mechanizm lazy modules w Koin 4.2 pozwala na odroczone ładowanie feature modules do momentu pierwszego dostępu.
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 wymusza widoczność zależności poprzez zakresy komponentów, binding zainstalowany w ViewModelComponent nie może wstrzyknąć zależności o krótszym cyklu życia. Koin opiera się na konwencjach zespołu: dowolny moduł ma dostęp do definicji każdego innego modułu, co daje elastyczność, ale może prowadzić do niejawnych powiązań.
Kotlin Multiplatform a wybór frameworka DI
Koin wspiera Kotlin Multiplatform (KMP) natywnie. Ten sam DSL module {} działa na platformach Android, iOS, Desktop i Web. Hilt jest rozwiązaniem wyłącznie androidowym, opiera się na komponentach cyklu życia specyficznych dla Androida oraz na procesorze adnotacji Daggera.
Dla projektów celujących w wiele platform Koin pozostaje jedynym dostępnym wyborem spośród obu frameworków. Współdzielone moduły logiki biznesowej mogą deklarować swoje zależności jednokrotnie i wstrzykiwać je na wszystkich docelowych platformach.
Migracja między frameworkami
Koin 4.2 wprowadził moduł mostka Dagger, umożliwiający stopniową migrację. Zespoły przechodzące z Hilt na Koin mogą udostępniać istniejące moduły Daggera przez service locator Koin podczas okresu przejściowego. To podejście inkrementalne pozwala uniknąć przepisania całości od razu, choć wprowadza tymczasową złożoność architektury.
Najczęstsze pytania rekrutacyjne: Hilt vs Koin
Poniższe pytania regularnie pojawiają się na rozmowach technicznych z Androida. Każda odpowiedź jest zwięzła i koncentruje się na tym, czego oczekuje osoba prowadząca rekrutację.
Pytanie: Czym różni się wstrzykiwanie zależności w czasie kompilacji od wstrzykiwania w runtime?
DI w czasie kompilacji (Hilt/Dagger) generuje kod wstrzykiwania podczas budowania projektu. Kompilator waliduje cały graf zależności, wychwytując brakujące bindingi przed uruchomieniem aplikacji. DI w runtime (Koin) rozwiązuje zależności w momencie ich pierwszego żądania, wykorzystując wzorzec rejestru usług. DI kompilacyjne zapewnia szybszy start aplikacji kosztem wolniejszych buildów; DI runtime eliminuje narzut na budowanie, ale odracza wykrywanie błędów.
Pytanie: Jakie zakresy komponentów definiuje Hilt i jak mapują się na cykl życia Androida?
SingletonComponent istnieje przez cały czas życia aplikacji. ActivityRetainedComponent przeżywa zmiany konfiguracji. ViewModelComponent jest powiązany z cyklem życia ViewModel. ActivityComponent, FragmentComponent i ViewComponent odpowiadają cyklom życia swoich właścicieli w hierarchii Android. Hierarchię tę można rozszerzać o niestandardowe zakresy.
Pytanie: Jak Koin obsługuje wstrzykiwanie ViewModel w Jetpack Compose?
Koin udostępnia koinViewModel() jako funkcję Composable, która tworzy lub pobiera ViewModel w zakresie najbliższego ViewModelStoreOwner. Od wersji 4.2 dostępna jest również funkcja koinNavViewModel(), która zakresuje ViewModele do wpisów grafu nawigacji przy korzystaniu z nawigacji Jetpack Compose. Koin 4.2 dodał także pełne wsparcie dla Jetpack Navigation 3.
Pytanie: Kiedy Koin będzie lepszym wyborem niż Hilt?
Koin sprawdza się lepiej w projektach KMP (Hilt działa wyłącznie na Androidzie), w małych i średnich aplikacjach, gdzie czas budowania ma większe znaczenie niż czas startu, oraz w zespołach preferujących jawny DSL Kotlina zamiast konfiguracji opartej na adnotacjach. Prototypowanie i projekty proof-of-concept również korzystają z minimalnej konfiguracji Koin.
Określanie Koin jako "frameworka wstrzykiwania zależności" jest technicznie nieprecyzyjne. Koin jest lokatorem usług (Service Locator), zależności są pobierane przez get(), a nie dostarczane przez wstrzykiwanie konstruktora. Rekruterzy znający to rozróżnienie oczekują, że kandydat je zasygnalizuje. Hilt/Dagger realizuje rzeczywiste wstrzykiwanie zależności poprzez generowane wywołania konstruktorów.
Ramka decyzyjna: wybór między Hilt a Koin
| Czynnik | Hilt | Koin |
|---|---|---|
| Walidacja grafu | Czas kompilacji | Runtime |
| Wpływ na czas buildu | Wyższy (generowanie kodu KSP) | Brak |
| Koszt startu aplikacji | Bliski zeru | Rośnie proporcjonalnie do rozmiaru grafu |
| Krzywa uczenia | Stroma (koncepcje Daggera) | Łagodna (czysty DSL Kotlina) |
| Wsparcie KMP | Nie | Tak |
| Integracja z Jetpack | Głęboka (oficjalna, Google) | Dobra (społeczność + Navigation 3) |
| Skalowalność w zespole | Silniejsza (wymuszone zakresy) | Elastyczna (oparta na konwencjach) |
| Testowanie | @TestInstallIn (walidacja kompilacyjna) | loadKoinModules (runtime) |
| Min SDK | API 23 (od wersji 2.60) | API 23 |
Żaden z frameworków nie jest uniwersalnie lepszy. Wybór zależy od skali projektu, doświadczenia zespołu i docelowych platform.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Źródła
- Dagger 2.60 Release Notes - wymaganie minSDK 23, bezparametrowy @Binds, wsparcie dla AGP 9
- Koin 4.2 Release Notes - CoreResolverV2, mostek Dagger, wsparcie Navigation 3
- Dokumentacja integracji Koin z Daggerem - moduł mostka do stopniowej migracji
Kluczowe wnioski dla Hilt vs Koin
- Hilt 2.60 z KSP zapewnia walidację grafu w czasie kompilacji i niemal zerowy koszt startu aplikacji, ale obecnie wymaga minSDK 23 i AGP 9+, co czyni go mocniejszym wyborem dla dużych, jednoplatformowych projektów Android o wysokich wymaganiach niezawodności
- Koin 4.2 z mechanizmem lazy modules, silnikiem
CoreResolverV2i nowym mostkiem Dagger zmniejsza dystans wydajnościowy przy starcie, zachowując zerowy wpływ na czas budowania i pełną kompatybilność z KMP - Oba frameworki obsługują architektury wielomodułowe, lecz Hilt wymusza granice zakresów na etapie kompilacji, podczas gdy Koin opiera się na konwencjach zespołu
- W kontekście przygotowania do rozmów rekrutacyjnych z Androida zrozumienie kompromisu między walidacją kompilacyjną a runtime'ową oraz umiejętność uzasadnienia wyboru frameworka w konkretnym kontekście projektowym stanowią oczekiwane minimum
- Migracja między frameworkami jest procesem nietrywalnym, jednak mostek Dagger w Koin oferuje teraz ścieżkę stopniowej migracji dla zespołów odchodzących od Hilt
Znajdziesz błąd w Android?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZałożyciel SharpSkill
Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.
Zaktualizowano 27 września 2026
Tagi
Udostępnij
Powiązane artykuły

Jetpack Navigation Compose w 2026: Nawigacja Type-Safe i Pytania Rekrutacyjne
Kompleksowy przewodnik po Jetpack Navigation Compose z nawigacją typu type-safe, zaawansowanymi wzorcami i pytaniami rekrutacyjnymi dla programistów Android.

Android WorkManager w 2026: Zadania w Tle, Ograniczenia i Pytania Rekrutacyjne
Kompleksowy przewodnik po WorkManager 2.11 w Kotlinie - od podstawowej konfiguracji przez ograniczenia i łańcuchy zadań po testowanie i pytania na rozmowy kwalifikacyjne.

Room Database w Androidzie 2026: Migracje, Relacje i Współbieżność z Coroutines
Kompleksowy przewodnik po Room Database w Androidzie 2026 - od migracji schematu, przez relacje między encjami, po integrację z Kotlin Coroutines i najlepsze praktyki.