Dependency Injection (DI, vkládání závislostí) — technika, při které objekt získává své závislosti zvenčí, místo aby je sám vytvářel. DI je implementací principu IoC (Inversion of Control) a je základem Dagger, Hilt a Swinject. Vkládání závislostí snižuje provázanost kódu, zjednodušuje testování a činí architekturu flexibilní. V Androidu je DI standardem přes Dagger Hilt od Googlu, v iOS — přes Swinject nebo ruční vkládání. Více — v Android DI Guide.
Hlavní body
Dependency Injection — technika, při které objekt přijímá závislosti (služby, repozitáře, konfigurace) přes konstruktor, setter nebo rozhraní, místo aby je sám vytvářel pomocí new. Cílem DI je snížit provázanost (coupling) mezi třídami. Pokud si třída vytváří závislosti sama, je pevně svázána s konkrétními implementacemi, což ztěžuje testování a úpravy. Při DI třída pracuje s abstrakcí (protocol/interface) a konkrétní implementace je dodávána zvenčí.
Tři způsoby vkládání — Constructor Injection (přes init/constructor), Setter Injection (přes vlastnost/setter), Interface Injection (přes metodu rozhraní). Constructor Injection — preferovaný způsob: závislosti jsou jasně viditelné v signatuře, objekt je vždy vytvořen v platném stavu. Setter Injection se používá pro volitelné závislosti s výchozí hodnotou. Interface Injection — zřídka, hlavně pro DI kontejnery.
| Typ DI | Způsob | Kdy použít | Příklad |
|---|---|---|---|
| Constructor | Parametry inicializátoru | Povinné závislosti | init(service: ServiceProtocol) |
| Property | Vlastnost třídy | Volitelné závislosti | var service: ServiceProtocol? |
| Method | Parametr metody | Dočasné závislosti | func doWork(with service: Service) |
DI kontejner — knihovna, která spravuje vytváření a životní cyklus závislostí. Kontejner obsahuje registraci typů (každý abstraktní typ je mapován na konkrétní implementaci) a továrnu pro vytváření objektů s vyřešenými závislostmi. V Androidu — Dagger/Hilt, v iOS — Swinject, Needle, Dip. Kontejner může spravovat rozsah (Scope): singleton (jedna instance na aplikaci), rozsah funkce (na obrazovku) nebo nový objekt při každém požadavku.
Dagger Hilt — vrstva nad Dagger od Googlu, standardní DI knihovna pro Android. Hilt zjednodušuje Dagger: odstraňuje ruční vytváření komponent, přidává @HiltAndroidApp, @AndroidEntryPoint a @Module. Hilt se integruje s životním cyklem Androidu: ViewModel, Activity, Fragment, Service, BroadcastReceiver mohou přijímat závislosti přes anotace. Generování kódu probíhá při kompilaci — Dagger generuje implementace komponent, což poskytuje nulové runtime overhead.
// Application class
@HiltAndroidApp
class MyApp : Application()
// Module — definuje, jak vytvářet závislosti
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder().build()
@Provides
@Singleton
fun provideApiService(client: OkHttpClient): ApiService {
return Retrofit.Builder()
.baseUrl("https://api.example.com")
.client(client)
.build()
.create(ApiService::class.java)
}
}
// ViewModel přijímá závislost přes konstruktor
@HiltViewModel
class MainViewModel @Inject constructor(
private val apiService: ApiService
) : ViewModel() {
private val _state = MutableStateFlow(MainState.Loading)
val state: StateFlow<MainState> = _state.asStateFlow()
fun loadData() {
viewModelScope.launch {
_state.value = MainState.Success(apiService.getData())
}
}
}
// Activity — @AndroidEntryPoint aktivuje DI
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
}
Komponenty Dagger a rozsahy viditelnosti — @Singleton (pro celou aplikaci), @ActivityScoped (pro Activity), @FragmentScoped (pro Fragment), @ViewModelScoped (pro ViewModel). Volba rozsahu určuje životnost objektu. @Singleton — jedna instance na proces, vhodná pro OkHttpClient a databázi. @ActivityScoped — objekt žije dokud žije Activity, pro závislosti obrazovky. @ViewModelScoped — novinka Hilt 2.45+, objekt žije dokud žije ViewModel, vhodné pro coroutine rozsahy.
Swinject — populární DI framework pro iOS s otevřeným zdrojovým kódem. Swinject poskytuje Container, Assemblies a různé rozsahy. Na rozdíl od Dagger, Swinject pracuje v runtime — závislosti jsou řešeny dynamicky bez generování kódu. To činí Swinject jednodušším na konfiguraci, ale ztěžuje ladění: chyba nevyřešené závislosti se projeví až v runtime. Swinject podporuje Constructor Injection, Property Injection a Method Injection.
import Swinject
// Assembly — skupina registrací
class NetworkAssembly: Assembly {
func assemble(container: Container) {
container.register(NetworkServiceProtocol.self) { _ in
NetworkService()
}.inObjectScope(.container) // singleton
container.register(UserRepositoryProtocol.self) { r in
UserRepository(
networkService: r.resolve(NetworkServiceProtocol.self)!
)
}
}
}
// ViewModel přes Constructor Injection
class ProfileViewModel: ObservableObject {
private let repository: UserRepositoryProtocol
init(repository: UserRepositoryProtocol) {
self.repository = repository
}
@Published var user: User?
func loadUser() {
repository.fetchUser { [weak self] user in
self?.user = user
}
}
}
// Konfigurace DI v AppDelegate nebo App
let assembler = Assembler([NetworkAssembly(), ServiceAssembly()])
let viewModel = assembler.resolver.resolve(ProfileViewModel.self)!
// Property Injection pro UIKit ViewController
container.register(UserViewController.self) { r in
let vc = UserViewController()
vc.viewModel = r.resolve(ProfileViewModel.self)
return vc
}
Rozsahy Swinject — .transient (nový objekt pokaždé), .container (singleton na kontejner), .graph (výchozí — objekt je sdílen v rámci jednoho grafu závislostí). Pro iOS aplikace stačí .container a .transient. Swinject také podporuje Assembler — seskupování Assembly pro modulární architekturu. Pro testy jsou Assembly nahrazeny MockAssembly, což umožňuje nahrazení závislostí bez změny produkčního kódu.
DI vs Service Locator — oba vzory řeší problém správy závislostí, ale různými způsoby. DI vkládá závislosti do objektu, Service Locator poskytuje globální registr, ze kterého si objekt závislosti sám vyžádá. DI explicitně deklaruje závislosti přes konstruktor (nebo setter). Service Locator skrývá závislosti — jsou vyžadovány uvnitř metody, což činí signaturu méně informativní. DI se snadněji testuje: stačí vložit mock do konstruktoru. Service Locator vyžaduje konfiguraci globálního registru pro každý test.
| Vlastnost | Dependency Injection | Service Locator | Ruční vkládání |
|---|---|---|---|
| Zřejmost závislostí | V konstruktoru | Skryté v těle metody | Explicitní |
| Testování | Mock v konstruktoru | Konfigurace Locator | Mock v konstruktoru |
| Složitost konfigurace | Vyžaduje DI kontejner | Globální registr | Ruční vytváření |
| Runtime overhead | Dagger — compile-time | Runtime lookup | Žádný |
DI vs ruční vkládání — bez DI kontejneru jsou závislosti vytvářeny ručně v továrnách nebo AppDelegate. Pro 5-10 tříd je ruční vkládání jednodušší — nevyžaduje učení Dagger nebo Swinject. Pro 50+ tříd se ruční vkládání stává problémem: konstruktory s 5-6 parametry, složité pořadí vytváření, duplicita kódu. DI kontejner tyto procesy automatizuje a poskytuje jasný životní cyklus. Ruční vkládání bez kontejneru — dobrá volba pro malé projekty a prototypy.
Constructor Injection — standard. Vždy používejte Constructor Injection pro povinné závislosti. To činí závislosti explicitními a objekt vždy připraveným k práci. Setter Injection — pouze pro volitelné závislosti (např. delegate nebo listener). Interface Injection — nepoužívejte, pokud nepíšete vlastní DI knihovnu. Constructor Injection — jediný způsob, jak zaručit, že je objekt vytvořen v platném stavu.
Jedna třída — jedna odpovědnost. Pokud konstruktor třídy vyžaduje 5+ parametrů, třída pravděpodobně porušuje Single Responsibility Principle. Rozdělte třídu na několik s méně závislostmi. Signál: pokud píšete třídu ServiceManager se 6 různými službami — to je anti-vzor God Object. Přesuňte obchodní logiku do Use Cases (Interactors), každý s 1-2 závislostmi.
// ❌ Špatně: 6 závislostí — God Object
class ProfileViewModel @Inject constructor(
private val api: ApiService,
private val db: Database,
private val analytics: Analytics,
private val prefs: Preferences,
private val location: LocationProvider,
private val notification: NotificationManager
)
// ✅ Dobře: Use Cases s 1-2 závislostmi
class ProfileViewModel @Inject constructor(
private val loadProfileUseCase: LoadProfileUseCase,
private val trackAnalyticsUseCase: TrackAnalyticsUseCase
)
Rozsah a životní cyklus — vyberte správný rozsah pro každou závislost. Singleton: OkHttpClient, databáze, SharedPreferences. Rozsah funkce: repozitáře, Use Cases (pokud nemají stav). Transient: Value Objects, DateFormatter, analyzátory. Chyba rozsahu — častý problém: singleton ukládající stav obrazovky vede k únikům paměti. V Android Hilt @ActivityScoped řeší tento problém, v Swinject — .container s opatrností.
Často kladené otázky
New vytváří pevné propojení mezi třídami — nemůžete nahradit implementaci bez změny kódu. Testování je obtížné: nelze vložit mock místo skutečné služby. SRP je porušeno: třída odpovídá jak za obchodní logiku, tak za vytváření závislostí. DI řeší tyto problémy prostřednictvím vkládání závislostí zvenčí a práce s abstrakcemi.
Dagger Hilt — standard od Googlu, compile-time DI s generováním kódu, lepší výkon a integrace s Jetpack. Koin — runtime DI, jednodušší na konfiguraci, ale pomalejší a s runtime chybami. Vybírejte Hilt pro produkční projekty. Koin je vhodný pro prototypy a malé aplikace.
Ne. Pro iOS jsou k dispozici: Swinject (runtime, populární), Needle (compile-time od Uberu), Dip (odlehčený), Weaver (založený na Sourcery). Apple neposkytuje vestavěný DI kontejner, ale ruční vkládání přes init je standardní praxí. Pro SwiftUI často stačí ruční DI přes Environment nebo @StateObject bez externích knihoven.
Ano. Ruční vkládání přes konstruktor — to je DI bez frameworku. Service Locator — alternativa bez frameworku. Továrny a Factory Method — také forma DI. Framework (Dagger, Swinject) automatizuje rutinní registraci a řešení závislostí, ale pro 10-20 tříd je ruční DI dostačující.
DI — je technika (vzor) implementující princip Inversion of Control. Na rozdíl od GoF vzorů nemá DI přísnou strukturu 3-4 tříd. DI je způsob organizace závislostí, nikoli návrhový vzor. DI kontejnery (Dagger, Swinject) jsou frameworky, které tuto techniku automatizují.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také