Permissão no Android parece um detalhe de implementação até o produto perder conversão, receber avaliação ruim ou quebrar em uma atualização de sistema. Um app que pede localização antes de explicar o valor, solicita câmera sem contexto, insiste em notificação depois de uma recusa ou trata armazenamento como se ainda estivesse em 2018 transmite desconfiança. Em 2026, saber desenhar permissões no Android com Kotlin é parte de arquitetura, UX, segurança e carreira.

O Android moderno tornou permissões mais granulares. Localização pode ser aproximada ou precisa. Fotos e vídeos podem ser selecionados sem acesso amplo à galeria. Notificações passaram a exigir consentimento explícito em versões recentes. Câmera, microfone, Bluetooth, sensores e serviços em primeiro plano têm regras próprias. Para times que usam Jetpack Compose, App Links, Android offline-first e testes Android com Kotlin, permissões precisam entrar no desenho da tela desde o início.

Este guia mostra como pensar permissões em apps Android Kotlin: quando pedir, como explicar, como modelar estado, como evitar solicitações desnecessárias, quais casos exigem cuidado extra e como testar fluxos de aceite, recusa e configuração manual.

O princípio: peça menos e peça melhor

A melhor permissão é a que o app não precisa pedir. Antes de abrir o requestPermissionLauncher, responda três perguntas:

  1. o recurso é essencial para a tarefa atual?
  2. existe uma alternativa com menos acesso?
  3. o usuário entende por que o pedido aparece agora?

Se um app de lista de tarefas pede localização na primeira abertura, a pessoa tende a desconfiar. Se um app de delivery pede localização depois que o usuário toca em “usar endereço atual”, o contexto é claro. A permissão não deve ser um ritual técnico; deve ser consequência de uma intenção visível.

Em Android, isso afeta métricas reais. Uma recusa permanente pode bloquear uma função importante, aumentar tickets de suporte e reduzir retenção. Por isso, permissões devem ser tratadas como parte do fluxo de produto, não como popup automático no onCreate.

Declare no Manifest apenas o necessário

O primeiro passo é revisar o AndroidManifest.xml. Declare só permissões que correspondem a funcionalidades reais e ativas. Permissão esquecida no Manifest pode gerar revisão mais dura na Play Store, aumentar desconfiança e criar dívida de compliance.

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>

Nem toda permissão declarada precisa ser solicitada na inicialização. O Manifest informa capacidade. O pedido em runtime deve acontecer no momento da ação. Para recursos opcionais, considere feature flags e telas alternativas para usuários que preferem não conceder acesso.

Runtime permission com Activity Result API

Em Kotlin moderno, evite APIs antigas como requestPermissions direto na Activity sem organização. A abordagem com Activity Result API separa o contrato de permissão e facilita testes de estado.

class CameraPermissionController(
    private val onGranted: () -> Unit,
    private val onDenied: () -> Unit,
) {
    fun handleResult(granted: Boolean) {
        if (granted) onGranted() else onDenied()
    }
}

Em uma Activity ou composable integrado ao lifecycle, o launcher fica responsável por abrir o diálogo do sistema:

val launcher = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.RequestPermission(),
) { granted ->
    if (granted) {
        viewModel.onCameraPermissionGranted()
    } else {
        viewModel.onCameraPermissionDenied()
    }
}

A regra importante é não espalhar lógica de negócio dentro do callback. O callback deve informar o resultado ao ViewModel ou a um controlador de UI. O ViewModel decide o próximo estado: abrir câmera, mostrar explicação, exibir alternativa ou registrar evento analítico.

Modele permissões como estado de UI

Permissões têm mais estados do que “sim” e “não”. Um fluxo real pode envolver:

  • ainda não pedido;
  • concedido;
  • negado uma vez;
  • negado com “não perguntar novamente”;
  • indisponível no aparelho;
  • exigindo ativação manual em configurações;
  • permitido parcialmente, como localização aproximada.

Uma modelagem explícita reduz bugs:

