Escolher entre Koin e Hilt parece uma decisão pequena no começo de um app Android em Kotlin. Na prática, ela influencia tempo de build, onboarding do time, testes, modularização, integração com Jetpack Compose, suporte a Kotlin Multiplatform e até a forma como erros aparecem em produção. Em 2026, as duas opções são maduras, mas resolvem o problema de injeção de dependência com filosofias bem diferentes.
Hilt é a escolha oficial do ecossistema Android, construída sobre Dagger, com validação forte em tempo de compilação e integração profunda com ViewModel, WorkManager, Navigation, instrumented tests e componentes Android. Koin é mais leve na experiência de uso: usa DSL Kotlin, exige menos anotações, costuma ser mais fácil de explicar para quem está aprendendo e funciona bem em projetos multiplataforma.
A pergunta certa não é “qual é melhor?”. A pergunta certa é: qual reduz risco para o tipo de projeto, time e arquitetura que você tem agora? Este guia compara Koin e Hilt com critérios práticos para apps Android modernos escritos em Kotlin.
O que DI precisa resolver em um app Kotlin
Injeção de dependência não serve apenas para “evitar new”. Ela cria um contrato claro entre camadas. Um ViewModel não deve saber como construir um client HTTP, abrir banco local, configurar logger, montar repository ou escolher implementação fake em teste. Ele deve receber dependências prontas e focar no estado da tela.
Em um app Android real, DI normalmente conecta:
- clients HTTP com Ktor, Retrofit ou GraphQL;
- DAOs do Room e stores locais;
- repositories e use cases;
- dispatchers de coroutines;
- loggers, feature flags e analytics;
- workers em background;
- ViewModels usados por telas Compose ou Fragments;
- implementações fake para testes.
Se você ainda está estruturando camadas, vale revisar também o guia de Clean Architecture em Kotlin, MVVM com Kotlin e o guia base de injeção de dependência em Kotlin.
Koin: DSL Kotlin, menos cerimônia
Koin costuma agradar porque o código parece Kotlin comum. Você declara módulos com single, factory e viewModel, depois resolve dependências com get() ou APIs próprias para Android e Compose.
val appModule = module {
single { HttpClientFactory.create() }
single<UsuarioApi> { UsuarioApiKtor(get()) }
single<UsuarioRepository> { UsuarioRepositoryImpl(get()) }
factory { ObservarPerfilUseCase(get()) }
viewModel { PerfilViewModel(get()) }
}
Para muitos times, isso reduz fricção. Um desenvolvedor que já entende Kotlin consegue ler o grafo sem aprender Dagger imediatamente. Também é simples criar módulos diferentes para teste, preview ou ambiente local.
val testeModule = module {
single<UsuarioRepository> { FakeUsuarioRepository() }
}
O ponto de atenção é que parte dos erros aparece em runtime. Se você esquece uma dependência ou cria um ciclo ruim, o app pode compilar e falhar ao iniciar a tela ou o teste. Koin tem recursos de verificação, mas o time precisa colocá-los no pipeline. Sem essa disciplina, a simplicidade inicial vira risco.
Hilt: validação forte e integração Android
Hilt usa anotações e geração de código para montar o grafo. A configuração parece mais verbosa, mas o ganho é que muitos problemas são detectados no build. Para apps grandes, essa previsibilidade pesa.
@HiltViewModel
class PerfilViewModel @Inject constructor(
private val observarPerfil: ObservarPerfilUseCase,
) : ViewModel()
Um módulo típico fica assim:
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindUsuarioRepository(
impl: UsuarioRepositoryImpl,
): UsuarioRepository
}
A integração com Android também é forte. @AndroidEntryPoint, @HiltViewModel, @ApplicationContext, scopes, testes instrumentados e integrações oficiais reduzem decisões manuais. Se seu app é Android puro, com muitos módulos e equipe acostumada a ferramentas Google, Hilt tende a ser uma escolha segura.
O custo aparece em build e curva de aprendizado. Mensagens de erro de Dagger podem assustar no começo. Projetos muito pequenos podem sentir que há cerimônia demais para pouco ganho.
Comparação prática
| Critério | Koin | Hilt |
|---|---|---|
| Curva de aprendizado | Mais simples para Kotlin puro | Mais íngreme por Dagger/anotações |
| Validação | Precisa de verificação/testes de grafo | Forte em tempo de compilação |
| Build | Menos geração de código | Pode aumentar tempo de build |
| Runtime | Resolução em runtime | Grafo gerado/otimizado |
| Android oficial | Bom suporte | Melhor integração oficial |
| Compose | Ergonomia boa com koinViewModel() | Ergonomia boa com hiltViewModel() |
| KMP | Forte candidato | Não é multiplatform |
| Times grandes | Funciona, mas exige disciplina | Geralmente mais previsível |
| Protótipos/MVPs | Muito produtivo | Pode ser excesso |
Essa tabela não deve ser lida como placar. Ela mostra trade-offs. Um app pequeno pode ficar excelente com Hilt se o time já domina Dagger. Um app grande pode usar Koin com sucesso se tiver verificação automatizada e arquitetura bem modularizada.
Jetpack Compose: as duas opções funcionam
Em Compose, a tela deve receber estado e callbacks, não sair resolvendo dependência profunda. O lugar mais comum para DI é o ViewModel.
Com Hilt:
@Composable
fun PerfilRoute(
viewModel: PerfilViewModel = hiltViewModel(),
) {
val state by viewModel.state.collectAsStateWithLifecycle()
PerfilScreen(
state = state,
onRefresh = viewModel::refresh,
)
}
Com Koin:
@Composable
fun PerfilRoute(
viewModel: PerfilViewModel = koinViewModel(),
) {
val state by viewModel.state.collectAsStateWithLifecycle()
PerfilScreen(
state = state,
onRefresh = viewModel::refresh,
)
}
A diferença não está no composable em si. Está no grafo por trás. Hilt tende a proteger mais em compile time. Koin tende a ser mais flexível para trocar módulos em testes, previews e projetos compartilhados.
Para UI moderna, DI também conversa com temas como testes de screenshot no Compose, Android offline-first com Kotlin e WorkManager com Kotlin, porque todos eles dependem de repositories substituíveis e efeitos colaterais isolados.
Testes: onde a decisão aparece rápido
DI boa deixa teste mais barato. O ViewModel pode receber um repository fake; o repository pode receber um client fake; o worker pode receber um executor controlado.
Com Hilt, testes instrumentados usam @HiltAndroidTest, HiltAndroidRule e substituição de módulos. É poderoso, mas exige configuração correta.
@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [RepositoryModule::class],
)
object FakeRepositoryModule {
@Provides
fun provideUsuarioRepository(): UsuarioRepository = FakeUsuarioRepository()
}
Com Koin, a troca costuma ser direta:
@Before
fun setup() {
startKoin {
modules(appModule, testeModule)
}
}
@After
fun tearDown() {
stopKoin()
}
A simplicidade de Koin é boa para testes unitários e módulos pequenos. Hilt brilha quando você quer testar o app Android inteiro com a mesma estrutura de produção e substituições explícitas.
Independentemente da ferramenta, não use DI como desculpa para esconder design ruim. Se um ViewModel precisa de dez dependências, talvez o problema seja falta de use cases ou responsabilidades misturadas. Se um repository cria threads, clients e DAOs internamente, a ferramenta de DI só está decorando acoplamento.
Performance e tempo de build
Em runtime, Hilt tende a ser previsível porque o grafo foi gerado no build. Koin resolve dependências dinamicamente, mas em muitos apps o custo é irrelevante perto de rede, banco e renderização. O problema real costuma ser outro: tempo de build versus segurança.
Hilt pode deixar builds mais pesados, especialmente em projetos Android grandes com muitos módulos, kapt ou configuração antiga. KSP e melhorias no ecossistema ajudam, mas a geração de código ainda precisa ser considerada. Koin reduz essa carga, mas exige que o pipeline rode testes de inicialização do grafo.
Uma prática saudável com Koin é criar um teste que sobe o grafo principal e resolve componentes críticos. Ele não substitui todos os testes, mas pega dependências faltando antes de chegar ao usuário.
@Test
fun `grafo principal inicia`() {
koinApplication {
modules(appModule)
}.checkModules()
}
A API exata pode variar conforme a versão do Koin, então confira a documentação atual. O ponto é cultural: se a ferramenta valida menos no compilador, o time precisa validar no CI.
Kotlin Multiplatform muda a conversa
Se o projeto é Android puro, Hilt é candidato forte. Se o projeto usa Kotlin Multiplatform, Koin ganha espaço porque pode operar fora do Android. Em KMP, você pode compartilhar repositories, use cases, clients Ktor e configuração de módulos comuns.
val sharedModule = module {
single { Json { ignoreUnknownKeys = true } }
single { ApiClient(get()) }
single<ProdutoRepository> { ProdutoRepositoryImpl(get()) }
}
No Android, você complementa com dependências específicas da plataforma. No iOS, inicializa o grafo compartilhado de outra forma. Isso combina com a direção descrita no artigo sobre nova estrutura de projetos KMP em 2026 e no tutorial de Kotlin Multiplatform.
Hilt continua útil em um app Android que consome um módulo KMP, mas ele não resolve DI do lado iOS. Uma arquitetura híbrida é possível: Koin no módulo compartilhado e Hilt no app Android. Só tome cuidado para não criar dois mundos que ninguém entende.
Quando escolher Koin
Escolha Koin quando:
- o time valoriza simplicidade e DSL Kotlin;
- o projeto é pequeno ou médio e precisa avançar rápido;
- há Kotlin Multiplatform no horizonte;
- você quer módulos fáceis de trocar em testes e previews;
- o time aceita adicionar verificação de grafo no CI;
- a equipe ainda não domina Dagger/Hilt e a cerimônia atrapalharia entrega.
Koin é especialmente confortável para produtos em descoberta, apps internos, módulos compartilhados e times que preferem clareza explícita em Kotlin a anotações espalhadas. Só não trate Koin como “sem arquitetura”. Ele facilita resolver dependências, mas não decide seus limites de camada.
Quando escolher Hilt
Escolha Hilt quando:
- o app é Android puro e deve crescer por anos;
- o time já conhece Dagger ou quer seguir o caminho oficial do Android;
- validação em tempo de compilação é prioridade;
- há muitos módulos, flavors, workers e testes instrumentados;
- você quer integração direta com
ViewModel, Navigation e componentes Android; - falhas de grafo em runtime seriam caras demais.
Hilt é uma boa aposta para apps corporativos, fintechs, healthtechs, marketplaces e produtos com muitas telas e ciclos longos de manutenção. O custo de aprender a ferramenta tende a se pagar quando o grafo cresce.
Erros comuns
O primeiro erro é misturar resolução de dependência dentro de composables de UI pura. Deixe a rota resolver o ViewModel e passe estado para o componente visual. Isso mantém previews e testes mais simples.
O segundo é injetar tudo. Classes de domínio pequenas podem receber parâmetros normais. Nem toda função precisa virar service. DI deve reduzir acoplamento, não transformar o projeto em uma coleção de singletons.
O terceiro é deixar scopes implícitos. Em Hilt, entenda SingletonComponent, ActivityRetainedComponent e escopos de ViewModel. Em Koin, diferencie single, factory e scopes quando realmente precisar deles.
O quarto é ignorar observabilidade. Se uma dependência crítica falha ao inicializar, registre erro útil. Em apps backend ou Android com sincronização pesada, combine DI com observabilidade em Kotlin para entender falhas reais.
Recomendação rápida
Para um app Android profissional e grande em 2026, eu começaria por Hilt, principalmente se a equipe já trabalha no ecossistema Google e não há KMP planejado. A validação forte e a integração oficial reduzem risco no longo prazo.
Para um projeto Kotlin Multiplatform, um MVP mobile, um app interno ou uma equipe que precisa de velocidade sem entrar no mundo Dagger agora, eu escolheria Koin e colocaria verificação do grafo no CI desde o primeiro dia.
A escolha não precisa ser ideológica. O objetivo é manter dependências explícitas, testes viáveis e arquitetura compreensível. Koin e Hilt conseguem entregar isso quando usados com disciplina. O que não funciona é deixar cada tela construir seu próprio mundo.
Se você está montando a base de um app Kotlin, leia também Kotlin para Android, testes com JUnit 5 e MockK, CI/CD para Kotlin e, para comparar com outras stacks de backend e automação, veja como Python Brasil aborda produtividade e testes.