Resposta rápida: use o Health Connect quando seu app Android precisa compartilhar dados de saúde e atividade física com outras soluções de forma centralizada e controlada pelo usuário. A integração em Kotlin segue quatro passos: verificar se o serviço está disponível, declarar somente os tipos de dados necessários, solicitar permissões em contexto e usar o HealthConnectClient para ler, agregar ou gravar registros. Em Jetpack Compose, mantenha o acesso em um repository e exponha estado pelo ViewModel; a tela apenas dispara o contrato de permissões e renderiza resultados. Como esses dados são sensíveis, aplique minimização, consentimento claro, armazenamento local protegido e as políticas atuais do Google Play. Health Connect é infraestrutura de dados — não é diagnóstico médico nem substitui orientação profissional.
Apps de treino, bem-estar, sono e acompanhamento de hábitos frequentemente precisam acessar informações produzidas por relógios, celulares ou outros aplicativos. Fazer integrações separadas com cada fabricante aumenta custo, cria comportamentos diferentes e dificulta ao usuário entender quem tem acesso aos seus dados.
O Health Connect oferece uma camada comum no Android. O usuário escolhe quais aplicativos podem ler ou gravar categorias como passos, exercícios, sono e frequência cardíaca, enquanto o sistema centraliza permissões e gerenciamento dos registros. Para o time de desenvolvimento, isso reduz integrações ponto a ponto; para a pessoa usuária, torna o controle mais visível.
Neste guia, você vai estruturar uma integração moderna com Kotlin e Compose, ler passos, calcular agregados, gravar sessões, tratar disponibilidade e desenhar um fluxo que respeita privacidade. Os exemplos usam versões como placeholders: consulte a documentação oficial do Health Connect antes de definir números no Gradle, permissões e requisitos de publicação.
O que é o Health Connect?
Health Connect é uma plataforma Android para armazenar e compartilhar dados de saúde e fitness entre aplicativos autorizados. Em vez de um app depender diretamente do banco de outro, ambos conversam com uma interface comum e o usuário controla as permissões.
Ele trabalha com record types, isto é, classes que representam categorias de dados. Alguns exemplos conceituais:
| Tipo de registro | Exemplo de uso |
|---|---|
StepsRecord | passos registrados em um intervalo |
HeartRateRecord | amostras de frequência cardíaca |
SleepSessionRecord | início, fim e estágios de sono |
ExerciseSessionRecord | corrida, caminhada ou outro exercício |
WeightRecord | medição de peso |
NutritionRecord | informações nutricionais registradas |
A disponibilidade depende da versão do Android e do provedor instalado. Em versões recentes do sistema, o recurso faz parte da experiência da plataforma; em versões anteriores compatíveis, pode depender do aplicativo provedor. Por isso, nunca presuma que o client está pronto apenas porque o aparelho executa Android.
Health Connect também não é um sistema de prontuário, diagnóstico ou prescrição. Seu app continua responsável por explicar a finalidade do tratamento, proteger dados e evitar conclusões clínicas que não consegue sustentar.
Quando usar — e quando não usar
Use Health Connect quando existir uma necessidade clara de interoperabilidade, por exemplo:
- importar passos registrados por outro app;
- mostrar um resumo de exercícios autorizados;
- gravar uma sessão criada no seu próprio app para que outras soluções a leiam;
- combinar sono e atividade para uma visão de hábitos, sem emitir diagnóstico;
- evitar integrações específicas com vários fabricantes;
- permitir que o usuário concentre o gerenciamento de acesso em um lugar conhecido.
Não peça Health Connect apenas para “ter mais dados”. Se uma funcionalidade opera sem informação de saúde, não solicite a permissão. Também não use a plataforma como desculpa para copiar todo o histórico para seu backend. O melhor desenho costuma processar o mínimo necessário, pelo menor tempo possível.
Antes de implementar, responda:
- qual funcionalidade visível depende de cada tipo de dado?
- o app precisa ler, gravar ou fazer ambos?
- qual intervalo de tempo é realmente necessário?
- o cálculo pode ocorrer no dispositivo?
- por quanto tempo o resultado precisa ser armazenado?
- como a pessoa revoga acesso ou exclui dados derivados?
Se o produto ainda não tem respostas objetivas, a implementação está adiantada em relação à decisão de privacidade.
Arquitetura recomendada para Kotlin e Compose
Não chame HealthConnectClient diretamente de cada Composable. Separe responsabilidades:
Compose UI
↓ eventos / estado
ViewModel
↓ casos de uso
HealthRepository
↓
HealthConnectClient
Essa divisão oferece benefícios práticos:
- a UI não conhece detalhes de records e paginação;
- o ViewModel controla loading, erro e permissão;
- o repository concentra conversões de tempo e tratamento de exceções;
- testes conseguem substituir a fonte por um fake;
- mudanças de API não se espalham por todas as telas.
É a mesma lógica usada em uma boa arquitetura MVVM com Kotlin e em fluxos offline-first no Android: a interface observa estado, enquanto integrações ficam nas bordas.
Uma interface pequena ajuda a não acoplar o domínio ao SDK:
interface HealthRepository {
suspend fun availability(): HealthAvailability
suspend fun readSteps(from: Instant, to: Instant): Long
suspend fun writeWorkout(workout: Workout)
}
enum class HealthAvailability {
Available,
ProviderUpdateRequired,
Unavailable
}
Seu domínio pode trabalhar com Workout e Long, deixando StepsRecord, ExerciseSessionRecord e exceções específicas dentro da implementação Android.
Configurando a dependência
Adicione o client do Health Connect no módulo Android. Use a versão estável recomendada pela documentação oficial no momento da implementação:
dependencies {
implementation("androidx.health.connect:connect-client:<versao-atual>")
}
Se o projeto usa Version Catalog, centralize o alias em libs.versions.toml, como mostrado no guia de Gradle Version Catalog. Isso facilita atualizar vários módulos sem espalhar números de versão.
O manifesto também precisa declarar as permissões correspondentes aos record types usados. Os nomes e requisitos podem evoluir, portanto copie a lista da documentação atual e revise-a sempre que adicionar uma categoria. Uma regra permanece válida: não declare uma permissão que o produto não utiliza.
Além do manifesto, a publicação pode exigir formulários, justificativas e uma política de privacidade coerente com o comportamento real do app. A aprovação da permissão em runtime não substitui as obrigações de transparência na loja.
Verificando disponibilidade
Antes de criar o client, consulte o status do SDK. Um fluxo conceitual:
class AndroidHealthRepository(
private val context: Context
) : HealthRepository {
override suspend fun availability(): HealthAvailability {
return when (HealthConnectClient.getSdkStatus(context)) {
HealthConnectClient.SDK_AVAILABLE ->
HealthAvailability.Available
HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED ->
HealthAvailability.ProviderUpdateRequired
else -> HealthAvailability.Unavailable
}
}
}
Dependendo da versão da biblioteca e da faixa de Android suportada, a chamada pode receber informações adicionais sobre o provedor. Confirme a assinatura atual antes de implementar.
A UI deve distinguir estados:
- disponível: mostrar a ação que explica o benefício e pede acesso;
- atualização necessária: orientar a instalar ou atualizar o provedor compatível;
- indisponível: manter o restante do app funcional, quando possível;
- erro temporário: permitir tentar novamente sem apagar estado.
Não exiba apenas “deu erro”. Uma mensagem como “O Health Connect não está disponível neste aparelho” é mais útil que uma stack trace convertida em texto.
Definindo permissões mínimas
As permissões são derivadas dos tipos de registros e da operação. Para ler passos e gravar exercícios, por exemplo:
val healthPermissions = setOf(
HealthPermissions.getReadPermission(StepsRecord::class),
HealthPermissions.getWritePermission(ExerciseSessionRecord::class)
)
Antes de pedir acesso, confira o que já foi concedido:
suspend fun missingPermissions(
client: HealthConnectClient,
required: Set<String>
): Set<String> {
val granted = client.permissionController.getGrantedPermissions()
return required - granted
}
Não interprete recusa como falha técnica. A pessoa pode negar uma categoria e continuar usando recursos que não dependem dela. Modele estados parciais:
data class HealthPermissionState(
val canReadSteps: Boolean,
val canWriteExercise: Boolean
)
Isso é melhor que um único hasHealthPermission, porque Health Connect permite decisões granulares.
Peça acesso em contexto
Uma tela inicial com dez permissões sensíveis tende a assustar e não explica valor. Prefira pedir acesso quando a pessoa ativa uma função concreta:
“Conecte seus passos para acompanhar a meta diária neste painel.”
A explicação deve informar:
- qual dado será acessado;
- se o app vai ler, gravar ou ambos;
- para qual recurso ele será usado;
- que o acesso pode ser revogado;
- onde consultar a política de privacidade.
Evite linguagem coercitiva, como “Você precisa aceitar para continuar”, quando o restante do produto pode funcionar sem essa integração.
Solicitando permissões no Jetpack Compose
O SDK fornece um contrato de Activity Result para abrir o fluxo de permissões. Em Compose, registre o launcher e use-o apenas em resposta a uma ação da pessoa:
@Composable
fun HealthPermissionButton(
permissions: Set<String>,
onResult: (Set<String>) -> Unit
) {
val launcher = rememberLauncherForActivityResult(
contract = PermissionController
.createRequestPermissionResultContract()
) { granted ->
onResult(granted)
}
Button(
onClick = { launcher.launch(permissions) }
) {
Text("Conectar ao Health Connect")
}
}
Não dispare o launcher diretamente no corpo do Composable. Recomposições poderiam tentar abrir o fluxo mais de uma vez. A abordagem segue o mesmo princípio discutido no guia de permissões Android com Kotlin e Compose: eventos do sistema devem nascer de uma ação ou de um efeito cuidadosamente controlado.
Depois do resultado, peça ao ViewModel para consultar novamente as permissões concedidas. Não dependa somente do conjunto retornado pelo launcher, pois o estado pode mudar fora do app.
Criando o HealthConnectClient
Crie o client apenas quando a plataforma estiver disponível:
fun createHealthConnectClient(
context: Context
): HealthConnectClient {
check(
HealthConnectClient.getSdkStatus(context) ==
HealthConnectClient.SDK_AVAILABLE
)
return HealthConnectClient.getOrCreate(context)
}
Em um projeto com injeção de dependência, forneça a instância como singleton do escopo de aplicação. Não guarde Activity no repository; use applicationContext para evitar vazamentos. Se o projeto usa Hilt, o guia de Hilt com módulos e scopes mostra como separar dependências de aplicação e tela.
Lendo passos em um intervalo
Existem dois caminhos comuns:
- ler os registros individuais;
- pedir um agregado, como a soma de passos.
Para um total diário, o agregado normalmente é mais apropriado:
suspend fun readTotalSteps(
client: HealthConnectClient,
start: Instant,
end: Instant
): Long {
val response = client.aggregate(
AggregateRequest(
metrics = setOf(StepsRecord.COUNT_TOTAL),
timeRangeFilter = TimeRangeFilter.between(start, end)
)
)
return response[StepsRecord.COUNT_TOTAL] ?: 0L
}
Converter “hoje” em Instant exige cuidado com fuso horário. Não calcule o dia usando UTC se a interface promete o dia local da pessoa:
fun todayRange(clock: Clock): Pair<Instant, Instant> {
val zone = ZoneId.systemDefault()
val today = LocalDate.now(clock.withZone(zone))
val start = today.atStartOfDay(zone).toInstant()
val end = today.plusDays(1).atStartOfDay(zone).toInstant()
return start to end
}
Mudanças de horário e viagens tornam datas menos triviais do que parecem. O artigo sobre datas e fusos com kotlinx-datetime aprofunda esse problema para projetos Kotlin.
Evite dupla contagem
Mais de um app ou dispositivo pode escrever registros sobrepostos. As APIs de agregação existem também para aplicar regras da plataforma e reduzir erros de deduplicação. Somar manualmente todos os StepsRecord sem entender origem e sobreposição pode produzir um número diferente daquele esperado pelo usuário.
Se seu caso exige records individuais, leia em páginas e preserve os identificadores de origem necessários para o processamento. Não carregue anos de histórico em memória de uma vez.
Lendo registros individuais
Para listar sessões ou investigar detalhes, use readRecords:
suspend fun readRecentWorkouts(
client: HealthConnectClient,
start: Instant,
end: Instant
): List<ExerciseSessionRecord> {
val result = client.readRecords(
ReadRecordsRequest(
recordType = ExerciseSessionRecord::class,
timeRangeFilter = TimeRangeFilter.between(start, end),
ascendingOrder = false
)
)
return result.records
}
Em intervalos grandes, trate paginação conforme a API atual. Converta o record do SDK para um modelo de domínio dentro do repository:
data class WorkoutSummary(
val start: Instant,
val end: Instant,
val type: String
)
Essa conversão reduz acoplamento e impede a UI de depender de detalhes do Health Connect.
Gravando dados com segurança
Seu app deve gravar apenas dados que ele realmente produziu ou que o usuário inseriu conscientemente. Um exemplo simplificado de registro de passos:
suspend fun writeSteps(
client: HealthConnectClient,
count: Long,
start: Instant,
end: Instant,
zoneOffset: ZoneOffset
) {
require(count >= 0) { "A quantidade de passos não pode ser negativa" }
require(end > start) { "O intervalo precisa ser válido" }
val record = StepsRecord(
count = count,
startTime = start,
endTime = end,
startZoneOffset = zoneOffset,
endZoneOffset = zoneOffset
)
client.insertRecords(listOf(record))
}
Antes de usar esse código em produção, confira os parâmetros exigidos pela versão atual do record e a estratégia de metadata recomendada.
Três cuidados evitam dados duplicados ou enganosos:
- não grave novamente a mesma sessão a cada recomposição ou retry;
- mantenha uma chave local que relacione o evento do domínio ao record criado;
- trate edição e exclusão como operações explícitas, não como novo insert.
Se um upload falha depois de parte do fluxo, não faça retry cego sem idempotência. Uma fila com WorkManager em Kotlin pode ajudar em sincronizações confiáveis, mas a operação precisa saber se aquele registro já foi enviado.
ViewModel e estado de UI
Uma tela não deve representar tudo como Boolean. Modele o fluxo:
sealed interface HealthUiState {
data object Checking : HealthUiState
data object Unavailable : HealthUiState
data class PermissionRequired(
val permissions: Set<String>
) : HealthUiState
data object Loading : HealthUiState
data class Ready(val steps: Long) : HealthUiState
data class Error(val message: String) : HealthUiState
}
No ViewModel:
class HealthViewModel(
private val repository: HealthRepository
) : ViewModel() {
private val _state = MutableStateFlow<HealthUiState>(
HealthUiState.Checking
)
val state: StateFlow<HealthUiState> = _state.asStateFlow()
fun refresh() {
viewModelScope.launch {
_state.value = HealthUiState.Loading
runCatching {
val (start, end) = todayRange(Clock.systemDefaultZone())
repository.readSteps(start, end)
}.onSuccess { steps ->
_state.value = HealthUiState.Ready(steps)
}.onFailure {
_state.value = HealthUiState.Error(
"Não foi possível carregar os passos agora."
)
}
}
}
}
A mensagem pública não deve expor detalhes internos. Registre a causa de forma segura na observabilidade, sem incluir valores de saúde, tokens ou payloads sensíveis.
A tela Compose apenas coleta o estado:
@Composable
fun HealthRoute(viewModel: HealthViewModel) {
val state by viewModel.state.collectAsStateWithLifecycle()
when (val current = state) {
HealthUiState.Checking,
HealthUiState.Loading -> CircularProgressIndicator()
HealthUiState.Unavailable ->
Text("Health Connect indisponível neste aparelho.")
is HealthUiState.PermissionRequired ->
HealthPermissionButton(
permissions = current.permissions,
onResult = { viewModel.refresh() }
)
is HealthUiState.Ready ->
Text("${current.steps} passos hoje")
is HealthUiState.Error ->
ErrorCard(
message = current.message,
onRetry = viewModel::refresh
)
}
}
Privacidade, LGPD e segurança
Dados de saúde são sensíveis. No Brasil, a Lei Geral de Proteção de Dados Pessoais (LGPD) dá tratamento especial a essa categoria. Este guia não é aconselhamento jurídico; a organização deve validar base legal, transparência, retenção e direitos do titular com profissionais responsáveis.
Na engenharia, aplique pelo menos estas medidas:
Minimize a coleta
Se a tela mostra passos de hoje, não leia cinco anos de histórico. Se o cálculo pode ser feito no aparelho, questione a necessidade de enviar records brutos ao servidor.
Separe permissão de finalidade
Ter acesso técnico não autoriza usar o dado para qualquer objetivo. Não reutilize informações de saúde para publicidade, perfil de risco ou outra finalidade incompatível com o consentimento apresentado.
Não registre payload sensível
Evite logs como:
Log.d("Health", "Registros: $records")
Isso pode copiar dados para ferramentas de crash, consoles de desenvolvimento e backups. Registre somente o necessário, como tipo de operação, duração e código de erro sanitizado.
Proteja dados derivados
Mesmo um score calculado a partir de sono e atividade pode continuar sensível. Se precisar persistir localmente, revise criptografia, backup e ciclo de vida. O guia de segurança de dados locais no Android apresenta as decisões mais importantes.
Ofereça controle real
Explique como revogar acesso no Health Connect, desconectar a integração e excluir dados mantidos pelo seu próprio serviço. Revogar a permissão impede novas leituras, mas não apaga automaticamente cópias que seu backend já armazenou.
Tratando erros sem assustar o usuário
A integração pode falhar por motivos diferentes:
| Situação | Resposta recomendada |
|---|---|
| serviço indisponível | ocultar ou desabilitar a função com explicação |
| provedor desatualizado | oferecer caminho para atualização |
| permissão negada | manter recursos independentes funcionando |
| permissão revogada | voltar ao estado de solicitação contextual |
| intervalo inválido | corrigir a lógica e registrar erro interno |
| falha temporária | permitir retry com limite |
| dado ausente | mostrar estado vazio, não “0” se isso mudar o significado |
Diferencie zero de sem dados. Zero passos pode ser um resultado válido; ausência pode significar que nenhuma fonte escreveu naquele período, que a permissão não cobre o dado ou que houve falha de leitura.
Também trate SecurityException como possível mudança de permissão, não como crash inevitável. O usuário pode revogar acesso enquanto o app está aberto.
Testes da integração
Uma boa suíte separa lógica pura do SDK.
Teste unidades de tempo e agregação
Funções que calculam início do dia, intervalos e estados vazios podem ser testadas com Clock fixo:
@Test
fun `calcula intervalo do dia no fuso informado`() {
val clock = Clock.fixed(
Instant.parse("2026-08-27T15:00:00Z"),
ZoneId.of("America/Sao_Paulo")
)
val (start, end) = todayRange(clock)
assertEquals(
Instant.parse("2026-08-27T03:00:00Z"),
start
)
assertTrue(end > start)
}
Use fake no ViewModel
O ViewModel não precisa de um HealthConnectClient real:
class FakeHealthRepository(
private val steps: Long
) : HealthRepository {
override suspend fun availability() = HealthAvailability.Available
override suspend fun readSteps(from: Instant, to: Instant) = steps
override suspend fun writeWorkout(workout: Workout) = Unit
}
Valide em aparelho compatível
Teste também o fluxo real:
- serviço disponível e indisponível;
- primeira solicitação;
- recusa parcial;
- revogação fora do app;
- nenhum dado no intervalo;
- várias fontes escrevendo registros;
- mudança de fuso;
- atualização ou ausência do provedor;
- processo recriado depois da tela de permissão.
Para automatizar a interface, combine testes instrumentados com o guia de Espresso para Android e Compose, sabendo que telas de permissão controladas pelo sistema podem exigir estratégia específica.
Health Connect vs Google Fit API
A decisão não deve ser tratada apenas como troca de nomes. Health Connect prioriza armazenamento e compartilhamento controlado no ecossistema Android, enquanto integrações anteriores do Google Fit tinham contratos e fluxos próprios.
Para um projeto novo que precisa compartilhar dados de saúde no Android, comece pela documentação atual do Health Connect. Para um app legado, faça inventário antes de migrar:
- quais tipos de dados são usados?
- eles têm equivalente no Health Connect?
- o app lê, grava ou sincroniza nos dois sentidos?
- quais históricos estão no backend?
- como evitar duplicação durante a transição?
- quais telas e textos de consentimento precisam mudar?
Execute a migração por capacidade, não com uma substituição total sem telemetria. Preserve a possibilidade de rollback e compare agregados com contas de teste antes de liberar.
Erros comuns
Pedir todas as permissões disponíveis
Isso reduz confiança e pode violar políticas. Solicite apenas records ligados a recursos visíveis.
Abrir a permissão na primeira recomposição
Use Activity Result em resposta a uma ação. Recomposição não representa consentimento.
Somar records sem considerar sobreposição
Prefira métricas de agregação quando o objetivo for um total. Entenda origem e deduplicação antes de calcular manualmente.
Tratar “sem dado” como zero
Esses estados comunicam coisas diferentes. Modele ausência explicitamente.
Enviar registros brutos ao backend por padrão
Faça minimização. Muitas experiências funcionam com cálculo local ou agregados.
Guardar uma Activity no repository
Use contexto de aplicação para o client e launcher na UI. Referências longas a telas podem causar memory leaks no Android.
Confiar apenas no resultado do launcher
Reconsulte permissões ao retomar a tela e antes de uma operação sensível.
Prometer conclusões médicas
Passos, sono e frequência cardíaca precisam de contexto e qualidade de medição. Não apresente diagnóstico, tratamento ou garantia de saúde sem validação clínica e regulatória adequada.
Checklist para produção
- cada record type está ligado a uma funcionalidade documentada;
- permissões de leitura e escrita foram separadas;
- a solicitação acontece em contexto e com linguagem clara;
- indisponibilidade e atualização do provedor têm estados próprios;
- a UI funciona parcialmente quando o acesso é negado;
- intervalos respeitam fuso horário e limites necessários;
- agregados são usados quando evitam dupla contagem;
- inserts têm estratégia de idempotência;
- nenhum dado sensível aparece em logs ou analytics;
- política de privacidade descreve o comportamento real;
- formulários e políticas atuais do Google Play foram revisados;
- revogação e exclusão têm fluxo compreensível;
- repository e ViewModel têm testes com fake;
- o app foi testado em versões diferentes do Android;
- textos evitam diagnóstico ou promessa médica indevida.
Perguntas frequentes
Health Connect funciona com Jetpack Compose?
Sim. O client não depende de Views. Em Compose, registre o contrato de permissões com rememberLauncherForActivityResult, mantenha operações no repository/ViewModel e renderize um estado observável.
Preciso de uma conta Google para usar Health Connect?
Não modele seu app presumindo autenticação Google como requisito do client. A disponibilidade depende da plataforma e do provedor compatível, enquanto sua própria conta e sincronização são decisões separadas do produto.
Posso ler todos os dados de saúde depois de uma única autorização?
Não. O acesso é granular por categorias e operações, pode ter limites adicionais e pode ser revogado. Sempre consulte as permissões atuais e lide com acesso parcial.
Health Connect sincroniza automaticamente com meu backend?
Não. Ele fornece acesso local aos registros autorizados. Qualquer sincronização com servidor é responsabilidade do seu app e precisa de justificativa, segurança, transparência e tratamento adequado pela LGPD.
Devo usar records individuais ou agregação?
Use agregação para totais e resumos, como passos do dia. Use records individuais quando a funcionalidade precisa de sessões, amostras ou metadados específicos. Evite transferir detalhes sem necessidade.
O app pode funcionar se o usuário negar acesso?
Idealmente, sim para tudo que não depende daqueles dados. Mostre uma alternativa manual, estado vazio ou recurso desabilitado com explicação, em vez de bloquear o produto inteiro.
Health Connect garante que os dados estão corretos?
Não. Os registros dependem das fontes, sensores, entradas manuais e regras de cada app. Apresente informações como dados registrados, não como verdade clínica absoluta.
Conclusão e próximos passos
Health Connect reduz a fragmentação do ecossistema Android, mas uma integração de qualidade exige mais que adicionar uma dependência. O produto precisa definir finalidade; a engenharia deve pedir o mínimo, tratar disponibilidade e preservar idempotência; a interface deve explicar valor e aceitar recusa; e a organização precisa proteger dados sensíveis durante todo o ciclo de vida.
Comece por uma capacidade pequena, como exibir o total de passos de hoje. Implemente a verificação do serviço, solicite apenas leitura de passos, use agregação, diferencie zero de ausência e mantenha tudo no dispositivo. Depois que esse fluxo estiver estável e compreensível, avance para sessões de exercício ou outros records realmente necessários.
Para consolidar a arquitetura, continue por MVVM com Kotlin, StateFlow e coroutines, permissões no Android com Compose e segurança de dados locais. Se a integração fizer parte de um projeto de portfólio, documente as decisões de privacidade, os estados de permissão e os testes — isso demonstra maturidade muito além de apenas mostrar um contador na tela.