sealed interface PermissionUiState {
    data object Unknown : PermissionUiState
    data object Granted : PermissionUiState
    data object NeedsRationale : PermissionUiState
    data object Denied : PermissionUiState
    data object PermanentlyDenied : PermissionUiState
}

No Compose, a tela renderiza com base nesse estado. Isso evita abrir o diálogo repetidamente em recomposição, um erro comum quando o pedido de permissão fica acoplado ao corpo do composable.

@Composable
fun CameraGate(
    state: PermissionUiState,
    onRequestPermission: () -> Unit,
    onOpenSettings: () -> Unit,
) {
    when (state) {
        PermissionUiState.Granted -> CameraContent()
        PermissionUiState.NeedsRationale -> PermissionExplanation(
            title = "Permitir câmera?",
            text = "Usamos a câmera apenas para escanear o código que você escolher.",
            primaryAction = onRequestPermission,
        )
        PermissionUiState.PermanentlyDenied -> SettingsCard(onOpenSettings)
        else -> PermissionIntro(onRequestPermission)
    }
}

O pedido do sistema deve acontecer em resposta a clique, não como efeito invisível. Isso respeita o usuário e reduz surpresa.

Rationale: explique antes de insistir

shouldShowRequestPermissionRationale ajuda a identificar quando o usuário negou antes e o app deveria explicar melhor. Mas não transforme isso em texto genérico. Uma boa rationale responde:

  • qual recurso precisa da permissão;
  • que dado será acessado;
  • quando o acesso acontece;
  • o que a pessoa perde se negar;
  • qual alternativa existe.

Ruim: “Precisamos desta permissão para continuar”.

Melhor: “Para anexar uma foto ao chamado, o app precisa abrir a câmera. Você também pode enviar a imagem pela galeria sem permitir câmera agora.”

Essa diferença é ainda mais importante em categorias sensíveis como saúde, finanças, família, trabalho de campo e educação. Se o app lida com dados locais, revise também segurança de dados locais no Android.

Notificações: peça no momento de valor

Desde Android 13, POST_NOTIFICATIONS exige consentimento em runtime. Muita gente implementa o pedido na primeira abertura e perde a chance de explicar valor. Em 2026, isso é uma decisão ruim para quase todo app.

Peça notificações quando o usuário ativa algo que naturalmente gera alerta:

  • acompanhar pedido;
  • receber lembrete de estudo;
  • monitorar vaga nova;
  • avisar sobre vencimento;
  • lembrar tarefa recorrente;
  • receber status de sincronização importante.

Para um app educacional em Kotlin, por exemplo, o pedido faria sentido depois que a pessoa escolhe receber lembretes de prática. Para um app de vagas, depois que ela cria um alerta. Isso aumenta consentimento porque o benefício está claro.

Também ofereça controle interno. Permissão do sistema não substitui preferências de produto. Um usuário pode permitir notificações gerais, mas querer desativar marketing e manter apenas alertas transacionais.

Localização: aproximada, precisa e background

Localização é uma das permissões mais sensíveis. Use ACCESS_COARSE_LOCATION quando localização aproximada basta. Peça ACCESS_FINE_LOCATION apenas quando precisão realmente muda o resultado, como navegação, check-in geográfico ou mapa com posição exata.

Evite pedir localização em background sem necessidade forte. Background location tem revisão mais rigorosa, exige justificativa clara e pode parecer invasiva. Muitos casos podem ser resolvidos com:

  • localização apenas enquanto o app está em uso;
  • endereço digitado manualmente;
  • seleção de cidade;
  • geocodificação aproximada;
  • atualização sob demanda.

Se o app também usa App Links, campanhas ou analytics, nunca misture localização com rastreamento sem consentimento claro. Permissão técnica não é autorização para coletar mais dado do que a função exige.

Fotos, vídeo e arquivos: prefira seletores do sistema

Muitos apps ainda pedem acesso amplo ao armazenamento quando só precisam de uma foto. Em versões modernas do Android, use o Photo Picker ou contratos de seleção de conteúdo sempre que possível. O usuário escolhe arquivos específicos sem dar acesso permanente à galeria inteira.

