---
title: "Hilt no Android com Kotlin: Módulos, Scopes e Testes | Kotlin Brasil"
url: "https://kotlin.dev.br/blog/hilt-android-kotlin-modulos-scopes-testes-2026/"
markdown_url: "https://kotlin.dev.br/blog/hilt-android-kotlin-modulos-scopes-testes-2026.MD"
description: "Aprenda a usar Hilt em apps Android com Kotlin em 2026: módulos, scopes, ViewModels, qualifiers, testes, substituição de dependências e erros comuns."
date: "2026-06-01"
author: "Karina Melo"
---

# Hilt no Android com Kotlin: Módulos, Scopes e Testes | Kotlin Brasil

Aprenda a usar Hilt em apps Android com Kotlin em 2026: módulos, scopes, ViewModels, qualifiers, testes, substituição de dependências e erros comuns.


Injeção de dependência parece um assunto abstrato até o primeiro app Android crescer de verdade. No começo, instanciar um `Repository` dentro do `ViewModel` funciona. Depois entram API, banco local, analytics, autenticação, feature flags, cache, testes, ambiente de homologação e múltiplos módulos. Sem um grafo claro de dependências, cada tela começa a criar objetos do seu jeito e o projeto fica difícil de testar.

No ecossistema Android com Kotlin, **Hilt** é o caminho oficial mais comum para resolver esse problema. Ele simplifica o Dagger, integra com `Application`, `Activity`, `Fragment`, `ViewModel`, WorkManager e testes, e valida boa parte do grafo em tempo de compilação. Em 2026, dominar Hilt continua sendo um diferencial forte para vagas Android, principalmente quando combinado com [Jetpack Compose](/guias/guia-jetpack-compose/), [MVVM com Kotlin](/tutoriais/kotlin-mvvm-tutorial/), [Room](/tutoriais/kotlin-room-database-tutorial/), [Ktor Client resiliente](/blog/ktor-client-resiliente-timeout-retry-circuit-breaker-2026/) e [testes Android com Compose e Maestro](/guias/testes-android-compose-maestro/).

Este guia mostra como pensar Hilt em um app real: onde configurar, quando criar módulos, como escolher scopes, como usar qualifiers, como injetar ViewModels e como substituir dependências em testes sem transformar tudo em boilerplate.

## O que Hilt resolve no projeto Android?

Hilt cria e entrega objetos para as classes que precisam deles. Em vez de uma tela construir manualmente `ApiClient`, `UsuarioRepository`, `Database`, `Logger` e `Analytics`, você declara como cada dependência nasce e deixa o framework montar o grafo.

Isso resolve problemas práticos:

- evita duplicação de configuração em várias telas;
- centraliza ciclo de vida de objetos caros, como banco e clientes HTTP;
- facilita trocar implementação real por fake em testes;
- deixa dependências explícitas no construtor;
- reduz singletons globais improvisados;
- antecipa erros de configuração durante o build.

A ideia não é “usar framework porque é moderno”. A ideia é manter a arquitetura legível quando o app passa de três telas para trinta.

## Configuração básica no Gradle

Em um projeto Android com Gradle Kotlin DSL, você normalmente adiciona o plugin do Hilt no projeto e no módulo do app. As versões devem acompanhar o Android Gradle Plugin e a documentação atual, mas a estrutura é esta:

```kotlin
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("com.google.dagger.hilt.android")
    id("com.google.devtools.ksp")
}

dependencies {
    implementation("com.google.dagger:hilt-android:2.52")
    ksp("com.google.dagger:hilt-android-compiler:2.52")

    implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
    ksp("androidx.hilt:hilt-compiler:1.2.0")
}
```

Muitos projetos antigos usam `kapt`. Em bases novas, vale preferir KSP quando a combinação de versões do projeto permite. Se você está organizando versões em `libs.versions.toml`, veja também o guia de [Gradle Version Catalog em Kotlin](/blog/gradle-version-catalog-kotlin-2026/).

