Koin é um framework DI para Kotlin que funciona sem geração de código, reflexão ou anotações. A biblioteca usa DSL para descrever módulos e injeta dependências através de um contêiner leve com suporte para Android, Ktor e Multiplatform. De acordo com a documentação oficial do Koin, o framework fornece módulos, escopos e suporte integrado para Jetpack Compose com mínimo boilerplate.
Principais pontos
Koin é um framework DI para Kotlin escrito em linguagem pura sem usar reflexão, anotações ou geração de código. Ao contrário do Dagger Hilt, que requer um processador de anotações e geração de código em tempo de compilação, o Koin funciona inteiramente em tempo de execução usando um DSL leve para descrever módulos.
A ideia principal do Koin é fornecer uma API simples para registrar e resolver dependências sem a necessidade de aprender conceitos complexos de grafos de dependências e árvores de componentes. O desenvolvedor descreve quais classes estão disponíveis para o contêiner, e o Koin as injeta automaticamente através do construtor ou delegados lentos by inject. O framework é totalmente compatível com Kotlin Multiplatform, permitindo uma abordagem DI única no Android, iOS e lado do servidor.
De acordo com a pesquisa da Comunidade de Desenvolvedores Kotlin (2025), o Koin é usado em 31% dos projetos Android comerciais, perdendo apenas para o Hilt (47%). A principal razão para escolhê-lo é a facilidade de configuração e a ausência de necessidade de geração de código, o que acelera a compilação do projeto.
Escolha o Koin para projetos médios e grandes onde um início rápido de desenvolvimento é importante, ou para soluções Kotlin Multiplatform onde o Hilt não está disponível por razões arquitetônicas.
O Koin não usa reflexão ou geração de código — todos os registros são construídos em funções inline com tipos reified que substituem o tipo concreto no corpo da função em tempo de compilação. Isso torna o Koin um dos frameworks DI mais leves em termos de tamanho final do APK: adicionar o Koin aumenta o tamanho da aplicação em apenas 100-150 KB, enquanto o Dagger Hilt adiciona cerca de 500 KB devido ao código gerado.
O contêiner Koin é inicializado através da função startKoin, que aceita uma lambda com configuração. Dentro desta lambda, os módulos com registros — o bloco de construção principal da lógica DI — são descritos.
A função startKoin cria um contêiner global acessível de qualquer lugar da aplicação através do GlobalContext, no entanto em projetos multimódulo é recomendado usar o KoinApplication para criar contêineres isolados. No Android, o AndroidContext é usado para inicialização, que é automaticamente vinculado ao ciclo de vida da Application. Os módulos são registrados através do parâmetro modules, que aceita uma lista de instâncias de Module.
val networkModule = module {
single {
OkHttpClient()
}
single {
Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
}
}
startKoin {
modules(networkModule)
}
Cada módulo contém definições através de single (singleton) ou factory (nova instância). As definições podem referenciar outras dependências registradas através de get(), formando um grafo de injeção sem especificação explícita de tipos e sem código boilerplate.
O Koin usa ativamente funções inline com parâmetros reified para inferência de tipo a partir do contexto. Isso permite escrever registros sem especificar explicitamente a classe: single { MyService() } determina automaticamente o tipo pelo valor de retorno da lambda.
Ao contrário do Dagger, o Koin não verifica o grafo de dependências em tempo de compilação — todos os erros são detectados em tempo de execução no primeiro acesso a uma dependência não resolvida. Isso é um compromisso que simplifica significativamente o código e acelera as compilações, mas requer cobertura de teste da configuração DI. Muitas equipes escolhem o Koin precisamente pela velocidade de desenvolvimento e simplicidade, apesar da falta de verificações em tempo de compilação.
Na versão Koin 3.5, apareceu a verificação experimental do grafo em tempo de compilação através do plugin Koin Annotations. O desenvolvedor adiciona as anotações @Module e @KoinComponent, e o plugin gera código de validação que é executado durante a compilação. No entanto, a principal vantagem do Koin — ausência de geração de código — se perde neste modo, portanto a maioria das equipes continua usando a abordagem DSL clássica com verificações em tempo de execução através de testes.
Koin fornece várias maneiras de injetar dependências: by inject(), get() e passagem direta por construtor. A escolha depende do contexto de uso.
O delegado by inject é o método de injeção mais comum em ViewModels e fragmentos Android. A dependência é inicializada lentamente — apenas no primeiro acesso à propriedade. Isso é eficiente para serviços que consomem muitos recursos que podem não ser necessários imediatamente.
class MainViewModel : ViewModel() {
private val repository: UserRepository by inject()
fun loadUsers() {
repository.fetchAll()
}
}
A função get retorna uma instância de dependência imediatamente. É usada dentro de lambdas de fábrica durante o registro ou quando a dependência é necessária em um contexto síncrono sem inicialização lenta. Ao contrário de by inject(), get() não suporta carregamento lento e requer que o contêiner já esteja inicializado no momento da chamada.
Escopo no Koin é um mecanismo para vincular o tempo de vida das dependências a um componente específico, como uma Activity, Fragment ou sessão personalizada. Esta é uma funcionalidade chave para gerenciamento de memória em aplicativos Android.
A função scope dentro de um módulo cria um escopo que vive enquanto o componente vinculado viver. Todas as dependências registradas no escopo são destruídas quando ele é fechado, evitando vazamentos de memória.
val userScope = module {
scope<UserSession> {
scoped {
UserRepository(get())
}
scoped {
SessionManager(get())
}
}
}
A função scoped registra uma dependência que só existirá dentro do escopo. Quando o escopo é fechado, todos os objetos scoped ficam disponíveis para o coletor de lixo.
single registra uma única instância para toda a aplicação com inicialização lenta. Usado para serviços sem estado: clientes de rede, caches, registradores.
factory cria uma nova instância a cada chamada get(). É aplicado a ViewModel, repositórios e objetos com estado onde uma instância nova é importante a cada acesso.
Integrar Koin em um projeto Android é mínimo: basta adicionar uma dependência no build.gradle e chamar startKoin no Application.onCreate. O Koin fornece módulos para integração com Jetpack Compose, Navigation e WorkManager, tornando-se uma alternativa completa ao Hilt.
A biblioteca especial koin-android-compose permite injetar dependências diretamente em funções Composable através de koinViewModel() e koinInject(). Isso elimina a necessidade de passar o contêiner pelos parâmetros de cada tela e torna o código ViewModel mais limpo através da vinculação automática ao ciclo de vida.
De acordo com o Google I/O 2024, o Jetpack Compose se tornou o framework principal para novos projetos Android. O Koin fornece suporte nativo ao Compose sem configuração adicional, vinculando automaticamente os escopos ao ciclo de vida do ViewModel através de koinViewModel() levando em conta o contexto de corrotinas.
Para testes, o Koin fornece as funções koinTest e koinTestRule, criando um contêiner de teste isolado com módulos de teste e fechando-o automaticamente após a conclusão do teste. Isso garante o isolamento dos testes e evita vazamentos de estado entre casos de teste.
A integração do Koin com Jetpack Navigation é implementada através do módulo koin-androidx-navigation. O ViewModel de cada tela recebe automaticamente as dependências através de by viewModel() com passagem de SavedStateHandle para preservar o estado ao girar a tela e recuperá-lo após suspensão do aplicativo.
Para testes unitários de ViewModel com Koin, usa-se koinTestRule da biblioteca koin-test-junit5 ou koin-test-junit4. A regra cria um contêiner isolado com módulos de teste antes de cada teste e o fecha automaticamente após a conclusão, evitando vazamentos de estado entre casos de teste. As dependências reais são substituídas por mocks via MockK: um módulo com registros single
Uma das principais características do Koin 3.x é o suporte ao Ktor para criar aplicativos servidor em Kotlin e Compose Multiplatform para aplicativos desktop. Isso torna o Koin o único framework DI que cobre todas as três plataformas Kotlin sem mudar o paradigma de injeção. O módulo koin-ktor permite registrar dependências através de install(Koin) no bloco Application e injetar serviços nas rotas através de by inject() assim como no Android. Isso faz do Koin uma solução DI universal para projetos Kotlin de qualquer arquitetura — desde um cliente móvel até um backend de servidor.
A integração do Koin com Jetpack Navigation através do módulo koin-androidx-navigation elimina a necessidade de criar manualmente um ViewModelProvider.Factory para cada tela. Para projetos multimódulo, o Koin suporta carregamento lento de módulos através de loadKoinModules, permitindo que cada módulo de funcionalidade conecte sua configuração DI de forma independente.
Perguntas frequentes
Koin funciona em tempo de execução sem geração de código ou anotações, o que acelera as compilações mas não verifica o grafo de dependências em tempo de compilação. Hilt gera código em tempo de compilação e detecta erros DI mais cedo, mas requer configuração complexa e retarda a compilação.
Sim, Koin suporta totalmente Kotlin Multiplatform. A biblioteca koin-core funciona em todas as plataformas Kotlin, enquanto koin-android e koin-compose adicionam capacidades específicas de plataforma para Android e iOS respectivamente.
Dependências circulares levam a StackOverflowError em tempo de execução. O Koin não as detecta automaticamente. A solução é refatorar a arquitetura: extrair uma interface comum, usar o padrão Listener/Observer ou quebrar o ciclo através de uma fábrica com inicialização diferida.
No Android, os escopos podem ser vinculados ao ciclo de vida da Activity ou Fragment através do AndroidScope. Quando o componente é destruído, o Koin fecha automaticamente o escopo correspondente. Em escopos personalizados (sessão de usuário), o fechamento é feito manualmente chamando scope.close.
Use a função koinTest do módulo koin-test. Ela cria um contêiner isolado com módulos de teste que fecha automaticamente após o teste. As dependências reais são substituídas por mocks através de um módulo com Mockito ou MockK.
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