Android SDK in an XML/View-based apps
This page covers integrating the Arkose Android SDK in an XML/View-based application.
Before you begin, review the Compatibility, Technical Requirements (including the Kotlin-Compose compatibility reference), and Prerequisites sections on the main Android Mobile SDK page. Integrating in a Jetpack Compose application? Refer to the Compose integration steps instead.
Dependency Management:
Include the dependencies using the Authenticated Package Manager(APM)
Prerequisites:
-
You have received the Arkose provided credentials (username and token) from Arkose Command Center. Please visit How to Request a Mobile SDK Token for more information.
-
Store these credentials securely on your CI environment and in your project's local.properties for fetching locally.
-
Declare Credentials:
maven { url = uri("https://maven.pkg.github.com/ArkoseLabs/alsdk-android-packages") credentials{ username = "TODO: Insert Arkose provided username here" password = "TODO: Insert Arkose provided token here" } }
Ensure these credentials are stored securely and not shared outside authorized usage.
-
Add the SDK Dependency to Your Project:
-
To integrate an SDK package into your project, use the following format for the dependency:
implementation 'ArkoseLabs.sdk:android:x.xx.xxxxx'
-
-
Sync the Project with Gradle Files
-
After adding the dependency, sync your project with the Gradle files to ensure all dependencies are correctly resolved. Use the Sync Project with Gradle Files option in Android Studio or run:
./gradlew sync
-
Legacy Support: Include the dependencies using manual AAR file
-
Under
src/main, create alibsfolder. -
Copy your
.aarfiles tosrc/main/libs -
In the app level
build.gradlefile, add these lines to itsdependenciesblock.implementation fileTree(dir: "libs", include: ["*.aar"])
Additional Dependencies:
This section applies only to the manual AAR (Legacy Support) integration. If you use the Authenticated Package Manager (APM/Maven), all of these transitive dependencies are resolved automatically from the package metadata, so no manual additions are needed.
A manually added .aar does not carry dependency information, so the SDK's dependencies must be declared in your app's build.gradle. Add the following to the dependencies block. These are the versions the SDK is built and tested against. Use these versions or newer compatible releases:
// UI & resources: required for the SDK's screens to build and render
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.6.1' // required from SDK v2.22.0; minimum 1.5.0
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
// Kotlin runtime
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.7.20'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
// Serialization & device signals
implementation 'com.google.code.gson:gson:2.11.0'
implementation 'androidx.biometric:biometric:1.2.0-alpha04'Version compatibility for com.google.android.material: the minimum supported version is 1.5.0 (the first release providing the Material 3 styles the SDK references); the SDK has been verified against Material 1.5.0 through 1.14.0. Note that newer Material versions impose their own requirements on your app (e.g. Material 1.14.0 requires minSdk 23, compileSdk 35, and Android Gradle Plugin 8.6+); these are Material's requirements, not the SDK's.
Upgrading to v2.22.0 from an earlier version (manual AAR only): v2.22.0 adds
com.google.android.material:material(1.5.0 or newer) as a required dependency for the SDK's themed UI. If it is missing, your app's build fails at resource linking with:
error: resource style/Theme.Material3.DayNight not found
Add the Material line from the dependency list above to resolve it. APM/Maven integrations are unaffected because the dependency is resolved automatically.
ProGuard Rules:
Update the Android ProGuard or R8 configuration with the following rule sets:
-dontwarn com.google.android.play.core.integrity.IntegrityManagerFactory
-dontwarn com.google.android.play.core.integrity.StandardIntegrityManager
-dontwarn okhttp3.OkHttpClient$Builder
-dontwarn okhttp3.OkHttpClient
-dontwarn okhttp3.logging.HttpLoggingInterceptor$Level
-dontwarn okhttp3.logging.HttpLoggingInterceptor$Logger
-dontwarn okhttp3.logging.HttpLoggingInterceptor
-dontwarn retrofit2.Converter$Factory
-dontwarn retrofit2.Retrofit$Builder
-dontwarn retrofit2.Retrofit
-dontwarn retrofit2.converter.gson.GsonConverterFactory
In the toolbar, click on File. Then in its menu click Sync Project with Gradle Files.
Import and add Arkose code to your application
Import the necessary SDK classes. Add the below code in your main file, right after you import your android and androidx classes and before your class definition statements.
import com.arkoselabs.sdk.ArkoseChallenge;
import com.arkoselabs.sdk.ArkoseConfig;
import com.arkoselabs.sdk.ArkoseManager;Implement Standard Integration
-
In the main screen activity section (Under src/main/java), in the onCreate method of the activity, initialize the SDK with ArkoseConfig object with API URL, API Key and other parameters
// Build the configuration object final ArkoseConfig arkoseConfig = new ArkoseConfig.Builder() .apiKey(<YOUR_PUBLIC_KEY>) .blobData("") //encrypted blob data (optional) .loading(true) // setting this to false will not show the default loading spinner .enableBackButton(true) // To disable/enable device back button in EC process. Default is 'true' .setDismissChallengeOnTouchOutside(false) // To disable/enable touch outside of EC dialog to dismiss EC. Default is 'true' .language("") // Can set language here (optional) .setClientAPIRetryCount(0) // Can set number of retry count for when onError is triggered for certain client-side errors (optional) .setStyleTheme("") // Can set EC style theme (optional) .viewFrameAnimation(R.anim.custom_animation) // This to set animation while EC will be loading. You can set your own custom animation by putting value R.anim.<AnimStyle> .setTimeoutUntilReady(0) // SDK timeout in seconds (optional, positive integer). .setAccessibilityTextScaleCap(0) // Accessibility Text Scale Cap (optional, positive integer), 0 means unset (maintains default Android accessibility behavior) .build(); // Set the log level ArkoseManager.setLogLevel(ArkoseManager.INFO); // Initialize the SDK // For v2.0.0 SDK //ArkoseManager.initialize(arkoseConfig); // For v2.1.0 SDK ArkoseManager.initialize(arkoseConfig, getApplication()); -
If you want to add your own custom animation then the value of
viewFrameAnimationwill beR.anim.<CustomAnimStyle>. TheCustomAnimStyleis your animation xml file.-
We provide a number of inbuilt animations in the Mobile SDK. Use the desired animation style by replacing the
<AnimStyle>inR.anim.<AnimStyle>parameter.- blink
- fade
- scale_center
- sequential
- slide_down
- slide_left
- slide_right
- slide_up
- zoom_in
- zoom_out
-
-
To show the Enforcement View from the
onClick()method of the Login Button, callshowEnforcementChallengemethod withFragmentActivityas its parameter. This will run Arkose detection component (when running Arkose enforcement component, it also runs Arkose’s Enforcement Challenge). Fill in the method definitions with what you want to happen when that method is invoked as described in the comments. Note there are separate code examples for our detection and enforcement components.
Enforcement
ArkoseChallenge arkoseChallenge = ArkoseManager.showEnforcementChallenge(this);
arkoseChallenge.
.addOnCompletedListener(arkoseChallengeResponse -> {
// invoked On Enforcement Challenge completed
})
.addOnFailureListener(arkoseChallengeResponse -> {
// invoked on Enforcement Challenge failed
})
.addOnErrorListener(arkoseChallengeResponse -> {
// invoked on getting error while loading EC
})
.addOnWarningListener(arkoseChallengeResponse -> {
// invoked when an issue occurs which needs to be shared with
the app as a warning
})
.addOnResizeListener(arkoseChallengeResponse -> {
// Provides the width and height of the visible EC from an SDK call
// While you cannot set the width and height values yourself,
// you can make use of their new values from the resizing
// as you'd like, such as putting them in a log entry.
})
.addOnReadyListener(() -> {
//Do Something on ready callback received
})
.addOnShowListener(() -> {
//Do Something on show callback received
})
.addOnShownListener(() -> {
//Deprecated since Arkose SDK v2.13.0
})
.addOnShownListener(new ArkoseChallenge.OnShownListener() {
@Override
public void onShown(ArkoseChallengeResponse response) {
//Do Something on shown callback received
}
@Override
public void onShown() {
//Deprecated since Arkose SDK v2.13.0
}
})
.addOnHideListener(() -> {
//Deprecated since Arkose SDK v2.13.0
})
.addOnHideListener(new ArkoseChallenge.OnHideListener() {
@Override
public void onHide() {
//Deprecated since Arkose SDK v2.13.0
}
@Override
public void onHide(ArkoseChallengeResponse response) {
//Do Something on hide callback received
}
})
.addOnResetListener(() -> {
//Do Something on reset callback received
})
.addOnViewFramePosition(arkoseChallengeResponse -> {
// Do Something to set the position of view frame
// Example:
// arkoseECResponse.getWindow().getAttributes().gravity = Gravity.BOTTOM;
// arkoseECResponse.getWindow().getAttributes().width = ViewGroup.LayoutParams.MATCH_PARENT;
})
.addOnSuppressListener(() -> {
//Deprecated since Arkose SDK v2.13.0
})
.addOnSuppressListener(new ArkoseChallenge.OnSuppressListener() {
@Override
public void onSuppress(ArkoseChallengeResponse response) {
//Do Something on suppress callback received
}
@Override
public void onSuppress() {
//Deprecated since Arkose SDK v2.13.0
}
});Deprecated Legacy Support
ArkoseLabs.getActivityClient(this).ShowEnforcementChallenge(arkoseConfig)
.addOnCompletedListener(new OnCompletedListener < ArkoseECResponse > () {
@Override
public void onCompleted(ArkoseECResponse arkoseEnforcementChallengeResponse) {
// invoked On Detection completed
}
})
.addOnErrorListener(new OnErrorListener<ArkoseECResponse>() {
@Override
public void onError(ArkoseECResponse arkoseECResponse) {
// invoked on getting error while loading detection
}
})
.addOnWarningListener(new OnErrorListener<ArkoseECResponse>() {
@Override
public void onWarning(ArkoseECResponse arkoseECResponse) {
// invoked on getting warning while loading detection
}
})
.addOnReadyListener(new OnReadyListener() {
@Override
public void onReady() {
//Do Something on ready callback received
}
})
.addOnShowListener(new OnShowListener() {
@Override
public void onShow() {
//Do Something on show callback received
}
.addOnHideListener(new OnHideListener() {
@Override
public void onHide() {
//Do Something on hide callback received
}
})
.addOnSuppressListener(new OnSuppressListener() {
@Override
public void onSuppress() {
//Do Something on suppress callback received
}
});Embed the challenge as a view Fragment
To create a view Fragment that can be embedded in another activity, use ArkoseManager.createEnforcementChallenge() method which returns an instance of ArkoseChallenge. This will create and set up a view Fragment which can be accessed using the method ArkoseChallenge.getEnforcementChallengeFragment. The example below shows how you can create the ArkoseLabs challenge view Fragment and embed it into your app's activity.
// Create Arkose challenge fragment
ArkoseChallenge arkoseChallenge = ArkoseManager.createEnforcementChallenge();
arkoseChallenge
.addOnCompletedListener(arkoseChallengeResponse -> {
// invoked On Enforcement Challenge completed
dismissEnforcementChallenge();
})
.addOnFailureListener(arkoseChallengeResponse -> {
// invoked on Enforcement Challenge failed
boolean isRecoverable = arkoseChallengeResponse.getResponse().optBoolean("recoverable", false);
if (!isRecoverable) {
dismissEnforcementChallenge();
}
})
.addOnHideListener(() -> {
dismissEnforcementChallenge();
});
// Add other listeners as needed
// Add the fragment to the activity
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.frameLayout, arkoseChallenge.getEnforcementChallengeFragment());
ft.commit();
/**
* Removes the challenge fragment from the UI.
* Create a function like below and
* invoke it from the terminal callbacks to remove the fragment from the container
* e.g. addOnCompletedListener, addOnFailureListener, addOnHideListener, addOnErrorListener, addOnWarningListener
*/
private void dismissEnforcementChallenge() {
try {
if (arkoseChallenge != null)
arkoseChallenge.removeChallengeFragment();
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.frameLayout);
if (fragment != null) {
getSupportFragmentManager().beginTransaction()
.remove(fragment)
.commitAllowingStateLoss();
}
} catch (Exception e) {
Log.e(TAG, "Error hiding challenge: " + e.getMessage());
}
}Resetting the Arkose Session
The Arkose session can be reset dynamically to initiate a new challenge with updated configurations or other triggering events. This effectively clears the current session data and allows for a fresh start.
To reset the session, follow these steps:
- Create a new
ArkoseConfiginstance with the desired configuration. - Call
ArkoseManager.updateConfig(arkoseConfig, arkoseChallenge);to update the session.
ArkoseConfig arkoseConfig = new ArkoseConfig.Builder()
.apiKey("<YOUR_PUBLIC_KEY>")
.blobData("<YOUR_ENCRYPTED_DATA>") //encrypted blob data (optional)
.language("<YOUR_PREFERED_LANGUAGE>") // Can Set Language here. (optional)
.setStyleTheme("<YOUR_PREFERED_STYLE_THEME>")// Can set EC style theme (optional)
.build();
/*
* Call updateConfig() to apply the new configuration.
* This will reset the session and apply any changes.
*/
ArkoseManager.updateConfig(arkoseConfig, arkoseChallenge); Implement Preloading of Challenges to onReady with On-Demand Presentation
onReady with On-Demand PresentationOverview:
The new inlineRunOnTrigger configuration enables preloading of enforcement challenge to onReady state, giving your application full control over when a session token is generated or the challenge is presented to the user. This greatly improves user-perceived latency. Available from Android SDK v2.18.0 (Android XML-based SDK only).
Configuration:
- Set via
.setInlineRunOnTrigger(true)(default:false) - Use with
createEnforcementChallenge()to preload the challenge - Resume the challenge flow by calling
runTriggeredInline(arkoseChallenge)
Important Notes:
- Do NOT use this configuration with
showEnforcementChallenge()- Do NOT enable
loader=truewhen using this feature, as it will cause the loader to display indefinitely
Usage Example:
private boolean isInlineRunOnTriggeredCalled = false;
private boolean isOnReadyCalled = false;
// Step 1: Configure ArkoseConfig with inlineRunOnTrigger enabled
ArkoseConfig arkoseConfig = new ArkoseConfig.Builder()
.apiKey("<YOUR_PUBLIC_KEY>")
.blobData("<YOUR_ENCRYPTED_DATA>") // Optional: encrypted blob data
.language("<YOUR_PREFERRED_LANGUAGE>") // Optional: set language
.setStyleTheme("<YOUR_PREFERRED_STYLE_THEME>") // Optional: set EC style theme
.setInlineRunOnTrigger(true) // Enable preloading of enforcement challenge
.build();
ArkoseManager.initialize(arkoseConfig, getApplication()); // initialize config with current context
// Step 2: Create Arkose challenge fragment (this preloads the challenge)
ArkoseChallenge arkoseChallenge = ArkoseManager.createEnforcementChallenge();
// Step 3: Set up event listeners
arkoseChallenge
.addOnCompletedListener(arkoseChallengeResponse -> {
// Handle enforcement challenge completion
dismissEnforcementChallenge();
})
.addOnFailureListener(arkoseChallengeResponse -> {
// Handle enforcement challenge failure
boolean isRecoverable = arkoseChallengeResponse.getResponse().optBoolean("recoverable", false);
if (!isRecoverable) {
dismissEnforcementChallenge();
}
})
.addOnReadyListener(() -> {
isOnReadyCalled = true;
})
.addOnHideListener(() -> {
dismissEnforcementChallenge();
});
// Add other listeners as needed
// Step 4: Add the fragment to your activity
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.frameLayout, arkoseChallenge.getEnforcementChallengeFragment());
ft.commit();
// Step 5: Trigger the challenge only after onReady callback (e.g., on button click)
btLogin.setOnClickListener(v -> {
if (!isInlineRunOnTriggeredCalled) {
if (arkoseChallenge != null && isOnReadyCalled) {
isInlineRunOnTriggeredCalled = true;
// This call resumes the preloaded challenge
ArkoseManager.runTriggeredInline(arkoseChallenge);
}
}
});
/**
* Removes the challenge fragment from the UI.
* Create a function like below and
* invoke it from the terminal callbacks to remove the fragment from the container
* e.g. addOnCompletedListener, addOnFailureListener, addOnHideListener, addOnErrorListener, addOnWarningListener
*/
private void dismissEnforcementChallenge() {
try {
if (arkoseChallenge != null)
arkoseChallenge.removeChallengeFragment();
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.frameLayout);
if (fragment != null) {
getSupportFragmentManager().beginTransaction()
.remove(fragment)
.commitAllowingStateLoss();
}
} catch (Exception e) {
Log.e(TAG, "Error hiding challenge: " + e.getMessage());
}
}To build and test your integration, refer to Build the revised project and Run and test the app on the main page.
Retrieve the response token
When the verification or detection is successful, the onCompleted event returns a response token. The following sample code shows what this should look like and what to do with the token.
.addOnCompletedListener(arkoseChallengeResponse -> {
// invoked On Enforcement Challenge completed
String userResponseToken = arkoseChallengeResponse.getResponse().toString();
// Get value of the token
String userResponseToken = arkoseChallengeResponse.getResponse().getString("token");
Log.i(TAG,"Arkose EC completed: " + userResponseToken);
// sendToBackendServer(userResponseToken);
})Programmatic Enforcement Challenge Dismissal
If your application flow requires force dismissal of the Enforcement Challenge (such as during navigation changes), choose one of the following approaches based on your implementation:
-
Java/XML-Based Implementation
Invokedispose()using theArkoseChallengeobjectpublic class MainActivity extends AppCompatActivity { private ArkoseChallenge arkChallenge; private ArkoseChallenge createEC() { return ArkoseManager.createEnforcementChallenge(this) // Add callback listeners as needed... } private void showDialogChallenge() { arkChallenge = createEC(); if (arkChallenge != null) { arkChallenge.showEnforcementChallenge(); } } private void goToDetailsActivity() { if (arkChallenge != null) { // Use dispose() method to dismiss the challenge arkChallenge.dispose(); } } } -
Inline Fragment Implementation
InvokeremoveChallengeFragment()using theArkoseChallengeobjectpublic class InlineChallengeActivity extends AppCompatActivity { private ArkoseChallenge arkoseChallenge; /** * Creates and displays the inline Enforcement Challenge fragment embedded into your xml layout. * Sets up all event listeners for challenge lifecycle events. */ private void showInlineChallenge() { try { /* The inline Enforcement Challenge component */ arkoseChallenge = ArkoseManager.createEnforcementChallenge() // Add callback listeners as needed... // Show challenge fragment in the container using commit() for user-initiated action (safe, preserves state) // Alternative: commitAllowingStateLoss() if called from async callbacks getSupportFragmentManager().beginTransaction() .replace(R.id.frameLayout, arkoseChallenge.getEnforcementChallengeFragment()) .commit(); } catch (Exception ex) { Log.e(TAG, Objects.requireNonNull(ex.getMessage())); } } private void hideChallenge() { if (arkoseChallenge != null) { // Call the removeChallengeFragment() method to remove challenege fragment arkoseChallenge.removeChallengeFragment(); } Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.frameLayout); if (fragment != null) { getSupportFragmentManager().beginTransaction() .remove(fragment) .commitAllowingStateLoss(); } arkoseChallenge = null; } }
For the Jetpack Compose approach, refer to Programmatic Enforcement Challenge Dismissal on the main page.
In-SDK Cancel / Reset Buttons
Available from Android SDK v2.22.0.
For the feature overview, the
ArkoseActionConfigreference, the reset loader (showLoaderOnReset), theming, and important notes, refer to In-SDK Cancel / Reset Buttons on the main page. This section covers XML usage.
Usage
Pass the action configs to showEnforcementChallenge(...), then optionally register the reset callbacks:
// e.g
// Configure the buttons. Pass null for either to hide it.
ArkoseActionConfig cancelConfig = new ArkoseActionConfig(
"Cancel", "Closes the challenge and returns to the previous screen");
ArkoseActionConfig resetConfig = new ArkoseActionConfig(
"Reset", "Restarts the challenge");
ArkoseChallenge arkoseChallenge =
ArkoseManager.showEnforcementChallenge(this, cancelConfig, resetConfig);
arkoseChallenge
.addOnResetListener(() -> {
// Invoked after the challenge session has reset.
})
.addOnHideListener(arkoseChallengeResponse -> {
// Invoked when the user taps Cancel (challenge dismissed).
});
showEnforcementChallenge(...)takes the two action configs positionally: pass the Cancel config first and the Reset config second. Swapping them binds each title/label to the wrong button. (In Compose these are named parameters, so call order doesn't matter.) The on-screen layout is always Reset on the left, Cancel on the right.
Resetting with the same configuration
When the user taps Reset, the SDK invokes the optional onPrepareForReset hook (if registered) and then resets the session. If you do not register the hook, the reset proceeds immediately, reusing the current configuration.
arkoseChallenge.addOnPrepareForResetListener(completion -> {
// Runs immediately when the user taps Reset.
// Do any async prep here (e.g. refresh a token, update arkose config), then continue:
completion.run(); // triggers the reset action
});Resetting with a new configuration
To reset with fresh values (for example a new blobData), call ArkoseManager.updateConfig(...) inside onPrepareForReset before calling completion.run(). The new config is staged and applied to the pending reset:
arkoseChallenge.addOnPrepareForResetListener(completion -> {
ArkoseConfig newConfig = new ArkoseConfig.Builder()
.apiKey(<YOUR_PUBLIC_KEY>)
.blobData(<YOUR_NEW_ENCRYPTED_DATA>)
.build();
ArkoseManager.updateConfig(newConfig, arkoseChallenge); // stage the new config
completion.run(); // triggers the reset action
});Dismissing the Challenge on Background Transition
The following code snippet demonstrates how to dismiss the enforcement challenge when your application transitions to the background state.
Dialog Implementation
- Invoke the
dispose()method using theArkoseChallengeinstance in the activity'sonPause()callback.
public class MainActivity extends AppCompatActivity {
private ArkoseChallenge arkChallenge;
@Override
protected void onPause() {
super.onPause();
if (arkChallenge != null) {
arkChallenge.dispose();
}
}
}Inline Fragment Implementation
- Invoke
removeChallengeFragment()using theArkoseChallengeobject in the activity'sonPause()lifecycle method.
public class XmlBasedActivity extends AppCompatActivity {
private ArkoseChallenge arkoseChallenge;
@Override
protected void onPause() {
super.onPause();
hideChallenge();
}
private void hideChallenge() {
if (arkoseChallenge != null) {
// Call the removeChallengeFragment() method to remove challenege fragment
arkoseChallenge.removeChallengeFragment();
}
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.frameLayout);
if (fragment != null) {
getSupportFragmentManager().beginTransaction()
.remove(fragment)
.commitAllowingStateLoss();
}
arkoseChallenge = null;
}
}For the Jetpack Compose approach, refer to Dismissing the Challenge on Background Transition on the main page. For the full list of configuration parameters and callback listeners, see the Android SDK Common API Reference. To capture SDK logs from a device or emulator, see Android SDK Logging and Troubleshooting.
Updated about 23 hours ago