No `Application`, marque a classe com `@HiltAndroidApp`:

```kotlin
@HiltAndroidApp
class AppKotlinBrasil : Application()
```

Esse ponto inicial permite que Hilt gere os componentes necessários para o app inteiro.

## Injeção por construtor é o padrão preferido

Sempre que possível, injete dependências pelo construtor. Isso deixa claro o que a classe precisa para funcionar e facilita testes unitários sem Android.

```kotlin
class UsuarioRepository @Inject constructor(
    private val api: UsuarioApi,
    private val dao: UsuarioDao,
    private val logger: AppLogger,
) {
    suspend fun buscarPerfil(id: String): Usuario {
        logger.debug("Buscando perfil $id")
        return dao.buscar(id) ?: api.buscarUsuario(id).also { dao.salvar(it) }
    }
}
```

Repare que o repository não sabe como `UsuarioApi`, `UsuarioDao` ou `AppLogger` são criados. Ele apenas declara suas necessidades. Essa separação combina bem com Clean Architecture: casos de uso e repositories dependem de contratos, enquanto detalhes de rede, banco e analytics ficam nas bordas.

## ViewModel com Hilt e Compose

Para ViewModels, use `@HiltViewModel` e injete dependências pelo construtor:

```kotlin
@HiltViewModel
class PerfilViewModel @Inject constructor(
    private val buscarPerfil: BuscarPerfilUseCase,
) : ViewModel() {
    private val _estado = MutableStateFlow(PerfilState())
    val estado: StateFlow<PerfilState> = _estado.asStateFlow()

    fun carregar(id: String) {
        viewModelScope.launch {
            _estado.value = PerfilState(carregando = true)
            runCatching { buscarPerfil(id) }
                .onSuccess { _estado.value = PerfilState(usuario = it) }
                .onFailure { _estado.value = PerfilState(erro = "Não foi possível carregar") }
        }
    }
}
```

Em uma tela Compose com Navigation, recupere o ViewModel com `hiltViewModel()`:

```kotlin
@Composable
fun PerfilRoute(
    viewModel: PerfilViewModel = hiltViewModel(),
) {
    val estado by viewModel.estado.collectAsStateWithLifecycle()
    PerfilScreen(estado = estado, onRetry = { viewModel.carregar("me") })
}
```

Evite injetar dependências diretamente em Composables. A tela deve receber estado e callbacks. O ViewModel conversa com os casos de uso; os casos de uso conversam com repositories. Essa divisão mantém UI, regra de negócio e infraestrutura separadas.

## Quando criar módulos Hilt

A injeção por construtor cobre muita coisa, mas alguns objetos precisam de módulo porque são criados por builder, vêm de biblioteca externa ou dependem de configuração manual. Exemplos: Retrofit, OkHttp, Room, DataStore, Firebase, Apollo, clientes Ktor e implementações de interfaces.

```kotlin
@Module
@InstallIn(SingletonComponent::class)
object RedeModule {
    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient =
        OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .build()

    @Provides
    @Singleton
    fun provideRetrofit(client: OkHttpClient): Retrofit =
        Retrofit.Builder()
            .baseUrl("https://api.exemplo.com/")
            .client(client)
            .addConverterFactory(MoshiConverterFactory.create())
            .build()

    @Provides
    fun provideUsuarioApi(retrofit: Retrofit): UsuarioApi =
        retrofit.create(UsuarioApi::class.java)
}
```

Use módulos para construção, não para esconder arquitetura. Se todo objeto do app vira um método `@Provides`, provavelmente você está perdendo a vantagem da injeção por construtor.

## Entendendo scopes sem exagerar

Scopes dizem por quanto tempo uma instância deve viver. O mais conhecido é `@Singleton`, que cria uma instância por aplicação. Ele faz sentido para banco, cliente HTTP, storage de preferências e serviços compartilhados.

