Introdução à Injeção de Dependência em Kotlin
O desenvolvimento moderno em Kotlin requer um gerenciamento adequado de dependências entre classes. Dependency Injection (DI) é um padrão de projeto que permite passar as dependências de um objeto externamente, em vez de criá-las internamente. Isso reduz o acoplamento do código, simplifica os testes e torna a aplicação mais flexível.
Neste artigo, vamos entender o que é injeção de dependência, por que ela é necessária e explorar três abordagens para implementá-la em Kotlin: injeção manual, uso da biblioteca Hilt e do framework leve Koin. O material será útil tanto para iniciantes quanto para desenvolvedores experientes que desejam organizar a arquitetura de seus projetos.
Fundamentos da Injeção de Dependência
Problema sem DI
Imagine a classe UserService, que cria diretamente uma instância de UserRepository:
class UserService { private val repository = UserRepository() // acoplamento rígido
fun getUser(id: Int): User { return repository.findById(id) }}Esse código é difícil de testar: você não consegue substituir UserRepository por um objeto mock. Além disso, ao alterar o construtor de UserRepository, será necessário modificar todos os locais onde ele é criado.
Solução: injeção via construtor
A maneira mais simples de aplicar DI é passar a dependência pelo construtor:
class UserService(private val repository: UserRepository) { fun getUser(id: Int): User { return repository.findById(id) }}Agora, UserService não sabe como UserRepository é criado. Você pode passar qualquer implementação — um banco de dados real, uma camada falsa ou um mock para testes.
Inversão de Controle (IoC)
DI está intimamente ligado ao princípio Inversion of Control (IoC). Em vez de a classe controlar a criação de suas dependências, o controle é transferido para um contêiner externo ou fábrica. Isso torna o sistema com baixo acoplamento e facilmente extensível.
Injeção Manual de Dependências (Manual DI)
Para projetos pequenos ou fins educacionais, é possível dispensar frameworks. Vamos criar um contêiner de dependências simples manualmente.
Exemplo: contêiner de dependências
// Definindo a interface do repositóriointerface UserRepository { fun findById(id: Int): User}
// Implementaçãoclass UserRepositoryImpl : UserRepository { override fun findById(id: Int): User { // lógica para obter usuário do banco de dados return User(id, "User$id") }}
// Contêiner que reúne todas as dependênciasclass AppContainer { val userRepository: UserRepository = UserRepositoryImpl() val userService: UserService by lazy { UserService(userRepository) }}
// Usofun main() { val container = AppContainer() val service = container.userService println(service.getUser(1))}Vantagens: controle total, sem dependências externas, simplicidade de compreensão.
Desvantagens: à medida que o projeto cresce, o contêiner se expande, tornando difícil gerenciar o ciclo de vida dos objetos e seus escopos.
Injeção de Dependência com Hilt
Hilt é a biblioteca oficial do Google para DI em aplicativos Android com Kotlin. Ela é construída sobre o Dagger, mas é significativamente mais simples de configurar.
Configuração do Hilt
Adicione no build.gradle.kts (nível do projeto):
plugins { id("com.google.dagger.hilt.android") version "2.48" apply false}No build.gradle.kts do módulo do aplicativo:
plugins { id("com.google.dagger.hilt.android")}
dependencies { implementation("com.google.dagger:hilt-android:2.48") kapt("com.google.dagger:hilt-compiler:2.48")}