Introdução: O que é Injeção de Dependência e por que você precisa dela em Kotlin?
Injeção de Dependência (DI) é um padrão de projeto que torna o código mais flexível, testável e sustentável. Em vez de um objeto criar suas próprias dependências, o DI as transmite (injeta) externamente. Em Kotlin, com sua sintaxe concisa e recursos poderosos, o DI se torna uma ferramenta especialmente elegante.
Imagine que você está escrevendo um aplicativo para pedir pizza. Você tem a classe OrderService, que precisa enviar notificações. Sem DI, ela ficaria assim:
class OrderService { private val notifier = EmailNotifier() // Acoplamento rígido
fun placeOrder(order: Order) { // Lógica do pedido notifier.send("Seu pedido foi recebido!") }}Problema: você não pode substituir facilmente EmailNotifier por SmsNotifier ou testar OrderService sem enviar e-mails reais. O DI resolve esse problema injetando a dependência através do construtor:
class OrderService(private val notifier: Notifier) { fun placeOrder(order: Order) { notifier.send("Seu pedido foi recebido!") }}Agora, OrderService não sabe como a notificação é enviada. Essa é a essência do DI: baixo acoplamento e alta reutilização.
Principais formas de implementar DI em Kotlin
Em Kotlin, existem três abordagens principais para injeção de dependências: DI manual, uso de bibliotecas (Koin, Dagger 2) e injeção através de frameworks (por exemplo, Spring). Vamos analisar cada uma na prática.
1. Injeção Manual de Dependências (Manual DI)
A maneira mais simples é passar dependências através de construtores ou setters. Isso não requer bibliotecas adicionais e é ideal para projetos pequenos.
Exemplo: Vamos criar um serviço simples para processar pagamentos.
// Interface do gateway de pagamentointerface PaymentGateway { fun process(amount: Double): Boolean}
// Implementação via PayPalclass PayPalGateway : PaymentGateway { override fun process(amount: Double): Boolean { println("Processando pagamento $amount via PayPal") return true }}
// Serviço de pedidos com injeção de dependênciaclass OrderService(private val paymentGateway: PaymentGateway) { fun checkout(total: Double) { if (paymentGateway.process(total)) { println("Pedido realizado!") } }}
// Ponto de entrada — "fábrica" ou "container"fun main() { val gateway = PayPalGateway() val service = OrderService(gateway) service.checkout(100.0)}Vantagens: Simplicidade, controle total, sem dependências de bibliotecas.
Desvantagens: À medida que o projeto cresce, o gerenciamento manual de todas as dependências se torna complexo (é necessário criar um "container" ou "fábrica" manualmente).
2. Injeção de Dependências com Koin
Koin é uma biblioteca leve para DI, escrita em Kotlin. Ela não usa reflexão e não requer geração de código, tornando-a ideal para projetos pequenos e médios.
Instalação: Adicione ao build.gradle.kts:
implementation("io.insert-koin:koin-core:3.5.6")Exemplo de uso:
import org.koin.core.context.startKoinimport org.koin.dsl.module
// Módulo de dependênciasval appModule = module { single<PaymentGateway> { PayPalGateway() } factory { OrderService(get()) } // get() obtém automaticamente o PaymentGateway}
fun main() { startKoin { modules(appModule) }
// Obtém o serviço do container val service: OrderService = org.koin.core.component.get() service.checkout(200.0)}Conceitos-chave do Koin:
single— cria uma única instância (singleton)