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.

Hilt vs Koin Android Dependency Injection

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.

Walidacja w czasie kompilacji vs runtime

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.

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()
        }
    }
}

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.

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

MetrykaHilt 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ówBłąd kompilacjiCrash w runtime
Minimalne SDKAPI 23API 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.

Zmiany łamiące w Hilt 2.60

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.

kotlin
// 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
    }
}
kotlin
// 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.

feature-payments/PaymentModule.ktkotlin
@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.

feature-payments/paymentModule.ktkotlin
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.

Częsty błąd na rozmowach rekrutacyjnych

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

CzynnikHiltKoin
Walidacja grafuCzas kompilacjiRuntime
Wpływ na czas builduWyższy (generowanie kodu KSP)Brak
Koszt startu aplikacjiBliski zeruRośnie proporcjonalnie do rozmiaru grafu
Krzywa uczeniaStroma (koncepcje Daggera)Łagodna (czysty DSL Kotlina)
Wsparcie KMPNieTak
Integracja z JetpackGłęboka (oficjalna, Google)Dobra (społeczność + Navigation 3)
Skalowalność w zespoleSilniejsza (wymuszone zakresy)Elastyczna (oparta na konwencjach)
Testowanie@TestInstallIn (walidacja kompilacyjna)loadKoinModules (runtime)
Min SDKAPI 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

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 CoreResolverV2 i 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
Wyzwanie dnia

Znajdziesz błąd w Android?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Zał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

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

Udostępnij

Powiązane artykuły