> ## 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.

# Quick start

> Launch the verification flow from Jetpack Compose or Views.

The SDK is launched through an `ActivityResultContract`, so the outcome comes back the
same way any other activity result does.

## Compose

```kotlin lines theme={null}
import androidx.activity.compose.rememberLauncherForActivityResult
import com.chmod.kyc.api.ChmodKycContract
import com.chmod.kyc.api.ChmodKycRequest
import com.chmod.kyc.api.VerificationResult

@Composable
fun VerificationScreen(viewModel: VerificationViewModel = viewModel()) {

    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    // Registered during composition, well before STARTED — the launcher is
    // ready by the time the user can tap anything.
    val verification = rememberLauncherForActivityResult(ChmodKycContract()) { result ->
        viewModel.onVerificationResult(result)
    }

    // The ViewModel fetches the token off the main thread; when it lands, launch.
    LaunchedEffect(Unit) {
        viewModel.requests.collect { request -> verification.launch(request) }
    }

    Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
        Button(
            onClick = viewModel::startVerification,
            enabled = !uiState.isLoading
        ) {
            Text("Verify my identity")
        }

        if (uiState.isLoading) CircularProgressIndicator()
        uiState.message?.let { Text(it) }
    }
}
```

The token request belongs in a `ViewModel`, not in the composable. It has to survive
configuration changes, and a composable is re-run for reasons that have nothing to do with
the user asking to verify:

```kotlin lines theme={null}
class VerificationViewModel(private val backend: KycBackend) : ViewModel() {

    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    private val _requests = MutableSharedFlow<ChmodKycRequest>()
    val requests: SharedFlow<ChmodKycRequest> = _requests.asSharedFlow()

    fun startVerification() {
        if (_uiState.value.isLoading) return          // Avoids launching the flow twice
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }
            runCatching { backend.createKycTransaction() }
                .onSuccess { token ->
                    _requests.emit(
                        ChmodKycRequest(
                            baseUrl = "https://api.chmod.com",
                            sdkToken = token,
                            sdkConfig = KycConfiguration.default
                        )
                    )
                }
                .onFailure {
                    _uiState.update { s -> s.copy(message = "We couldn't start the verification.") }
                }
            _uiState.update { it.copy(isLoading = false) }
        }
    }
}
```

## Views

```kotlin lines theme={null}
class VerificationActivity : AppCompatActivity() {

    // Registered as a field initializer, so it exists before onStart().
    // Registering after STARTED throws.
    private val verification = registerForActivityResult(ChmodKycContract()) { result ->
        handle(result)   // Always called on the main thread
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding.verifyButton.setOnClickListener {
            lifecycleScope.launch {
                val token = backend.createKycTransaction()
                verification.launch(
                    ChmodKycRequest(baseUrl = "https://api.chmod.com", sdkToken = token)
                )
            }
        }
    }
}
```

The same call works from a `Fragment`. Register it as a field there too.

<Warning>
  Never register the launcher in `onCreateView` or later. `registerForActivityResult` after
  the lifecycle reaches `STARTED` throws
  `LifecycleOwner is attempting to register while current state is RESUMED`.
</Warning>

## Fetching the token

The token comes from **your** backend, over your own authenticated endpoint:

```kotlin lines theme={null}
class KycBackend(private val client: OkHttpClient) {

    suspend fun createKycTransaction(): String = withContext(Dispatchers.IO) {
        val request = Request.Builder()
            .url("https://api.example.com/kyc/start")
            .post(EMPTY_BODY)
            .header("Authorization", "Bearer ${session.accessToken}")
            .build()

        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful) error("Token request failed: ${response.code}")
            json.decodeFromString<StartResponse>(response.body!!.string()).sdkToken
        }
    }
}
```

<Warning>
  Never embed your chmod client id and secret in the app. See [Create transaction](/api-reference/transactions/create-transaction) for the endpoint your backend should expose.
</Warning>

## Adding configuration

Pass an `SdkConfig` to control appearance, copy and capture behaviour. Every field has a
default, so `SdkConfig()` is valid:

```kotlin lines theme={null}
val request = ChmodKycRequest(
    baseUrl = baseUrl,
    sdkToken = token,
    sdkConfig = SdkConfig(
        appearance = appearance,
        showWelcomeScreen = true,
        showResultScreen = false
    )
)
```

Full reference in [SDK configuration](/sdk/configuration/overview).

## Next

<Card title="Handling the result" icon="code-branch" href="/sdk/android/handling-results">
  Every outcome, every error code, and surviving process death.
</Card>


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