Mas nem tudo precisa ser singleton. Um use case geralmente pode ser sem escopo. Um objeto leve e stateless pode ser recriado sem problema. Scopes demais deixam o grafo rígido e podem segurar referências mais tempo do que deveriam.

Alguns componentes comuns:

| Scope | Quando usar | Cuidado |
| --- | --- | --- |
| `@Singleton` | banco, client HTTP, repository com cache global | não guardar referência de Activity ou View |
| `@ActivityRetainedScoped` | estado compartilhado entre ViewModels da mesma Activity | não confundir com ciclo de tela Compose |
| `@ViewModelScoped` | dependência específica daquele ViewModel | não usar para objeto que precisa sobreviver ao app |
| sem scope | use cases simples, mappers, formatadores | ok para objetos baratos |

A regra prática: comece sem scope. Adicione scope quando houver motivo claro de ciclo de vida, custo de criação ou compartilhamento de estado.

## Interfaces, implementações e `@Binds`

Quando uma camada depende de interface, use `@Binds` para dizer qual implementação concreta Hilt deve entregar.

```kotlin
interface AuthRepository {
    suspend fun usuarioAtual(): Usuario?
}

class AuthRepositoryRemoto @Inject constructor(
    private val api: AuthApi,
    private val tokenStore: TokenStore,
) : AuthRepository {
    override suspend fun usuarioAtual(): Usuario? = api.me(tokenStore.token())
}

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
    @Binds
    @Singleton
    abstract fun bindAuthRepository(
        impl: AuthRepositoryRemoto,
    ): AuthRepository
}
```

Prefira `@Binds` para interface-implementação simples. Use `@Provides` quando precisar executar lógica de criação.

## Qualifiers para dependências do mesmo tipo

Às vezes existem duas dependências do mesmo tipo: duas URLs base, dois dispatchers, dois clientes HTTP ou duas instâncias de analytics. Hilt não consegue adivinhar qual usar. Para isso, use qualifiers.

```kotlin
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ApiBaseUrl

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class CdnBaseUrl

@Module
@InstallIn(SingletonComponent::class)
object UrlModule {
    @Provides
    @ApiBaseUrl
    fun provideApiBaseUrl(): String = "https://api.exemplo.com/"

    @Provides
    @CdnBaseUrl
    fun provideCdnBaseUrl(): String = "https://cdn.exemplo.com/"
}
```

Na classe consumidora:

```kotlin
class ImagemService @Inject constructor(
    @CdnBaseUrl private val cdnBaseUrl: String,
)
```

Não use `@Named("api")` em tudo por preguiça. Qualifiers próprios são mais refatoráveis, documentam intenção e evitam erro de string.

## Testes com substituição de dependências

Um dos maiores ganhos de Hilt é trocar dependências reais por fakes em testes de integração Android. Para testes instrumentados, use `@HiltAndroidTest` e `HiltAndroidRule`:

```kotlin
@HiltAndroidTest
class PerfilScreenTest {
    @get:Rule
    val hiltRule = HiltAndroidRule(this)

    @Before
    fun setup() {
        hiltRule.inject()
    }
}
```

Para substituir um módulo inteiro em teste, use `@TestInstallIn`:

```kotlin
@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [RedeModule::class]
)
object FakeRedeModule {
    @Provides
    @Singleton
    fun provideUsuarioApi(): UsuarioApi = FakeUsuarioApi()
}
```

Isso permite testar tela, navegação e ViewModel contra dados previsíveis, sem bater na API real. Em testes unitários puros de ViewModel ou use case, você nem precisa subir Hilt: crie a classe manualmente com fakes no construtor. Hilt é ferramenta de composição do app, não obrigação para todo teste.

## Hilt, WorkManager e tarefas em background

Para workers, Hilt também ajuda bastante. Em apps offline-first, é comum um `Worker` precisar de repository, logger e sincronizador. Use `@HiltWorker` e `@AssistedInject`:

