Wstrzykiwanie zależności na Androidzie: Hilt vs Koin - kompletny przewodnik z pytaniami rekrutacyjnymi 2026
Szczegółowe porównanie Hilt 2.57 i Koin 4.2 dla Android DI: walidacja w czasie kompilacji vs runtime, benchmarki wydajności, testowanie, architektura wielomodułowa i pytania rekrutacyjne.

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.57.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.1, 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))
}
}Adnotacja @InstallIn(SingletonComponent::class) określa zakres życia bindingów — w powyższym przykładzie są one powiązane z cyklem życia całej aplikacji. Hilt definiuje predefiniowaną hierarchię komponentów: SingletonComponent, ActivityRetainedComponent, ViewModelComponent, ActivityComponent, FragmentComponent i ViewComponent, wymuszając poprawne zarządzanie zakresami na poziomie kompilacji.
@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.57 (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 |
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.
Przejście z KAPT na KSP przy przetwarzaniu adnotacji Hilt skraca czas procesowania o 40-60%. Każdy projekt nadal wykorzystujący KAPT z Hilt powinien niezwłocznie przeprowadzić migrację do KSP. Hilt wspiera KSP od wersji 2.48.
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.
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.
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.
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ść) |
| Skalowalność w zespole | Silniejsza (wymuszone zakresy) | Elastyczna (oparta na konwencjach) |
| Testowanie | @TestInstallIn (walidacja kompilacyjna) | loadKoinModules (runtime) |
Ż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.
Podsumowanie
- Hilt 2.57 z KSP zapewnia walidację grafu w czasie kompilacji i niemal zerowy koszt startu aplikacji, co czyni go mocniejszym wyborem dla dużych, jednoplatformowych projektów Android o wysokich wymaganiach niezawodności
- Koin 4.2 z mechanizmem lazy modules i silnikiem
CoreResolverV2zmniejsza 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 — początkowy wybór powinien uwzględniać długoterminową strategię platformową i kierunek skalowania projektu
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Tagi
Udostępnij
Powiązane artykuły

Jetpack Compose: Zaawansowane Animacje Krok po Kroku
Kompletny przewodnik po zaawansowanych animacjach Compose: przejścia, AnimatedVisibility, Animatable, gesty i wydajność płynnych interfejsów Android.

20 najczesciej zadawanych pytan rekrutacyjnych z Jetpack Compose w 2026
20 najczesciej zadawanych pytan na rozmowie kwalifikacyjnej z Jetpack Compose: rekompozycja, zarzadzanie stanem, nawigacja, wydajnosc i wzorce architektoniczne.

Kotlin Coroutines na Androida: Kompletny Przewodnik 2026
Kompleksowy przewodnik po korutynach Kotlin w programowaniu Android: funkcje suspend, zakresy, dispatchery, Flow i zaawansowane wzorce.