Injeção de Dependência (DI) é uma técnica na qual um objeto recebe suas dependências externamente, em vez de criá-las por si só. DI é uma implementação do princípio IoC (Inversão de Controle) e está na base do Dagger, Hilt e Swinject. A injeção de dependências reduz o acoplamento do código, simplifica os testes e torna a arquitetura flexível. No Android, DI é padrão através do Dagger Hilt do Google; no iOS, através do Swinject ou injeção manual. Saiba mais no Guia de DI do Android.
Principais pontos
Injeção de Dependência é uma técnica na qual um objeto recebe dependências (serviços, repositórios, configurações) através de um construtor, setter ou interface, em vez de criá-las ele mesmo com new. O objetivo do DI é reduzir o acoplamento entre classes. Se uma classe cria dependências por si mesma, ela fica fortemente ligada a implementações específicas, dificultando testes e modificações. Com DI, a classe trabalha com uma abstração (protocolo/interface), e a implementação concreta é fornecida externamente.
Três formas de injeção — Injeção por Construtor (via init/constructor), Injeção por Setter (via propriedade/setter), Injeção por Interface (via um método de interface). A Injeção por Construtor é a abordagem preferida: as dependências são claramente visíveis na assinatura e o objeto é sempre criado em um estado válido. A Injeção por Setter é usada para dependências opcionais com valor padrão. A Injeção por Interface é rara, principalmente para contêineres DI.
| Tipo de DI | Método | Quando usar | Exemplo |
|---|---|---|---|
| Construtor | Parâmetros do inicializador | Dependências obrigatórias | init(service: ServiceProtocol) |
| Propriedade | Propriedade da classe | Dependências opcionais | var service: ServiceProtocol? |
| Método | Parâmetro do método | Dependências temporárias | func doWork(with service: Service) |
Contêiner DI — uma biblioteca que gerencia a criação e o ciclo de vida das dependências. O contêiner contém registros de tipos (cada tipo abstrato mapeado para uma implementação concreta) e uma fábrica para criar objetos com dependências resolvidas. No Android — Dagger/Hilt, no iOS — Swinject, Needle, Dip. O contêiner pode gerenciar escopo: singleton (uma instância por aplicação), escopo de funcionalidade (por tela) ou um novo objeto a cada solicitação.
Dagger Hilt é um invólucro sobre o Dagger do Google, a biblioteca DI padrão para Android. Hilt simplifica o Dagger: elimina a criação manual de componentes, adiciona @HiltAndroidApp, @AndroidEntryPoint e @Module. Hilt integra-se ao ciclo de vida do Android: ViewModel, Activity, Fragment, Service, BroadcastReceiver podem receber dependências através de anotações. A geração de código ocorre em tempo de compilação — o Dagger gera implementações de componentes, proporcionando sobrecarga de runtime zero.
// Classe de aplicação
@HiltAndroidApp
class MyApp : Application()
// Módulo — define como criar dependências
@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 recebe dependência através do construtor
@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 habilita DI
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
}
Componentes e escopos do Dagger — @Singleton (toda a aplicação), @ActivityScoped (por Activity), @FragmentScoped (por Fragment), @ViewModelScoped (por ViewModel). A escolha do escopo determina o tempo de vida do objeto. @Singleton — uma instância por processo, adequado para OkHttpClient e bancos de dados. @ActivityScoped — o objeto vive enquanto a Activity vive, para dependências de tela. @ViewModelScoped — novo no Hilt 2.45+, o objeto vive enquanto o ViewModel vive, conveniente para escopos de corrotinas.
Swinject é um framework DI popular de código aberto para iOS. Swinject fornece Container, Assemblies e vários escopos. Ao contrário do Dagger, Swinject funciona em tempo de execução — as dependências são resolvidas dinamicamente sem geração de código. Isso torna o Swinject mais fácil de configurar, mas a depuração é mais difícil: um erro de dependência não resolvida só aparece em tempo de execução. Swinject suporta Injeção por Construtor, Injeção por Propriedade e Injeção por Método.
import Swinject
// Assembly — um grupo de registros
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 via Injeção por Construtor
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
}
}
}
// Configuração de DI no AppDelegate ou App
let assembler = Assembler([NetworkAssembly(), ServiceAssembly()])
let viewModel = assembler.resolver.resolve(ProfileViewModel.self)!
// Injeção por Propriedade para UIKit ViewController
container.register(UserViewController.self) { r in
let vc = UserViewController()
vc.viewModel = r.resolve(ProfileViewModel.self)
return vc
}
Escopos do Swinject — .transient (novo objeto a cada vez), .container (singleton por contêiner), .graph (padrão — o objeto é compartilhado dentro de um único grafo de dependências). Para aplicações iOS, .container e .transient são suficientes. Swinject também suporta Assembler — agrupamento de Assembly para arquitetura modular. Para testes, a Assembly é substituída por MockAssembly, permitindo a substituição de dependências sem alterar o código de produção.
DI vs Service Locator — ambos os padrões resolvem o gerenciamento de dependências, mas de forma diferente. DI injeta dependências no objeto; Service Locator fornece um registro global do qual o objeto solicita as dependências por si só. DI declara explicitamente as dependências através do construtor (ou setter). Service Locator oculta as dependências — elas são solicitadas dentro do método, tornando a assinatura menos informativa. DI é mais fácil de testar: basta passar um mock ao construtor. Service Locator requer a configuração do registro global para cada teste.
| Característica | Injeção de Dependência | Service Locator | Injeção manual |
|---|---|---|---|
| Visibilidade das dependências | No construtor | Ocultas no corpo do método | Explícitas |
| Testes | Mock no construtor | Configuração do Locator | Mock no construtor |
| Complexidade de configuração | Requer contêiner DI | Registro global | Criação manual |
| Sobrecarga em runtime | Dagger — tempo de compilação | Busca em runtime | Nenhuma |
DI vs injeção manual — sem um contêiner DI, as dependências são criadas manualmente em fábricas ou no AppDelegate. Para 5-10 classes, a injeção manual é mais simples — não requer aprender Dagger ou Swinject. Para 50+ classes, a injeção manual torna-se problemática: construtores com 5-6 parâmetros, ordem de criação complexa, duplicação de código. Um contêiner DI automatiza esses processos e fornece gerenciamento claro do ciclo de vida. A injeção manual sem contêiner é uma boa escolha para projetos pequenos e protótipos.
Injeção por Construtor — padrão. Use sempre a Injeção por Construtor para dependências obrigatórias. Isso torna as dependências explícitas e o objeto sempre pronto para funcionar. Injeção por Setter — apenas para dependências opcionais (por exemplo, delegate ou listener). Injeção por Interface — não use, a menos que esteja escrevendo sua própria biblioteca DI. A Injeção por Construtor é a única maneira de garantir que um objeto seja criado em um estado válido.
Uma classe — uma responsabilidade. Se o construtor de uma classe requer 5+ parâmetros, provavelmente a classe viola o Princípio da Responsabilidade Única. Divida a classe em várias com menos dependências. Um sinal: se você escreve uma classe ServiceManager com 6 serviços diferentes — isso é o antipadrão God Object. Extraia a lógica de negócios para Use Cases (Interactors), cada um com 1-2 dependências.
// ❌ Ruim: 6 dependências — 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
)
// ✅ Bom: Use Cases com 1-2 dependências
class ProfileViewModel @Inject constructor(
private val loadProfileUseCase: LoadProfileUseCase,
private val trackAnalyticsUseCase: TrackAnalyticsUseCase
)
Escopo e ciclo de vida — escolha o escopo correto para cada dependência. Singletons: OkHttpClient, banco de dados, SharedPreferences. Escopo de funcionalidade: repositórios, Use Cases (se não tiverem estado). Transient: Value Objects, DateFormatter, analisadores. Erros de escopo são um problema comum: um singleton armazenando estado de tela causa vazamentos. No Android, o @ActivityScoped do Hilt resolve isso; no Swinject, use .container com cautela.
Perguntas frequentes
new cria um acoplamento forte entre classes — você não pode trocar a implementação sem alterar o código. Testar torna-se difícil: não é possível injetar um mock em vez de um serviço real. O SRP é violado: a classe é responsável tanto pela lógica de negócios quanto pela criação de dependências. DI resolve esses problemas injetando dependências externamente e trabalhando com abstrações.
Dagger Hilt é o padrão do Google, DI em tempo de compilação com geração de código, melhor desempenho e integração com Jetpack. Koin é DI em tempo de execução, mais fácil de configurar, mas mais lento e com erros em runtime. Escolha Hilt para projetos de produção. Koin é adequado para protótipos e aplicações pequenas.
Não. Para iOS estão disponíveis: Swinject (runtime, popular), Needle (tempo de compilação da Uber), Dip (leve), Weaver (baseado em Sourcery). A Apple não fornece um contêiner DI integrado, mas a injeção manual via init é uma prática padrão. Para SwiftUI, a DI manual através de Environment ou @StateObject sem bibliotecas externas é frequentemente suficiente.
Sim. A injeção manual através do construtor é DI sem framework. Service Locator é uma alternativa sem framework. Fábricas e Factory Method também são formas de DI. Um framework (Dagger, Swinject) automatiza o registro rotineiro e a resolução de dependências, mas para 10-20 classes, o DI manual é suficiente.
DI é uma técnica (template) que implementa o princípio da Inversão de Controle. Ao contrário dos padrões GoF, DI não tem uma estrutura rígida de 3-4 classes. DI é uma forma de organizar dependências, não um padrão de design. Os contêineres DI (Dagger, Swinject) são frameworks que automatizam essa técnica.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também