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

# Handling the result

> Every outcome the Android SDK returns, error codes, process death, and troubleshooting.

`VerificationResult` is a sealed interface, so `when` used as an expression is checked by
the compiler.

```kotlin lines theme={null}
when (result) {
    is VerificationResult.Completed -> Unit  // The user finished uploading everything
    is VerificationResult.Cancelled -> Unit  // The user backed out on purpose
    is VerificationResult.Failed    -> Unit  // Something went wrong; `code` tells you what
}
```

Every case carries `transactionId`, `code` and `message`, so `result.transactionId` reads
the id without a `when` when that is all you need. It is a `kotlin.uuid.Uuid?`, still an
experimental API, so the file that touches it needs:

```kotlin theme={null}
@file:OptIn(ExperimentalUuidApi::class)
```

The callback always runs on the main thread.

## `Completed` does not mean approved

It means the user finished the flow successfully. The actual verdict is produced afterwards and delivered to **your backend through a [webhook](/results/webhooks)**, usually within seconds.

<Warning>
  Show something like *"We're reviewing your document"* on `Completed`, and update the user
  once your backend receives the webhook. A success screen here will be wrong for every
  user who is later rejected.
</Warning>

## Keeping the result across process death

`VerificationResult` is not `Parcelable`, so it cannot go into `rememberSaveable` or
`onSaveInstanceState`.

Persist the transaction id as a `String` and re-derive the rest, or let your backend be the
source of truth once the flow has completed:

```kotlin lines theme={null}
// Survives process death; the result object does not.
savedStateHandle["transactionId"] = result.transactionId?.toString()
```

## Error codes

`VerificationResultCode` is an enum:

| Code | What happened | Suggested handling |
| - | - | - |
| `CAMERA_PERMISSION_DENIED` | Camera access was refused | Offer a link to app settings |
| `LOCATION_PERMISSION_DENIED` | Location access was refused | Offer to retry |
| `LOCATION_PERMISSION_PERMANENTLY_DENIED` | Refused with "don't ask again" | Point the user to app settings |
| `DOCUMENT_SCAN_MAX_RETRIES` | Too many failed scan attempts | Offer human support |
| `LIVENESS_ERROR` | The face check failed | Offer to retry |
| `UNKNOWN_ERROR` | Unexpected failure | Show `message`, allow retry |

<Warning>
  **Always keep an `else ->` branch when matching on the code.** The enum is compiled into
  your app: a newer SDK can return a value your build has never seen, and an exhaustive
  `when` without `else` throws `NoWhenBranchMatchedException` at runtime rather than
  failing to compile.
</Warning>

```kotlin lines theme={null}
fun handle(result: VerificationResult) = when (result) {
    is VerificationResult.Completed ->
        show("We're reviewing your document. We'll let you know shortly.")

    is VerificationResult.Cancelled -> finish()

    is VerificationResult.Failed -> when (result.code) {
        VerificationResultCode.CAMERA_PERMISSION_DENIED,
        VerificationResultCode.LOCATION_PERMISSION_PERMANENTLY_DENIED ->
            promptToOpenAppSettings()

        VerificationResultCode.LOCATION_PERMISSION_DENIED,
        VerificationResultCode.LIVENESS_ERROR ->
            offerRetry()

        VerificationResultCode.DOCUMENT_SCAN_MAX_RETRIES ->
            offerSupport()

        // Covers UNKNOWN_ERROR and anything a newer SDK introduces.
        else -> show(result.message ?: "Something went wrong. Please try again.")
    }
}
```

## Troubleshooting

| Symptom | Cause |
| - | - |
| `Could not find com.chmod.kyc:chmod-kyc-sdk-android` | `gpr.user` / `gpr.key` missing, or the PAT has no `read:packages` |
| `LifecycleOwner is attempting to register while current state is RESUMED` | `registerForActivityResult` called after `onStart` — move it to a field initializer |
| The flow renders in Material purple | The palette you passed replaced the defaults; `copy` from `defaultLight()` instead |
| A custom `fontFamily` has no effect | `Typography.fontFamily` is `@Transient` and does not survive the launching `Intent` |
| `NoWhenBranchMatchedException` on a result | A `when` over `VerificationResultCode` with no `else`, against a newer SDK |
| An overridden string does not change | Key typo — the SDK ignores unknown keys silently |
| The button does nothing | The token request threw and the failure was not caught |
| Camera denied before the flow even starts | The app requested `CAMERA` itself; leave the permission to the SDK |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.