```kotlin
@HiltWorker
class SyncWorker @AssistedInject constructor(
    @Assisted context: Context,
    @Assisted params: WorkerParameters,
    private val syncRepository: SyncRepository,
) : CoroutineWorker(context, params) {
    override suspend fun doWork(): Result =
        runCatching { syncRepository.sincronizarPendentes() }
            .fold(
                onSuccess = { Result.success() },
                onFailure = { Result.retry() },
            )
}
```

Combine isso com o guia de [WorkManager com Kotlin](/blog/workmanager-kotlin-android-2026/) para não misturar fila offline, regra de negócio e agendamento em uma classe gigante.

## Erros comuns com Hilt

Os problemas mais frequentes em projetos Android não vêm do Hilt em si, mas de uso apressado:

1. **Injetar contexto errado:** use `@ApplicationContext` para dependências de app. Evite guardar `Activity` em singletons.
2. **Transformar tudo em singleton:** singletons demais criam estado global disfarçado.
3. **Colocar regra de negócio em módulos:** módulos devem construir objetos, não decidir fluxo de produto.
4. **Injetar em Composables diretamente:** prefira ViewModel e parâmetros de tela.
5. **Usar Service Locator manual junto com Hilt:** misturar padrões aumenta confusão.
6. **Não testar o grafo:** pelo menos uma suíte instrumentada deve garantir que o app sobe com dependências reais.
7. **Qualifiers genéricos:** `@Named("x")` espalhado vira armadilha em refactors.

Se o build falha com erro de binding ausente, leia a mensagem de baixo para cima: Hilt geralmente mostra qual classe pediu a dependência, qual tipo faltou e em qual componente.

## Hilt ou Koin?

Koin continua sendo uma opção Kotlin-first, leve e muito usada, especialmente em projetos multiplataforma. Hilt costuma ser melhor quando o app é Android nativo, segue recomendações oficiais do Google e quer validação mais forte em tempo de compilação. A comparação detalhada está em [Koin vs Dagger/Hilt](/comparacoes/koin-vs-dagger/).

Para um app Android profissional em 2026, escolher Hilt é uma decisão conservadora e forte. A documentação, exemplos oficiais, integração com Jetpack e reconhecimento em entrevistas pesam bastante. Para KMP com compartilhamento amplo, Koin ou injeção manual por construtor podem ser mais naturais.

## Checklist antes de levar para produção

Antes de considerar o setup de Hilt pronto, revise:

- `@HiltAndroidApp` configurado no `Application`;
- dependências principais usando injeção por construtor;
- módulos apenas para objetos que precisam de construção especial;
- `@Singleton` limitado a objetos realmente compartilhados;
- qualifiers próprios para valores do mesmo tipo;
- ViewModels com `@HiltViewModel` e estado exposto por `StateFlow`;
- testes com fakes para API, banco ou storage sensível;
- nenhum singleton guardando referência de `Activity`, `Fragment` ou `View`;
- CI rodando build que valide geração de código.

Hilt não substitui arquitetura. Ele só torna a composição da arquitetura mais segura. O ganho aparece quando cada classe declara claramente o que precisa, quando ciclos de vida são escolhidos com intenção e quando testes conseguem trocar bordas reais por fakes sem gambiarra.

Para quem quer crescer como dev Android Kotlin, Hilt é um desses assuntos que conectam código, arquitetura e empregabilidade. Ele aparece em projetos reais porque resolve um problema real: manter um app grande testável, modular e previsível. Domine o básico, evite scopes desnecessários, escreva módulos pequenos e use testes para provar que o grafo funciona.

Também vale olhar para outros ecossistemas de backend e mobile: em aplicações server-side, frameworks de DI e configuração existem há anos em Java, Spring e .NET; em stacks mais explícitas, como <a href="https://golang.com.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go para APIs</a>, a composição manual é mais comum. Entender esses contrastes ajuda a usar Hilt pelo motivo certo: reduzir acoplamento sem esconder demais o funcionamento do app.
