Capítulo 12: Injeção de Dependências no Android: Koin vs Hilt e Arquitetura Modular
Capítulo 12: Injeção de Dependências no Android: Koin vs Hilt e Arquitetura Modular
Especialização em Mobile Nativo com Android • Kotlin 2.0 & Jetpack Compose
🗺️ Mapa Conceitual de Grafos de Injeção de Dependências (DI)
flowchart TD
APP["Classe Application (Ponto de Partida do Grafo de DI)"] --> DI_CONTAINER["Contêiner de Injeção (Koin startKoin / Hilt @HiltAndroidApp)"]
subgraph MODULES["Módulos de Dependência (Definição de Provedores)"]
NET_MOD["Módulo de Rede: singleOf(::KtorHttpClient)"]
DATA_MOD["Módulo de Dados: singleOf(::RoomAppDatabase)"]
REPO_MOD["Módulo de Repositório: singleOf(::UsuarioRepositoryImpl) bind UsuarioRepository::class"]
VM_MOD["Módulo de ViewModel: viewModelOf(::UsuarioViewModel)"]
end
DI_CONTAINER --> NET_MOD
DI_CONTAINER --> DATA_MOD
DATA_MOD --> REPO_MOD
NET_MOD --> REPO_MOD
REPO_MOD --> VM_MOD
subgraph COMPOSE["Injeção Reativa na Camada de Apresentação (Compose)"]
SCREEN["TelaUsuarioScreen()"]
INJECT["val vm: UsuarioViewModel = koinViewModel() / hiltViewModel()"]
SCREEN --> INJECT
end
VM_MOD -->|"Instanciação Automática com Escopo de Lifecycle"| INJECT
style APP fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style DI_CONTAINER fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style REPO_MOD fill:#b3e5fc,stroke:#0288d1,stroke-width:2px
style VM_MOD fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style SCREEN fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. O Princípio da Inversão de Controle e a Arquitetura Mobile
À medida que um aplicativo Android cresce, instanciar clientes HTTP, bancos de dados locais e repositórios manualmente dentro de cada Activity ou ViewModel gera um acoplamento crítico. Classes tornam-se impossíveis de testar de forma isolada, pois dependências concretas ficam engessadas no código.
A Injeção de Dependências (DI) inverte esse fluxo: as classes declaram o que precisam em seus construtores e um contêiner externo fornece essas instâncias:
- Hilt (Google Dagger para Android): É a biblioteca oficial recomendada pelo Google para projetos de grande porte. O Hilt gera código Java/Kotlin em tempo de compilação através de processadores de anotações (KSP - Kotlin Symbol Processing). Sua grande vantagem é a validação estática: se uma dependência estiver ausente no grafo, o projeto falha ao compilar, evitando crashes surpresa em tempo de execução.
- Koin (The Pragmatic Kotlin DI): É um framework puramente escrito em Kotlin que opera através de um DSL declarativo e Service Locator funcional. Ele não gera código adicional, não usa reflexão pesada e tem tempo de compilação quase instantâneo. É a escolha preferida em projetos que priorizam builds rápidos, Kotlin Multiplatform (KMP) e simplicidade de configuração.
- Escopos no Jetpack Compose: Tanto o Koin quanto o Hilt fornecem extensões específicas para o Compose (
koinViewModel(),hiltViewModel()). Essas funções garantem que a instância do ViewModel seja obtida doViewModelStoreOwnermais próximo, mantendo o estado vivo durante rotações e descartando-o quando a tela sai do grafo de navegação.
📊 2. Comparativo Arquitetural: Koin vs Hilt (Dagger)
| Dimensão Técnica | Koin (Kotlin Pragmatic DI) | Hilt (Google Dagger Android) |
|---|---|---|
| Resolução do Grafo | Runtime (lazy, sob demanda na primeira chamada) | Compile-time (validação estática completa via KSP) |
| Impacto no Tempo de Build | Zero impacto (não gera código nem exige KSP) | Moderado (geração de classes e fábricas de grafo) |
| Sintaxe & Curva de Aprendizado | Baixíssima (DSL simples: single, factory, viewModel) |
Média-alta (anotações: @AndroidEntryPoint, @Inject, @Provides) |
| Kotlin Multiplatform (KMP) | Suporte nativo completo (Android, iOS, Desktop) | Exclusivo para o ecossistema Android nativo |
| Detecção de Dependências Ausentes | Dispara InstanceCreationException em runtime |
Falha o build do Gradle com relatório detalhado |
💻 3. Laboratório Prático: Configuração de Módulos e Injeção com Koin 3.5+
O código abaixo demonstra a configuração completa de injeção de dependências em um projeto Android utilizando a sintaxe idiomática moderna do Koin:
package br.com.portal.mobile.android.cap12
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModelOf
import org.koin.core.context.startKoin
import org.koin.core.module.dsl.bind
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
// 1. Interfaces e Implementações de Domínio/Dados
interface DispositivoRepository {
fun obterNomeAparelho(): String
}
class DispositivoRepositoryImpl : DispositivoRepository {
override fun obterNomeAparelho(): String = "Android 14 ARM64 Snapdragon"
}
// 2. ViewModel com Injeção de Construtor
class SistemaViewModel(
private val repository: DispositivoRepository
) : ViewModel() {
private val _informacao = MutableStateFlow("Carregando...")
val informacao = _informacao.asStateFlow()
fun carregarInfo() {
viewModelScope.launch {
_informacao.value = "Hardware: ${repository.obterNomeAparelho()}"
}
}
}
// 3. Módulo Koin com Construtores Automáticos (singleOf e viewModelOf)
val appModuloKoin = module {
// Registra Singleton da interface mapeada para a implementação concreta
singleOf(::DispositivoRepositoryImpl) { bind<DispositivoRepository>() }
// Registra ViewModel com ciclo de vida gerenciado pelo AndroidX
viewModelOf(::SistemaViewModel)
}
// 4. Inicialização na Classe Application
class PortalMobileApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@PortalMobileApplication)
modules(appModuloKoin)
}
}
}
⚠️ 4. Armadilhas Comuns em Injeção de Dependências
- Injetar Contexto de Activity em Singletons: Criar um singleton que armazena uma referência para a
Activitydo Android (androidActivityScope) causa vazamento catastrófico de memória. Sempre injete oandroidContext()(Application Context) para singletons de rede, banco ou preferências. - Esquecer de Vincular a Interface (
bind): Ao declararsingleOf(::RepositorioImpl), se seu ViewModel depender da interfaceRepositorio, o Koin lançará uma exceção afirmando que a interface não foi encontrada no grafo. Lembre-se sempre de encadear{ bind<Repositorio>() }. - Módulos Gigantes Monolíticos: Evite concentrar todas as dependências do aplicativo em um único arquivo de módulo. Divida seus módulos por camada ou por feature (
redeModule,bancoModule,autenticacaoModule), facilitando a manutenção e os testes unitários.