Integrar validação de CPF em um app Android com Jetpack Compose significa combinar a reatividade do framework declarativo com chamadas à API do CPFHub.io via coroutines, seguindo o padrão MVVM recomendado pelo Google — o resultado é um formulário que valida o CPF localmente em tempo real e confirma a identidade do usuário via API antes de avançar no fluxo.
Introdução
Jetpack Compose é o toolkit moderno do Android para construir interfaces declarativas em Kotlin. Ao integrar validação de CPF com Compose, você combina a reatividade do framework com chamadas à API do CPFHub.io usando coroutines e o padrão MVVM recomendado pelo Google.
Configurando a injeção de dependências com Hilt
Hilt simplifica a injeção de dependências no Android, garantindo que o serviço de CPF seja criado e gerenciado corretamente.
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import okhttp3.OkHttpClient
import okhttp3.Interceptor
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
val authInterceptor = Interceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("x-api-key", BuildConfig.CPFHUB_API_KEY)
.build()
chain.proceed(request)
}
return OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(15, java.util.concurrent.TimeUnit.SECONDS)
.build()
}
@Provides
@Singleton
fun provideCPFApi(client: OkHttpClient): CPFApi {
return Retrofit.Builder()
.baseUrl("https://api.cpfhub.io/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(CPFApi::class.java)
}
}
| Componente | Escopo | Função |
|---|---|---|
| NetworkModule | Singleton | Provê instâncias únicas de rede |
| OkHttpClient | Singleton | Cliente HTTP com interceptor de autenticação |
| CPFApi | Singleton | Interface Retrofit para a API de CPF |
- BuildConfig.CPFHUB_API_KEY -- chave armazenada no gradle.properties, nunca no código fonte
- @Singleton -- garante uma única instância do cliente HTTP em toda a aplicação
- @InstallIn(SingletonComponent) -- o módulo vive durante todo o ciclo de vida do app
Criando o ViewModel com StateFlow
O ViewModel gerencia o estado da tela usando StateFlow, a abordagem recomendada pelo Google para Compose.
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
data class CPFScreenState(
val cpfInput: String = "",
val resultado: CPFData? = null,
val erro: String? = null,
val isLoading: Boolean = false,
val validacaoLocal: String = ""
)
@HiltViewModel
class CPFViewModel @Inject constructor(
private val cpfApi: CPFApi
) : ViewModel() {
private val _state = MutableStateFlow(CPFScreenState())
val state: StateFlow<CPFScreenState> = _state.asStateFlow()
fun onCPFChanged(valor: String) {
val numeros = valor.filter { it.isDigit() }.take(11)
val validacao = when {
numeros.length < 11 -> "Faltam ${11 - numeros.length} dígitos"
validarCPFLocal(numeros) -> "Formato válido"
else -> "CPF inválido"
}
_state.value = _state.value.copy(
cpfInput = numeros,
validacaoLocal = validacao,
erro = null
)
}
fun consultar() {
viewModelScope.launch {
_state.value = _state.value.copy(isLoading = true, erro = null, resultado = null)
try {
val response = cpfApi.consultarCPF(_state.value.cpfInput)
if (response.isSuccessful && response.body()?.success == true) {
_state.value = _state.value.copy(
resultado = response.body()?.data,
isLoading = false
)
} else {
_state.value = _state.value.copy(
erro = "CPF não encontrado",
isLoading = false
)
}
} catch (e: Exception) {
_state.value = _state.value.copy(
erro = "Erro de conexão: ${e.message}",
isLoading = false
)
}
}
}
private fun validarCPFLocal(cpf: String): Boolean {
if (cpf.length != 11) return false
val digits = cpf.map { it.digitToInt() }
if (digits.toSet().size == 1) return false
val sum1 = (0 until 9).sumOf { digits[it] * (10 - it) }
val check1 = if (sum1 % 11 < 2) 0 else 11 - (sum1 % 11)
if (digits[9] != check1) return false
val sum2 = (0 until 10).sumOf { digits[it] * (11 - it) }
val check2 = if (sum2 % 11 < 2) 0 else 11 - (sum2 % 11)
return digits[10] == check2
}
}
- StateFlow -- fluxo de estado reativo que Compose coleta automaticamente para recomposição
- viewModelScope -- escopo de coroutines atrelado ao ciclo de vida do ViewModel
- copy() -- método das data classes que cria cópias imutáveis com campos alterados
Construindo a tela com Compose
A tela de consulta usa Material 3 e coleta o estado do ViewModel para reagir a mudanças.
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CPFScreen(viewModel: CPFViewModel = hiltViewModel()) {
val state by viewModel.state.collectAsState()
Scaffold(
topBar = {
TopAppBar(title = { Text("Consulta de CPF") })
}
) { padding ->
Column(
modifier = Modifier
.padding(padding)
.padding(16.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
OutlinedTextField(
value = formatarCPF(state.cpfInput),
onValueChange = { viewModel.onCPFChanged(it) },
label = { Text("CPF") },
placeholder = { Text("000.000.000-00") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true,
modifier = Modifier.fillMaxWidth(),
supportingText = {
Text(
text = state.validacaoLocal,
color = if (state.validacaoLocal == "Formato válido")
MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant
)
}
)
Button(
onClick = { viewModel.consultar() },
enabled = !state.isLoading && state.cpfInput.length == 11,
modifier = Modifier.fillMaxWidth()
) {
if (state.isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
strokeWidth = 2.dp
)
Spacer(modifier = Modifier.width(8.dp))
}
Text(if (state.isLoading) "Consultando..." else "Validar CPF")
}
state.erro?.let { erro ->
Card(colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer
)) {
Text(
text = erro,
modifier = Modifier.padding(16.dp),
color = MaterialTheme.colorScheme.onErrorContainer
)
}
}
state.resultado?.let { dados ->
ResultadoCard(dados = dados)
}
}
}
}
@Composable
fun ResultadoCard(dados: CPFData) {
Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(16.dp)) {
Text("Resultado", style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.height(8.dp))
ResultadoRow("Nome", dados.name)
ResultadoRow("CPF", formatarCPF(dados.cpf))
ResultadoRow("Nascimento", dados.birthDate)
ResultadoRow("Sexo", if (dados.gender == "M") "Masculino" else "Feminino")
}
}
}
@Composable
fun ResultadoRow(label: String, valor: String) {
Row(
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(label, style = MaterialTheme.typography.bodyMedium)
Text(valor, style = MaterialTheme.typography.bodyLarge)
}
}
fun formatarCPF(cpf: String): String {
val n = cpf.filter { it.isDigit() }
return buildString {
n.forEachIndexed { i, c ->
if (i == 3 || i == 6) append('.')
if (i == 9) append('-')
append(c)
}
}
}
- collectAsState() -- converte StateFlow em State do Compose para recomposição automática
- hiltViewModel() -- obtém o ViewModel com dependências injetadas automaticamente pelo Hilt
- Material 3 -- componentes com suporte a Dynamic Color e design system moderno
Perguntas frequentes
Como o Jetpack Compose se integra ao padrão MVVM para validação de CPF?
O Compose coleta o StateFlow do ViewModel via collectAsState(), recompondo automaticamente a tela cada vez que o estado muda. O ViewModel fica responsável pela lógica de negócio — validação local e chamada à API — enquanto o Composable cuida apenas da apresentação. Essa separação é recomendada pela documentação oficial do Android e torna o código fácil de testar e manter.
Qual é a diferença entre validação local e consulta à API de CPF?
A validação local verifica os dígitos verificadores matematicamente, sem consumir nenhuma requisição. Ela é instantânea e serve para dar feedback ao usuário antes de submeter o formulário. A consulta à API do CPFHub.io vai além: retorna nome, data de nascimento e gênero do titular, confirmando que o CPF pertence a uma pessoa real — dado essencial para onboarding, KYC e prevenção de fraudes.
Como armazenar a chave de API do CPFHub.io com segurança em um app Android?
Nunca insira a chave diretamente no código-fonte. A abordagem recomendada é armazená-la no gradle.properties (excluído do controle de versão via .gitignore) e acessá-la via BuildConfig. Para aplicações em produção com requisitos de segurança mais altos, considere buscar a chave em um backend próprio, evitando que ela fique embutida no APK.
O que acontece se a API retornar erro ou a conexão falhar durante a validação?
O ViewModel captura exceções via try/catch e atualiza o StateFlow com uma mensagem de erro que o Composable exibe ao usuário. A API do CPFHub.io não bloqueia requisições ao atingir o limite do plano — ela cobra R$0,15 por consulta adicional — portanto erros de rede ou CPF não encontrado são os cenários mais comuns a tratar na interface.
Conclusão
Integrar validação de CPF em um app Android com Jetpack Compose resulta em um código declarativo, reativo e fácil de manter. A combinação de Hilt para injeção de dependências, StateFlow para gerenciamento de estado e Compose para a interface fornece uma arquitetura sólida seguindo as recomendações oficiais do Google. Com validação local em tempo real e consulta à API para confirmação, seu app oferece uma experiência completa ao usuário.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e adicione validação de CPF ao seu app Android em menos de 30 minutos.
CPFHub.io
Pronto para integrar a API?
50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.
Sobre a redação
Redação CPFHub.io
Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.