val pickImage = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.PickVisualMedia(),
) { uri ->
    if (uri != null) {
        viewModel.onImageSelected(uri)
    }
}

Essa abordagem reduz fricção, melhora privacidade e simplifica a explicação. Em muitos fluxos, você não precisa de READ_MEDIA_IMAGES; precisa apenas deixar a pessoa escolher uma imagem.

Câmera e microfone: deixe o escopo explícito

Câmera e microfone exigem confiança. Antes de pedir, mostre a ação concreta: escanear documento, tirar foto de perfil, ler QR Code, gravar áudio ou participar de chamada. Depois de concedido, dê feedback visual claro enquanto o recurso está ativo.

Também trate falhas:

  • câmera indisponível;
  • outro app usando microfone;
  • aparelho sem sensor esperado;
  • permissão concedida, mas feature bloqueada por política corporativa;
  • app em modo restrito por perfil de trabalho.

Esse cuidado pesa em entrevistas Android. Saber chamar uma API é básico; saber lidar com estados reais de dispositivo mostra maturidade.

Testes de fluxo de permissão

Permissões são difíceis de testar apenas com unit test, mas a regra de decisão pode ser isolada. Teste o ViewModel ou reducer que transforma resultado do sistema em estado de UI.

@Test
fun `quando usuario nega camera apos rationale mostra estado negado`() {
    val reducer = PermissionReducer()

    val state = reducer.reduce(
        current = PermissionUiState.NeedsRationale,
        event = PermissionEvent.Result(granted = false),
    )

    assertEquals(PermissionUiState.Denied, state)
}

Para testes instrumentados, valide pelo menos os caminhos críticos: primeira solicitação, recusa, concessão e retorno das configurações. Em suites com Maestro ou UI Automator, mantenha esses testes pequenos e estáveis. O objetivo não é automatizar todo comportamento do diálogo do sistema, mas garantir que sua tela reage corretamente depois do resultado.

Se o projeto já usa testes de screenshot no Compose, vale capturar os estados de explicação, negado e configurações. Esses cards costumam quebrar visualmente quando o app ganha novos idiomas, fontes maiores ou textos de compliance.

Analytics sem invadir privacidade

Eventos de permissão ajudam produto a melhorar o fluxo, mas devem ser agregados e cuidadosos. Meça coisas como:

  • tela onde o pedido aconteceu;
  • permissão solicitada;
  • aceite ou recusa;
  • clique em “abrir configurações”;
  • uso de alternativa sem permissão.

Não registre dado sensível associado à permissão. Se o usuário negou localização, você não precisa guardar coordenada, endereço ou identificador pessoal para entender que o fluxo precisa de melhoria. Para comparar práticas de observabilidade e privacidade em outras stacks, o ecossistema irmão de Python para automação e dados também reforça a mesma regra: medir comportamento sem coletar mais do que o necessário.

Checklist para produção

Antes de publicar uma funcionalidade com permissão em Android Kotlin, revise:

  • a permissão está declarada no Manifest apenas se for necessária;
  • o pedido acontece no momento de valor, não na primeira abertura;
  • existe texto de rationale claro e específico;
  • a UI trata concedido, negado, negado permanente e alternativa;
  • o app funciona parcialmente sem a permissão quando possível;
  • localização precisa e background só são pedidos com justificativa forte;
  • seleção de fotos usa Photo Picker quando acesso amplo não é necessário;
  • eventos analíticos não vazam dados sensíveis;
  • há testes para a regra de estado e para a tela de fallback;
  • a documentação interna explica por que cada permissão existe.

Permissões bem tratadas deixam o app mais confiável, reduzem suporte e aumentam a chance de aceite quando o recurso realmente importa. Em Kotlin, a combinação de Activity Result API, ViewModel, sealed interfaces e Compose permite modelar esse fluxo de forma limpa. O desafio não é técnico apenas: é pedir acesso com respeito, no contexto certo, e manter uma alternativa honesta para quem prefere dizer não.