> ## Documentation Index
> Fetch the complete documentation index at: https://documentacao.legitimuz.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Android

> Abra o fluxo de verificação num app Android e receba os eventos da sessão em Kotlin.

Ao final deste guia o fluxo de verificação abre dentro do seu app Android, com a câmera liberada e
os eventos da sessão chegando ao seu código Kotlin.

## Pré-requisitos

* Android 7.0 (API 24) ou superior.
* Uma `access_credential` emitida pelo seu backend, como em
  [criar verificação](/api/create-verification). O app nunca gera uma credencial sozinho e nunca
  guarda a chave de API.
* O **App Bundle** do seu app registrado e verificado em
  [Integrações → Segurança](/platform/domains). Sem ele, a sessão recusa abrir.

<Warning>
  A chave de API (`lz_...`) nunca vai para o app. Ela fica no seu servidor. O app recebe apenas a
  `access_credential`, que vale para uma verificação e expira.
</Warning>

## Configure o projeto

<Steps>
  <Step title="Declare as permissões">
    A verificação captura documento e prova de vida, então precisa da câmera. A localização só entra
    se o seu fluxo tiver etapa de geolocalização.

    ```xml title="AndroidManifest.xml" theme={null}
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ```
  </Step>

  <Step title="Adicione o componente à tela">
    ```xml title="activity_verificacao.xml" theme={null}
    <com.legitimuz.LegitimuzVerification
        android:id="@+id/verificacao"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    ```
  </Step>

  <Step title="Peça a permissão de câmera antes de abrir">
    Peça a câmera **antes** de iniciar a sessão. Se o titular chega na etapa de captura e a
    permissão ainda não foi concedida, o fluxo trava numa tela sem saída.

    ```kotlin title="VerificacaoActivity.kt" theme={null}
    private val pedirCamera = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { concedida ->
        if (concedida) iniciarVerificacao() else mostrarExplicacaoDeCamera()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            == PackageManager.PERMISSION_GRANTED
        ) {
            iniciarVerificacao()
        } else {
            pedirCamera.launch(Manifest.permission.CAMERA)
        }
    }
    ```
  </Step>

  <Step title="Busque a credencial no seu backend">
    O seu app pede ao seu servidor, que chama a Legitimuz e devolve só a credencial.

    ```kotlin title="VerificacaoRepository.kt" theme={null}
    suspend fun obterCredencial(refId: String): String = withContext(Dispatchers.IO) {
        val resposta = api.criarVerificacao(CriarVerificacaoRequest(refId = refId))
        resposta.accessCredential
    }
    ```

    No seu backend, a origem precisa ser a do app, o `entry` volta como
    `{ "kind": "native", "access_credential": "..." }`.
  </Step>

  <Step title="Abra a sessão">
    ```kotlin title="VerificacaoActivity.kt" theme={null}
    private fun iniciarVerificacao() = lifecycleScope.launch {
        val credencial = repository.obterCredencial(refId = "pedido-4471")

        binding.verificacao.start(
            accessCredential = credencial,
            listener = object : LegitimuzListener {
                override fun onEvent(nome: String, dados: Map<String, Any?>) {
                    Log.i("Legitimuz", "evento: $nome")
                }

                override fun onComplete(status: String) {
                    // O desfecho confiável chega pelo webhook, no seu backend.
                    finish()
                }

                override fun onError(codigo: String, mensagem: String) {
                    Log.e("Legitimuz", "erro $codigo: $mensagem")
                }
            }
        )
    }
    ```
  </Step>
</Steps>

## Confira que funcionou

O fluxo abre na primeira etapa e o seu Logcat registra `session.started`. No dashboard, a
verificação sai de `not_opened` e passa a `started`.

## Os eventos

Os eventos são os mesmos da web. O catálogo completo, com os payloads, está em
[eventos](/guides/web/best-practices/events).

| Evento                       | Quando dispara                     |
| ---------------------------- | ---------------------------------- |
| `session.started`            | o fluxo abriu a primeira etapa     |
| `session.stepTransitioned`   | o servidor trocou a etapa          |
| `document.capture.completed` | a captura do documento foi enviada |
| `liveness.completed`         | a prova de vida foi concluída      |
| `session.completed`          | o fluxo terminou                   |
| `session.error`              | um erro ocorreu na sessão          |

<Warning>
  **Não decida nada a partir de `session.completed`.** Ele diz que o titular terminou de interagir,
  não que a verificação foi aprovada. O desfecho confiável chega ao seu backend por
  [`verification.decided`](/webhooks/events#verificationdecided), onde o titular não pode
  interferir.
</Warning>

## Casos de borda

<AccordionGroup>
  <Accordion title="O titular negou a câmera">
    Uma negativa simples pode ser pedida de novo. Depois de "não perguntar mais", só as configurações
    do sistema resolvem, mande o titular para lá com um `Intent`:

    ```kotlin theme={null}
    startActivity(
        Intent(
            Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
            Uri.fromParts("package", packageName, null)
        )
    )
    ```

    Explique por que a câmera é necessária antes de pedir de novo. Um pedido repetido sem contexto é
    negado de novo.
  </Accordion>

  <Accordion title="O app foi para segundo plano no meio da jornada">
    A sessão é retomável. Ao voltar, o componente busca o estado atual e reabre na etapa onde o
    titular parou, desde que a credencial não tenha expirado.

    Não recrie a Activity a cada rotação: declare `android:configChanges` ou guarde o estado no
    `ViewModel`.
  </Accordion>

  <Accordion title="A credencial expirou">
    `session.error` chega com um código de credencial inválida. Peça uma credencial nova ao seu
    backend e comece outra sessão. Não tente reaproveitar a antiga.
  </Accordion>

  <Accordion title="A sessão recusa abrir">
    Quase sempre é origem. Confirme que o **App Bundle** do seu `applicationId` está registrado e
    **verificado** em [Integrações → Segurança](/platform/domains), e que a
    verificação foi criada com o `integration_origin_public_id` dessa origem.
  </Accordion>
</AccordionGroup>

## Próximo passo

<Columns cols={2}>
  <Card title="iOS" icon="apple" href="/sdks/ios">
    O mesmo fluxo em Swift.
  </Card>

  <Card title="Receber a decisão" icon="tower-broadcast" href="/webhooks/introduction">
    Onde o desfecho realmente chega.
  </Card>
</Columns>
