Dagger — to framework wstrzykiwania zależności dla Javy i Kotlina, generujący kod DI na etapie kompilacji poprzez przetwarzanie adnotacji. Hilt — to nakładka na Dagger dla Androida, upraszczająca konfigurację komponentów i cyklu życia. Według danych Google, 2025, Hilt jest używany w ponad 70% aplikacji Android z Google Play Top-100, obsługując Activity, Fragment, ViewModel i Service poprzez predefiniowane komponenty. Oba frameworki zapewniają sprawdzanie grafu zależności w czasie kompilacji, eliminując błędy wstrzykiwania w czasie wykonania.
Najważniejsze
Dagger — to framework wstrzykiwania zależności z generowaniem kodu na etapie kompilacji (compile-time DI). Opracowany w Square, a później przekazany Google, Dagger używa procesora adnotacji Java APT do analizy grafu zależności i generowania klas fabrycznych. W przeciwieństwie do runtime-DI (Guice, Koin), Dagger nie używa refleksji — cały kod jest tworzony podczas kompilacji, co zapewnia maksymalną wydajność w czasie wykonania i wykrywanie błędów na etapie budowania.
Hilt — to biblioteka od Google, zbudowana na bazie Dagger i zoptymalizowana pod kątem Androida. Hilt dostarcza predefiniowane komponenty odpowiadające cyklowi życia komponentów Androida: @SingletonComponent dla Application, @ActivityComponent dla Activity, @FragmentComponent dla Fragment, @ViewModelComponent dla ViewModel. Eliminuje to rutynową konfigurację Component i Module, wymaganą w czystym Dagger. Hilt generuje również graf zależności dla każdego komponentu Androida automatycznie poprzez @AndroidEntryPoint.
Według danych Google I/O 2024, Hilt jest rekomendowanym rozwiązaniem DI w aplikacjach Android napisanych w Kotlinie. Biblioteki Jetpack (Navigation, Room, WorkManager) mają wbudowaną integrację z Hilt poprzez @HiltViewModel i @HiltWorker. W projektach niekorzystających z Androida (czyste biblioteki Java/Kotlin, aplikacje serwerowe) stosuje się czysty Dagger bez nakładki Hilt.
Bez frameworka DI programista tworzy obiekty ręcznie poprzez konstruktory lub fabryki, przekazując zależności w łańcuchu. Każde nowe wymaganie — zmiana sygnatury wszystkich konstruktorów w łańcuchu. Dagger automatyzuje ten proces: wystarczy zadeklarować, jaki typ jest wymagany (@Inject constructor), a Dagger utworzy graf zależności, rozwiązując wszystkie zagnieżdżone typy. Przy zmianie zależności Dagger automatycznie aktualizuje wygenerowany kod — nie można popełnić błędu w łańcuchu.
Wstrzykiwanie zależności (Dependency Injection) — to wzorzec, w którym obiekt otrzymuje swoje zależności z zewnątrz, a nie tworzy je samodzielnie. DI realizuje zasadę odwrócenia sterowania (IoC): klasa nie odpowiada za tworzenie swoich zależności, ale deklaruje je poprzez konstruktor, metodę lub pole. Wstrzykiwanie przez konstruktor (constructor injection) jest uważane za najbardziej preferowane, ponieważ gwarantuje, że obiekt jest tworzony w prawidłowym stanie.
| Typ wstrzykiwania | Składnia Dagger | Kiedy używać |
|---|---|---|
| Constructor injection | @Inject constructor | Główny sposób — dla wszystkich własnych klas |
| Field injection | @Inject lateinit var | Tylko dla komponentów Androida (Activity, Fragment) |
| Method injection | @Inject fun bind() | Do inicjalizacji post-construct |
Główne zalety DI obejmują testowalność (zależności można zastępować przez obiekty mock), słabe powiązanie (klasy zależą od interfejsów, a nie implementacji) oraz jawne zarządzanie cyklem życia obiektów poprzez scopes. Dagger automatycznie gwarantuje, że obiekt jest tworzony raz w ramach swojego scope'u i niszczony po wyjściu z scope'u.
Component — centralny element grafu zależności Dagger. To interfejs adnotowany @Component, który opisuje most między Module a celami wstrzykiwania. Dagger generuje implementację Component (np. DaggerAppComponent) na etapie kompilacji. Component określa, które typy są dostępne do wstrzykiwania, poprzez abstrakcyjne metody zwracające wymagane typy lub metody inject przyjmujące obiekt do field injection.
// Moduł: dostarcza zależności, których Dagger nie może sam utworzyć
@Module
class NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build()
}
@Provides
@Singleton
fun provideApiService(client: OkHttpClient): ApiService {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
// Komponent: łączy Module i cele wstrzykiwania
@Component(modules = [NetworkModule::class])
interface AppComponent {
fun inject(activity: MainActivity)
fun getApiService(): ApiService
}
@Module — to klasa zawierająca metody z @Provides, które zwracają instancje zależności. Module jest używany dla typów, których Dagger nie może utworzyć automatycznie: biblioteki zewnętrzne (OkHttp, Retrofit), obiekty z parametrami konstruktora, interfejsy z wyborem implementacji. @Binds — alternatywa dla @Provides w przypadkach, gdy metoda zwraca interfejs i przyjmuje pojedynczą implementację: Dagger generuje bezpośrednie rzutowanie bez wywoływania metody.
@Scope określa czas życia obiektu w grafie zależności. @Singleton — obiekt jest tworzony raz dla całej aplikacji. @ActivityScoped — obiekt żyje, dopóki żyje Activity. @FragmentScoped — dopóki żyje Fragment. Bez scope'u Dagger tworzy nową instancję przy każdym wstrzyknięciu. @Reusable — scope dla obiektów, które nie muszą być singletonami, ale ich tworzenie jest kosztowne — Dagger może buforować instancję, ale nie gwarantuje tego.
Hilt upraszcza konfigurację Dagger dla Androida dzięki predefiniowanym komponentom i automatycznemu generowaniu podstawowego grafu. Adnotacja @HiltAndroidApp na klasie Application uruchamia generowanie komponentu Hilt. Bez tej adnotacji Hilt nie działa — jest obowiązkowa dla każdej aplikacji Android używającej Hilt. @HiltAndroidApp tworzy nadrzędny komponent SingletonComponent, od którego dziedziczą wszystkie pozostałe komponenty aplikacji.
@HiltAndroidApp
class MyApplication : Application()
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var apiService: ApiService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// apiService został już wstrzyknięty przed wywołaniem onCreate
}
}
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext ctx: Context): AppDatabase {
return Room.databaseBuilder(ctx, AppDatabase::class.java, "app.db").build()
}
}
@AndroidEntryPoint — adnotacja dla Activity, Fragment, Service, BroadcastReceiver i View. Generuje komponent Hilt dla każdego typu: @AndroidEntryPoint na Activity tworzy ActivityComponent, który dziedziczy z SingletonComponent. Komponent potomny automatycznie otrzymuje wszystkie zależności rodzica. Field injection z @Inject lateinit var jest dostępny tylko w klasach adnotowanych @AndroidEntryPoint — w zwykłych klasach używa się constructor injection.
@InstallIn wskazuje, do którego komponentu Hilt instalowany jest moduł. NetworkModule z @InstallIn(SingletonComponent::class) jest dostępny w całej aplikacji. Module z @InstallIn(ActivityComponent::class) — tylko w Activity. Izoluje to grafy zależności: moduły specyficzne dla Activity nie są widoczne w Fragment i ViewModel, zapobiegając przypadkowemu użyciu niedozwolonych zależności. @ApplicationContext — wbudowany kwalifikator Hilt do uzyskania kontekstu aplikacji.
Gdy wymagane jest wstrzyknięcie dwóch różnych implementacji tego samego interfejsu, używa się kwalifikatorów. Hilt obsługuje @Named dla identyfikatorów tekstowych oraz niestandardowe adnotacje z @Qualifier. Na przykład @Named("baseUrl") i @Named("imageBaseUrl") dla różnych konfiguracji tekstowych. Niestandardowe kwalifikatory są preferowane nad @Named ze względu na sprawdzanie na etapie kompilacji — nieprawidłowa nazwa tekstu nie zostanie wykryta do czasu wykonania.
@HiltViewModel — adnotacja zastępująca ręczną fabrykę ViewModelProvider.Factory. Klasa adnotowana @HiltViewModel z @Inject constructor automatycznie otrzymuje wszystkie zależności poprzez Dagger. Hilt generuje ViewModelFactory używaną przez Jetpack ViewModelProvider. Bez Hilt programista musiałby pisać fabrykę ręcznie, przekazując każdy parametr z Activity lub fragmentu.
@HiltViewModel
class MainViewModel
@Inject constructor(
private val apiService: ApiService,
private val database: AppDatabase
) : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users.asStateFlow()
fun loadUsers() {
viewModelScope.launch {
_users.value = apiService.getUsers()
}
}
}
// W Activity — Hilt automatycznie tworzy ViewModel
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
}
ViewModelScoped — scope Hilt dla zależności, które żyją, dopóki żyje ViewModel. Jeśli dwa ViewModel tego samego typu wstrzykują tę samą zależność @ViewModelScoped, każdy otrzymuje własną instancję. To odróżnia @ViewModelScoped od @ActivityScoped, gdzie jedno Activity otrzymuje jedną instancję dla wszystkich fragmentów. Dla zależności specyficznych dla ViewModel (np. SavedStateHandle) używa się @HiltViewModel z @Inject constructor(savedStateHandle: SavedStateHandle).
Hilt obsługuje assisted injection poprzez bibliotekę Hilt Extensions. Assisted injection pozwala przekazywać parametry do konstruktora podczas wstrzykiwania, gdy część zależności jest znana tylko w czasie wykonania (np. ID użytkownika z intencji). Do assisted injection używa się @AssistedInject w kombinacji z parametrami @Assisted. Hilt generuje AssistedFactory, którą można wstrzyknąć w standardowy sposób.
Czysty Dagger wymaga ręcznego tworzenia Component, definiowania scope'ów i konfiguracji wstrzykiwania w każdym komponencie Androida. Programista tworzy AppComponent, ActivityComponent, FragmentComponent, zarządza ich relacjami poprzez @Subcomponent. To podejście daje maksymalną kontrolę, ale wymaga znacznej ilości kodu boilerplate. Dagger jest używany w dużych projektach, gdzie wymagana jest niestandardowa architektura DI, lub w nie-Androidowych projektach Java/Kotlin.
Hilt automatyzuje boilerplate: jeden @HiltAndroidApp, jeden @AndroidEntryPoint dla każdego komponentu, predefiniowane scope'y. Hilt jest rekomendowany przez Google dla wszystkich nowych projektów Android. Migracja z Dagger na Hilt obejmuje zastąpienie Component przez @InstallIn, zastąpienie @Subcomponent przez predefiniowane komponenty Hilt i zastąpienie ręcznego ViewModelProvider.Factory przez @HiltViewModel. Większość klas @Module jest przenoszona z dodaniem @InstallIn bez zmiany metod @Provides.
| Cecha | Dagger | Hilt |
|---|---|---|
| Konfiguracja | Ręczna: Component, Subcomponent, Builder | Auto: @HiltAndroidApp, @AndroidEntryPoint |
| Komponenty Android | Brak predefiniowanych | 12+ wbudowanych komponentów |
| ViewModel | Ręczna fabryka | @HiltViewModel + @Inject constructor |
| Multimodule | Przez @Component(dependencies) | Przez @InstallIn + agregację |
| Złożoność | Wysoka — wymaga doświadczenia | Niska — intuicyjna |
| Elastyczność | Maksymalna | Standardowa (pokrywa 95% scenariuszy) |
Ograniczenia Hilt: biblioteka obsługuje tylko Android (nie nadaje się dla czysto serwerowych projektów Java), narzuca określoną strukturę komponentów (trudno nadpisać) i dodaje zależność od android.hilt:hilt-navigation-compose dla projektów Jetpack Compose. Dla aplikacji Compose Hilt udostępnia @HiltViewModel dostępny w Composable przez hiltViewModel() — bez ręcznego dostarczania ViewModel z Activity.
Często zadawane pytania
Dagger — podstawowy framework DI czasu kompilacji z ręczną konfiguracją Component i Module. Hilt — nakładka na Androida, automatyzująca tworzenie komponentów i integrację z cyklem życia Activity, Fragment, ViewModel, Service i BroadcastReceiver.
@HiltAndroidApp włącza generowanie komponentu Hilt dla Application. Bez tej adnotacji Hilt nie może utworzyć podstawowego komponentu SingletonComponent, od którego dziedziczą wszystkie ActivityComponent, FragmentComponent i ViewModelComponent. Adnotacja jest obowiązkowa dla każdego projektu Hilt.
Hilt Navigation udostępnia @HiltViewModel dla ViewModel w NavBackStackEntry oraz hiltNavGraphViewModels() do scopowania ViewModel w ramach grafu nawigacyjnego. Biblioteka android.hilt:hilt-navigation-fragment automatycznie tworzy ViewModel dla każdego NavBackStackEntry.
Użyj @ApplicationContext dla kontekstu aplikacji lub @ActivityContext dla kontekstu Activity. Hilt udostępnia te kwalifikatory wbudowane w bibliotekę android.hilt:hilt-android. @ActivityContext jest dostępny tylko w modułach zainstalowanych w ActivityComponent.
@Binds — efektywna alternatywa dla @Provides, gdy metoda przyjmuje dokładnie jeden parametr i zwraca jego typ jako interfejs. @Binds generuje bezpośrednie rzutowanie bez wywoływania metody, co zmniejsza ilość wygenerowanego kodu i zwiększa wydajność podczas wstrzykiwania.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również