Getting started: Android (Validations SDK)
Introduction
The Truora Validations SDK allows you to integrate identity verification features directly into your native mobile applications. The SDK handles the complexity of capturing the user’s identity documents, facial recognition, and liveness detection to verify their identity against backend records.
Requirements
Platform Requirements
- Android: 5.1 (API Level 21) or higher
- The SDK must be launched from an AppCompatActivity
Permissions Required
- Camera access (for document and face capture)
- Internet access (for API communication)
Prerequisites & Authentication
The Key Provider Interface
To prevent hardcoding sensitive logic, you must implement the TruoraAPIKeyGetter interface in your code. This allows you to retrieve the API key from a secure location (like a compiled secret, a secure storage enclave, or an obfuscated string). This interface is how the sdk will get access to the api key for the validations, the api key must be of type sdk (temporary), any other key type will result in an error. Below we show three examples of how to implement the interface, but it can work however you want
Option 1: Encrypted Storage
Java
import com.truora.interfaces.TruoraAPIKeyGetter;
// You would need to import necessary Android classes like Context and relevant security classes.
public class EncryptedStorageApiKeyProvider implements TruoraAPIKeyGetter {
// NOTE: In a real app, 'context' would be used to access EncryptedSharedPreferences.
// The key must be retrieved and cached synchronously or fetched asynchronously before this method is called.
private final String cachedApiKey;
public EncryptedStorageApiKeyProvider(String preFetchedKey) {
// Assuming the key is pre-fetched asynchronously and passed to the constructor.
this.cachedApiKey = preFetchedKey;
}
@Override
public String getApiKeyFromSecureStorage() {
// Check if the key was successfully retrieved and cached
if (cachedApiKey == null || cachedApiKey.isEmpty()) {
throw new RuntimeException("API key not found in secure storage or retrieval failed.");
}
return this.cachedApiKey;
}
}
Kotlin
import com.truora.interfaces.TruoraAPIKeyGetter
import android.content.Context
// You would need androidx.security.crypto for EncryptedSharedPreferences
class EncryptedStorageApiKeyProvider(private val context: Context, private val preFetchedKey: String?) : TruoraAPIKeyGetter {
// NOTE: For synchronous retrieval, this class assumes the API key was already
// fetched asynchronously and passed in the constructor, or it uses a blocking method.
override fun getApiKeyFromSecureStorage(): String {
// Use Kotlin's safety features to check for null/empty
return preFetchedKey.takeIf { !it.isNullOrEmpty() }
?: throw RuntimeException("API key not found in secure storage or retrieval failed.")
}
// Example conceptual function for synchronous secure retrieval if necessary
// private fun retrieveKeyFromStorageSync(context: Context, key: String): String? { /* ... */ }
}
Option 2: BuildConfig value
In this option we get the key from a generated class defined in your module’s build.gradle file.
Java
import com.truora.interfaces.TruoraAPIKeyGetter;
/**
* Retrieves the API key directly from the BuildConfig file.
*/
public class BuildConfigApiKeyProvider implements TruoraAPIKeyGetter {
@Override
public String getApiKeyFromSecureStorage() {
// Option 2: From environment config (less secure, for development)
String apiKey = BuildConfig.TRUORA_API_KEY;
if (apiKey == null || apiKey.isEmpty()) {
// Throw an error or return an empty string if the key is missing/null
return "";
}
return apiKey;
}
}
Kotlin
import com.truora.interfaces.TruoraAPIKeyGetter
// Assuming BuildConfig.TRUORA_API_KEY is available
/**
* Retrieves the API key directly from the BuildConfig file.
*/
class BuildConfigApiKeyProvider : TruoraAPIKeyGetter {
override fun getApiKeyFromSecureStorage(): String {
// Option 2: From environment config (less secure, for development)
// Uses Kotlin's 'takeIf' and Elvis operator (?:) for a concise null/empty check
return BuildConfig.TRUORA_API_KEY.takeIf { !it.isNullOrEmpty() } ?: ""
}
}
Option 3: Retrieve from a backend
Recommended for production builds these are examples for backend retrieval:
Java
import com.truora.interfaces.TruoraAPIKeyGetter;
public class BackendServiceApiKeyProvider implements TruoraAPIKeyGetter {
// Key must be declared volatile to ensure visibility across threads (network fetch vs. SDK call).
private volatile String cachedApiKey = null;
/**
* Conceptual method: Key should be fetched in the Application class or an initialization phase
* before the SDK is instantiated.
*/
public void fetchApiKeyAsynchronously(String userToken) {
// Implementation detail: Use a library like Retrofit or OkHttp to call your API.
// ... (Network call logic on a background thread) ...
// On successful response:
// this.cachedApiKey = fetchedKey;
}
@Override
public String getApiKeyFromSecureStorage() {
// Option 3: Return the cached key.
if (cachedApiKey == null || cachedApiKey.isEmpty()) {
// Indicates failure to pre-fetch the key, halting SDK initialization.
throw new RuntimeException("API key has not been retrieved from backend service or fetch failed.");
}
return cachedApiKey;
}
}
Kotlin
import com.truora.interfaces.TruoraAPIKeyGetter
class BackendServiceApiKeyProvider : TruoraAPIKeyGetter {
// Key must be declared volatile to ensure visibility across threads/Coroutines.
@Volatile private var cachedApiKey: String? = null
/**
* Conceptual method: This function would use Coroutines to safely fetch the key
* in a background thread and update 'cachedApiKey'.
*/
fun fetchApiKeyAsynchronously(userToken: String) {
// Implementation detail: Use Retrofit/Ktor within a Coroutine scope.
// After successful fetch: cachedApiKey = fetchedKey
}
override fun getApiKeyFromSecureStorage(): String {
// Option 3: Return the key that was retrieved and cached asynchronously beforehand
// Uses the Elvis operator to throw a RuntimeException if the key is not ready
return cachedApiKey ?: throw RuntimeException("API key has not been retrieved from backend service.")
}
}
Installation
The Truora SDK is modular and published on Maven. You don’t need to include every module in your project, only the validation modules that you need.
For example, if you are just interested in the face validation module add the following to your app-level build.gradle.kts (or build.gradle):
dependencies {
// Face Validation Module
implementation("com.truora:validations.face:1.0.0")
// Document Validation Module
implementation("com.truora:validations.document:1.0.0")
// Required AndroidX dependencies
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
}
Android Permissions
Add the camera permission to your AndroidManifest.xml. The SDK requires camera access to capture the selfie.
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
Configuration Options
Before implementing validations, let’s understand the available configuration options.
Using custom validation config and UI config is optional when building the validation:
- If no
uiConfigis provided then the validation view will show the standard Truora branding, logo and colors - If no
validationConfigis provided then default values will be applied like:- No waiting for validation results before finishing the validation process for the user
autocaptureof face/doc when it’s detected will beon
More indepth information on the validation configuration options, such as similarity threshold, can be found in the official validations api documentation
User ID
The userId parameter is crucial for initializing the Truora SDK. It provides a unique identifier for the user undergoing the validation process.
-
Purpose: The
userIdallows Truora to associate validation attempts and reference data (like previously captured reference faces) with a specific user in your system. It is mandatory for linking the validation results to your user base. -
Best Practice: This ID should be an immutable, non-sensitive identifier from your database (e.g., a UUID or internal user ID) that uniquely identifies the person. Do not use sensitive PII like an email address or national ID number as the
userId.
Language
The language parameter allows you to define the localization of the text and instructions displayed within the validation view.
-
Customization: By setting the language, you ensure a user-friendly experience by presenting instructions and feedback in the user’s preferred language.
-
Available Options: The SDK supports several languages. You should provide the language code as a two-letter string (e.g., “es” for Spanish, “en” for English, “pt” for Portuguese). If no language is provided, the SDK will typically default to English or attempt to use the device’s system language, though providing an explicit language is recommended.
Example in Java Android Initialization:
// Initialize SDK with an AppCompatActivity context, most often is the same caller "this"
// Do this before the onResume of the lifecycle of the component
ValidationHandler validationHandler = ValidationHandler.create(this);
After initializing once the handler its time to configure the SDK
// Build SDK by passing an object with the Key Provider interface
TruoraSDK.Validations.Builder(this,"user_unique_id") //Unique user id
.withLanguage(TruoraLanguage.SPANISH) //Sets language to Spanish
.withUIConfig({ /* ... */ })
.withValidation({ /* ... */ })
.build(validationHandler);
Example in Kotlin Android Initialization:
// Initialize SDK with an AppCompatActivity context, most often is the same caller "this"
// Do this before the onResume of the lifecycle of the component
val validationHandler = ValidationHandler.create(this)
After initializing once the handler its time to configure the SDK
// Build SDK by passing an object with the Key Provider interface
TruoraSDK.Validations.Builder(this,"user_unique_id") // Unique user id
.withLanguage(TruoraLanguage.SPANISH) //Sets language to Spanish
.withUIConfig{ /* ... */ }
.withValidation{ /* ... */ }
.build(validationHandler)
Face configuration
To create a face validation you just need to add it when building the validation object withValidation, you can just return the validation config inside the lambda function if you do not want to use any custom configuration
To specify that the validation type you want to use is Face, then explicitly type the lambda function as accepting a Face config validation type
Java Example
This is only the face configuration example in Java, the entire integration example is in the following section.
// 1. Configure using Lambdas
TruoraSDK.Validations.Builder builder = TruoraSDK.Validations.Builder(this, "user_unique_id");
.withValidation ((FaceValidationConfigFunction) config -> {
config
.useAutocapture(true)
.setSimilarityThreshold(0.95f)
.setTimeout(120) // 2 minutes timeout
.waitForResults(false)
.build()
});
// 2. Bind
builder.build(validationHandler);
Kotlin Example
This is only the face configuration example in Kotlin, the entire integration example is in the following section.
// 1. Configure using Lambdas
val builder = TruoraSDK.Validations.Builder(this, "user_unique_id")
.withValidation { config ->
config
.useAutocapture(true)
.setSimilarityThreshold(0.95f)
.setTimeout(120) // 2 minutes timeout
.waitForResults(false)
.build()
}
// 2. Bind
builder.build(validationHandler)
Configuration Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| useAutocapture | bool | true | Automatically capture when face is properly detected |
| similarityThreshold | double | 0.8 | Minimum similarity score required (0.0 - 1.0) |
| timeout | int | 60 | Maximum time in seconds for the validation |
| waitForResults | bool | false | Wait for API response before closing the view |
| referenceFace | ReferenceFace | null | Optional reference image for comparison |
Document configuration
To create a face validation you just need to added it when building the validation object withValidation, you can just return the validation config inside the lambda function if you do not want to use any custom configuration
To specify that the validation type you want to use is Document, then explicitly type the lambda function as accepting a Document config validation type
Java Example
This is only the document configuration example in Java, the entire integration example is in the following section.
// 1. Configure using Lambdas
TruoraSDK.Validations.Builder builder = TruoraSDK.Validations.Builder(this, "user_unique_id");
.withValidation ((DocumentValidationConfig) config -> {
config
.useAutocapture(true)
.setTimeout(120) // 2 minutes timeout
.waitForResults(false)
.setCountry(Country.CO) // Country code (e.g., co, mx, pe)
.setDocumentType(DocumentType.NATIONAL_ID) // Type of document
.build()
});
// 2. Bind
builder.build(validationHandler);
Kotlin Example
This is only the document configuration example in Kotlin, the entire integration example is in the following section.
// 1. Configure using Lambdas
val builder = TruoraSDK.Validations.Builder(this, "user_unique_id")
.withValidation { config ->
config
.useAutocapture(true)
.setTimeout(120) // 2 minutes timeout
.waitForResults(false)
.setCountry(Country.CO) // Country code (e.g., co, mx, pe)
.setDocumentType(DocumentType.NATIONAL_ID) // Type of document
.build()
}
// 2. Bind
builder.build(validationHandler)
Configuration Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| country | Country | null | Country code for document validation (if not set, user selects) |
| documentType | DocumentType | null | Type of document (if not set, user selects) |
| useAutocapture | bool | true | Automatically capture when document is detected |
| timeout | int | 90 | Maximum time in seconds for the validation |
| waitForResults | bool | false | Wait for API response before closing the view |
Available Countries and Document Types:
| Country | Code | Supported Document Types |
|---|---|---|
| Argentina | Country.ar | nationalId |
| Brazil | Country.br | cnh, generalRegistration |
| Chile | Country.cl | nationalId, foreignId, driverLicense, passport |
| Colombia | Country.co | nationalId, foreignId, rut, ppt, passport, identityCard, temporaryNationalId |
| Costa Rica | Country.cr | nationalId, foreignId |
| El Salvador | Country.sv | nationalId, foreignId, passport |
| Mexico | Country.mx | nationalId, foreignId, passport |
| Peru | Country.pe | nationalId, foreignId |
| Venezuela | Country.ve | nationalId |
| All | Country.all | passport |
Understanding SDK Results
Before implementing validations, it’s important to understand what results you’ll receive and how to handle them.
The SDK returns a TruoraValidationResult which can be:
- completed: Validation process finished (contains ValidationResult)
- canceled: Validation process canceled by the user (could contain ValidationResult)
- error: SDK error occurred (contains TruoraError)
Validation Result Object
The plugin returns ValidationResult objects with the following structure:
data class ValidationResult(
val id: String?, // Unique ID for this validation
val status: String?, // Status of the validation
val type: String?, // Type of the validation
val detail: ValidationDetailResponse?
)
Validation Statuses
| Status | Description | When it occurs |
|---|---|---|
| ValidationStatus.success | Validation passed | User successfully passed the validation |
| ValidationStatus.failure | Validation failed | User did not pass the validation criteria |
| ValidationStatus.pending | Awaiting processing | Validation is being process |
TruoraValidationError Types
When the SDK returns failed, the error can be one of three types:
| Exception Type | Description |
|---|---|
| TruoraValidationError.SDK | Internal SDK error (configuration, permissions, user actions) |
| TruoraValidationError.ValidationApi | Error from the Truora Validation API |
| TruoraValidationError.Network | Network connectivity error |
Common SDK Error Types (SDKErrorType):
| Error Type | Code | Description |
|---|---|---|
| cameraPermissionError | 20011 | Camera permission was denied |
| invalidApiKey | 20017 | API key is invalid or expired |
| invalidConfiguration | 20024 | SDK configuration is invalid |
| networkError | 20025 | Network connection failed |
| uploadFailed | 20026 | Failed to upload captured media |
Handling Example Java
This is only the results handling example in Java, the entire integration example is in the following section.
private void handleResult(TruoraValidationResult result) {
if (result instanceof TruoraValidationCompleted<?>) {
TruoraValidationCompleted<?> completed = (TruoraValidationCompleted<?>) result;
ValidationResult validation = (ValidationResult) completed.getValue();
Log.i(TAG, "Validation completed successfully: " + validation.getId()
+ "\n- Status: " + validation.getStatus()
+ "\n- Type: " + validation.getType()
);
if (validation.getStatus() == ValidationStatus.SUCCESS) {
// User passed the validation
navigateToSuccessScreen();
} else if (validation.getStatus() == ValidationStatus.FAILURE) {
// User did not pass the validation criteria
showRetryDialog();
}
} else if (result instanceof TruoraValidationError<?>) {
TruoraValidationError<?> error = (TruoraValidationError<?>) result;
handleSDKError(error);
} else if (result instanceof TruoraValidationCanceled<?>) {
// User cancelled - just go back
goToPrevScreen();
}
}
private void handleSDKError(TruoraValidationError<?> truoraError) {
TruoraException exception = truoraError.getException();
if (exception instanceof TruoraException.SDK) {
// SDK-level errors (includes network errors)
switch (exception.getCode()) {
case 20011: // SDKErrorType.CAMERA_PERMISSION_ERROR
// Camera permission denied
showPermissionDialog();
break;
case 20025: // SDKErrorType.NETWORK_ERROR
showErrorDialog("Connection error: " + exception.getMessage());
break;
default:
showErrorDialog(exception.getMessage());
break;
}
} else if (exception instanceof TruoraException.ValidationApi) {
// API-level errors (HTTP failures from the Truora API)
showErrorDialog("API error: " + exception.getMessage());
}
}
Handling Example Kotlin
This is only the results handling example in Kotlin, the entire integration example is in the following section.
private fun handleResult(result: TruoraValidationResult<*>) {
when (result) {
is TruoraValidationCompleted<*> -> {
val validation = result.value as ValidationResult
Log.i(TAG, "Validation completed successfully: ${validation.id}" +
"\n- Status: ${validation.status}" +
"\n- Type: ${validation.type}"
)
if (validation.status == ValidationStatus.SUCCESS) {
// User passed the validation
navigateToSuccessScreen()
} else if (validation.status == ValidationStatus.FAILURE) {
// User did not pass the validation criteria
showRetryDialog()
}
}
is TruoraValidationError<*> -> {
handleSDKError(result)
}
is TruoraValidationCanceled<*> -> {
// User cancelled - just go back
goToPrevScreen()
}
}
}
private fun handleSDKError(truoraError: TruoraValidationError<*>) {
val exception = truoraError.exception
when (exception) {
is TruoraException.SDK -> {
// SDK-level errors (includes network errors)
when (exception.getCode()) {
20011 -> { // SDKErrorType.CAMERA_PERMISSION_ERROR
// Camera permission denied
showPermissionDialog()
}
else -> {
showErrorDialog(exception.message)
}
}
}
is TruoraException.ValidationApi -> {
// API-level errors (HTTP failures from the Truora API)
showErrorDialog("API error: ${exception.message}")
}
is TruoraException.Network -> {
// Network connection errors
showErrorDialog("Connection error: ${exception.message}")
}
}
}
Java Implementation
Now that you understand configurations and result handling, let’s implement validations.
Basic Face Validation
Here’s a complete example of implementing face validation in Java:
package com.truora.sampleapp;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.truora.core.TruoraSDK;
import com.truora.core.shared.client.ValidationHandler;
import com.truora.errors.TruoraException;
import com.truora.interfaces.FaceValidationConfigFunction;
import com.truora.interfaces.TruoraAPIKeyGetter;
import com.truora.shared.models.TruoraValidationCanceled;
import com.truora.shared.models.TruoraValidationCompleted;
import com.truora.shared.models.TruoraValidationError;
import com.truora.shared.models.ValidationResult;
public class ValidationActivity extends AppCompatActivity implements TruoraAPIKeyGetter {
private static final String TAG = "ValidationActivity";
// Step 1: Create handler to register activity launcher
private final ValidationHandler validationHandler = ValidationHandler.create(this);
private TextView statusText;
private Button startButton;
private ProgressBar progressBar;
private boolean isValidating = false;
@Override
public String getApiKeyFromSecureStorage() {
// Retrieve from secure storage by customer development side
return "YOUR_API_KEY";
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_validation);
statusText = findViewById(R.id.status_text);
startButton = findViewById(R.id.start_button);
progressBar = findViewById(R.id.progress_bar);
statusText.setText("Ready to start validation");
startButton.setOnClickListener(v -> {
if (!isValidating) {
startFaceValidation();
}
});
}
private void startFaceValidation() {
isValidating = true;
statusText.setText("Starting face validation...");
startButton.setEnabled(false);
progressBar.setVisibility(View.VISIBLE);
try {
TruoraSDK.Validations.Builder builder = TruoraSDK.Validations
.Builder(this, "android-test-user");
// Optional: Customize UI
builder.withUIConfig(uiBuilder -> {
uiBuilder.setPrimaryColor("#435AE0");
uiBuilder.setSurfaceColor("#FFFFFF");
uiBuilder.setErrorColor("#FF5454");
uiBuilder.setLogo("https://your-cdn.com/logo.png");
return uiBuilder.build();
});
// Configure face validation
builder.withValidation((FaceValidationConfigFunction) config -> {
config.useAutocapture(true)
.setSimilarityThreshold(0.8f)
.waitForResults(true);
return config.build();
});
builder.build(validationHandler);
validationHandler.start(this::handleValidationResult);
} catch (TruoraException | IllegalArgumentException e) {
Log.e(TAG, "Failed to initialize SDK", e);
statusText.setText("Initialization failed: " + e.getMessage());
resetUI();
}
}
private void handleValidationResult(Object result) {
runOnUiThread(() -> {
resetUI();
if (result instanceof TruoraValidationCompleted<?> success) {
ValidationResult validation = (ValidationResult) success.getValue();
String status = validation.getStatus();
switch (status) {
case "success":
statusText.setText("Success! ID: " + validation.getId());
break;
case "failure":
statusText.setText("Validation failed. Please try again.");
break;
case "pending":
statusText.setText("Processing...");
break;
default:
statusText.setText("Status: " + status);
break;
}
} else if (result instanceof TruoraValidationError<?> error) {
// Detailed error handling
TruoraException exception = error.getException();
if (exception instanceof TruoraException.SDK) {
switch (exception.getCode()) {
case 20011: // cameraPermissionError
statusText.setText("Camera permission required");
break;
case 20025: // networkError
statusText.setText("Connection error: " + exception.getMessage());
break;
default:
statusText.setText("Error: " + exception.getMessage());
break;
}
} else if (exception instanceof TruoraException.ValidationApi) {
statusText.setText("API error: " + exception.getMessage());
}
} else if (result instanceof TruoraValidationCanceled<?> canceled) {
ValidationResult partial = (ValidationResult) canceled.getValidationResult();
if (partial != null) {
statusText.setText("Canceled. Partial ID: " + partial.getId());
} else {
statusText.setText("Validation canceled by user");
}
}
});
}
private void resetUI() {
isValidating = false;
startButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
}
}
Document Validation
For document validation, use the DocumentValidationConfig:
private void startDocumentValidation() {
isValidating = true;
statusText.setText("Starting document validation...");
startButton.setEnabled(false);
progressBar.setVisibility(View.VISIBLE);
try {
TruoraSDK.Validations.Builder builder = TruoraSDK.Validations
.Builder(this, "android-test-user");
// Optional: Customize UI
builder.withUIConfig(uiBuilder -> {
uiBuilder.setPrimaryColor("#435AE0");
uiBuilder.setSurfaceColor("#FFFFFF");
uiBuilder.setErrorColor("#FF5454");
uiBuilder.setLogo("https://your-cdn.com/logo.png");
return uiBuilder.build();
});
// Configure document validation
builder.withValidation((DocumentValidationConfigFunction) config -> {
config.useAutocapture(true)
.waitForResults(true);
return config.build();
});
builder.build(validationHandler);
validationHandler.start(this::handleValidationResult);
} catch (TruoraException | IllegalArgumentException e) {
Log.e(TAG, "Failed to initialize SDK", e);
statusText.setText("Initialization failed: " + e.getMessage());
resetUI();
}
}
Kotlin Implementation
Basic Face Validation
Now that you understand configurations and result handling, let’s implement validations in Kotlin.
package com.truora.sampleapp
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.truora.core.TruoraSDK
import com.truora.core.shared.client.ValidationHandler
import com.truora.errors.TruoraException
import com.truora.interfaces.TruoraAPIKeyGetter
import com.truora.shared.models.TruoraValidationCanceled
import com.truora.shared.models.TruoraValidationCompleted
import com.truora.shared.models.TruoraValidationError
import com.truora.shared.models.ValidationResult
class ValidationActivity : AppCompatActivity(), TruoraAPIKeyGetter {
companion object {
private const val TAG = "ValidationActivity"
}
// Step 1: Create handler to register activity launcher
private val validationHandler = ValidationHandler.create(this)
// Using 'lateinit var' for views initialized in onCreate
private lateinit var statusText: TextView
private lateinit var startButton: Button
private lateinit var progressBar: ProgressBar
private var isValidating = false
override fun getApiKeyFromSecureStorage(): String {
// Retrieve from secure storage by customer development side
return "YOUR_API_KEY"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_validation)
// Initialize views
statusText = findViewById(R.id.status_text)
startButton = findViewById(R.id.start_button)
progressBar = findViewById(R.id.progress_bar)
statusText.text = "Ready to start validation"
// Using Kotlin lambda for click listener
startButton.setOnClickListener {
if (!isValidating) {
startFaceValidation()
}
}
}
private fun startFaceValidation() {
isValidating = true
statusText.text = "Starting face validation..."
startButton.isEnabled = false
progressBar.visibility = View.VISIBLE
try {
// Building the SDK configuration using chained calls and the 'apply' scope function
TruoraSDK.Validations
.Builder(this, "android-test-user")
.apply {
// Optional: Customize UI. Using trailing lambda syntax.
withUIConfig { uiBuilder ->
uiBuilder.setPrimaryColor("#435AE0")
uiBuilder.setSurfaceColor("#FFFFFF")
uiBuilder.setErrorColor("#FF5454")
uiBuilder.setLogo("https://your-cdn.com/logo.png")
uiBuilder.build()
}
// Configure face validation. Using trailing lambda syntax.
withValidation { config ->
config.useAutocapture(true)
.setSimilarityThreshold(0.8f)
.waitForResults(true)
config.build()
}
}
.build(validationHandler)
// Using a Kotlin method reference (::) for the callback
validationHandler.start(::handleValidationResult)
} catch (e: TruoraException) {
Log.e(TAG, "Failed to initialize SDK", e)
statusText.text = "Initialization failed: ${e.message}"
resetUI()
} catch (e: IllegalArgumentException) {
// Combined catch block for multiple exceptions
Log.e(TAG, "Failed to initialize SDK", e)
statusText.text = "Initialization failed: ${e.message}"
resetUI()
}
}
private fun handleValidationResult(result: Any) {
// runOnUiThread is often omitted in modern Kotlin if using coroutines,
// but kept here for direct translation parity.
runOnUiThread {
resetUI()
// Using 'when' expression for type-checking and smart-casting
when (result) {
is TruoraValidationCompleted<*> -> {
val validation = result.value as? ValidationResult ?: return@runOnUiThread
val status = validation.status
statusText.text = when (status) {
"success" -> "Success! ID: ${validation.id}"
"failure" -> "Validation failed. Please try again."
"pending" -> "Processing..."
else -> "Status: $status"
}
}
is TruoraValidationError<*> -> {
// Detailed error handling
val exception = result.exception
when (exception) {
is TruoraException.SDK -> {
statusText.text = when (exception.code) {
20011 -> "Camera permission required" // cameraPermissionError
20025 -> "Connection error: ${exception.message}" // networkError
else -> "Error: ${exception.message}"
}
}
is TruoraException.ValidationApi -> {
statusText.text = "API error: ${exception.message}"
}
else -> {
statusText.text = "Unknown error occurred."
}
}
}
is TruoraValidationCanceled<*> -> {
val partial = result.validationResult as? ValidationResult
statusText.text = if (partial != null) {
"Canceled. Partial ID: ${partial.id}"
} else {
"Validation canceled by user"
}
}
}
}
}
private fun resetUI() {
isValidating = false
startButton.isEnabled = true
progressBar.visibility = View.GONE
}
}
Document Validation
For document validation, use the DocumentValidationConfig:
private fun startFaceValidation() {
isValidating = true
statusText.text = "Starting document validation..."
startButton.isEnabled = false
progressBar.visibility = View.VISIBLE
try {
// Building the SDK configuration using chained calls and the 'apply' scope function
TruoraSDK.Validations
.Builder(this, "android-test-user")
.apply {
// Optional: Customize UI. Using trailing lambda syntax.
withUIConfig { uiBuilder ->
uiBuilder.setPrimaryColor("#435AE0")
uiBuilder.setSurfaceColor("#FFFFFF")
uiBuilder.setErrorColor("#FF5454")
uiBuilder.setLogo("https://your-cdn.com/logo.png")
uiBuilder.build()
}
// Configure doc validation. Using trailing lambda syntax.
withValidation { config ->
config.useAutocapture(true)
.waitForResults(true)
config.build()
}
}
.build(validationHandler)
// Using a Kotlin method reference (::) for the callback
validationHandler.start(::handleValidationResult)
} catch (e: TruoraException) {
Log.e(TAG, "Failed to initialize SDK", e)
statusText.text = "Initialization failed: ${e.message}"
resetUI()
} catch (e: IllegalArgumentException) {
// Combined catch block for multiple exceptions
Log.e(TAG, "Failed to initialize SDK", e)
statusText.text = "Initialization failed: ${e.message}"
resetUI()
}
}
Troubleshooting
Common Issues
-
Camera not working on Android
Solution: Verify that camera permissions are added to AndroidManifest.xml and that the user has granted permission at runtime. -
“API Key Invalid” error
Solution: Verify your API key is correct and has the proper grants (generator or sdk type). Check that it hasn’t expired. -
Validation timeout
Solution: Increase the timeout value in the config, or ensure the device has a stable internet connection.