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

# Android

> Install the Cr3dentials SDK in an Android app and run a verification.

## Requirements

* Android 7.0 (API 24) or later, with compileSdk 36.
* Kotlin 2.1 or later. The AAR carries Kotlin 2.2 metadata.
* The values from [What you need from us](/sdk/overview#what-you-need-from-us), and a backend that [creates SDK runs](/sdk/create-a-run).

The AAR includes the native core for arm64-v8a, armeabi-v7a, x86\_64 and x86. Its 64-bit libraries are 16 KB-page aligned for Android 15 and later.

## Install

From `mavenCentral()`, which Android projects include by default:

```kotlin theme={null}
dependencies {
    implementation("xyz.cr3dentials:zktls-sdk:1.0.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0")  // for MainScope()
}
```

The AAR merges its permissions, its foreground service and its R8 keep rules into your app. You don't need extra ProGuard rules. It brings JNA, kotlinx-coroutines, androidx.webkit and the Kotlin standard library as Maven dependencies, each under its own licence.

The files on Maven Central are signed with the Cr3dentials OpenPGP key, fingerprint `E8588B38D6FDCC59E6C03011AE4CC59A87AD0C0B`.

## Build the run's configuration

Fetch the trust bundle and the policy bundle right before each run, and build one `TreeRunConfig` from them and from the run your backend issued:

```kotlin theme={null}
import ai.zktls.sdk.*

// Yours: what your backend returns after it creates the run.
data class IssuedRun(val packId: String, val resultUrl: String, val accessToken: String)

suspend fun treeConfig(run: IssuedRun) = TreeRunConfig(
    baseUrl = Cr3d.BASE_URL,
    appCredential = Cr3d.APP_CREDENTIAL,
    userIdHash = sha256Hex(currentUserId),                       // yours: never the raw id
    idempotencyKey = java.util.UUID.randomUUID().toString(),     // new for every run
    clientRegion = userCountry ?: "",                            // ISO 3166-1 alpha-2, or "" if unknown
    networkKey = "default",
    platformId = run.packId,
    trustBundleJson = fetchText(Cr3d.TRUST_BUNDLE_URL),          // yours: a plain HTTPS GET
    trustBundleUrl = Cr3d.TRUST_BUNDLE_URL,
    trustOperatorKey = Cr3d.TRUST_OPERATOR_KEY,
    policyBundleJson = fetchText(Cr3d.POLICY_BUNDLE_URL),
    policyBundleDigest = Cr3d.POLICY_BUNDLE_DIGEST,
    relyingPartyNonceHex = "",
    resultUrl = run.resultUrl,
    resultToken = run.accessToken,
    paramsJson = "",                                             // a check's params as JSON text, or ""
)
```

The fields follow the same rules as on iOS: see the [table on the iOS page](/sdk/ios#build-the-runs-configuration).

## Run a verification

```kotlin theme={null}
import ai.zktls.sdk.*
import android.app.Activity
import kotlinx.coroutines.*

class VerifyActivity : Activity() {
    private val scope = MainScope()
    private var client: ZktlsClient? = null

    fun verify(platformId: Int) {
        if (client != null) return                                   // one run at a time
        val client = ZktlsClient().also { this.client = it }         // a new client for every run
        val lifecycle = ZkTlsLifecycle(this, client.lifecycleHandle)
        client.onProgress { phase, percent ->
            runOnUiThread {
                if (phase == Phase.ATTESTING) {                      // keeps the hand-off alive if the user leaves
                    lifecycle.begin()
                    try {
                        ZkTlsSessionService.start(this@VerifyActivity, lifecycle) { client.cancel() }
                    } catch (e: IllegalStateException) {
                        // API 31+ refuses to start it from the background. The run goes on without it.
                    }
                }
                // update your progress bar
            }
        }
        scope.launch {
            try {
                val run = withContext(Dispatchers.IO) { backend.createSdkRun(platformId) }   // yours
                val summary = client.runTree(treeConfig(run), TreeWebViewHost(this@VerifyActivity, disclosure))
                when {
                    summary.handedOff -> show("You can close the app. We'll let you know when it's done.")
                    summary.succeeded && summary.delivered -> show("Verified.")
                    else -> show("Not verified. Please try again.")         // summary.error says why
                }
            } catch (e: SdkException) {
                show(message(e))                                     // see Errors; never retry automatically
            } catch (e: CancellationException) {
                throw e                                              // the activity is going: show nothing
            } catch (e: Exception) {
                show("Something went wrong. Please try again.")      // InternalException: report it
            } finally {
                lifecycle.end()                                      // every path, including failure
                ZkTlsSessionService.stop(this@VerifyActivity)
                this@VerifyActivity.client = null
            }
        }
    }

    fun cancelTapped() { client?.cancel() }

    override fun onDestroy() {
        scope.cancel()                                               // cancels the run in flight
        super.onDestroy()
    }
}

val disclosure = TreeDisclosure(
    title = "Connect your account",
    message = "We'll open the platform's own sign-in page. You sign in there yourself, and we never see " +
        "your password. Then we read the data this verification needs from your account.",
    accept = "Continue",
    decline = "Not now",
)
```

`backend`, `show` and `message` are yours. [Errors](/sdk/errors) has a `message` that covers every case.

When `runTree` throws, or returns with neither `delivered` nor `handedOff`, nothing reached Cr3dentials. Tell your backend so it [terminates the run](/sdk/create-a-run#hand-the-run-to-your-app).

## What `runTree` returns

`TreeSummary` has the same fields as on iOS ([table](/sdk/ios#what-runtree-returns)). On Android, `isolation` is one of these:

| `isolation` | Meaning |
| - | - |
| `profile` | Android System WebView 123 and later. The run had its own WebView profile |
| `shared` | Older WebViews. The run used your app's WebView store, which the SDK wiped before and after |
| empty | The page never opened |

## Things to know

* **The disclosure.** `TreeWebViewHost` shows your `TreeDisclosure` before it opens any page. If the user declines, nothing opens. Write it in your own words and translate it.
* **Where the page may go.** The WebView stays on the platform's domains and on the third-party sign-in providers the pack lists. If the user follows a link anywhere else, the run ends with `SdkException.Policy` and the text `navigation_refused: <host>`.
* **Your app's web data.** On WebViews older than 123, the run wipes your app's WebView cookies, web storage and cache before and after. Don't start a run while your own WebViews' data matters.
* **The foreground service.** `ZkTlsSessionService` is a `shortService`. Start it when the `ATTESTING` phase starts and stop it on every path. Android ends it after about three minutes. It holds one run at a time, so guard as the snippet does.
* **The notification.** It reads "Verifying", in a channel named "Verification". To translate or rebrand it, define `zktls_session_notification_title`, `zktls_session_channel_name` and the drawable `zktls_session_notification_icon` in your app's resources.
* **Cancelling.** `cancel()` ends `runTree` with `SdkException.Cancelled` and closes the page. So does the user pressing Back or closing the page. If you cancel the coroutine instead, it ends with its own `CancellationException`. Cancel your scope when the activity goes.
* **Threads.** `runTree` is a `suspend` function that runs on the SDK's own threads. `onProgress` calls you on an SDK thread, so don't block in it.
* **Error text.** `SdkException.Transport` and `SdkException.Policy` carry their text in `e.v1`. `e.message` reads `v1=<text>`, so match and log `e.v1`.
* **Logging.** Log error variants and `e.v1` only. Never log the run's `accessToken`, cookies or your app credential.

## Before you submit

The SDK adds `INTERNET`, `ACCESS_NETWORK_STATE` and `FOREGROUND_SERVICE` (type `shortService`) to your manifest. It asks for no runtime permission. Work through the [release checklist](/sdk/release-checklist) for the Google Play Data safety answers and the foreground service declaration.


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