---
title: "Migration Guide version 5+"
url: "https://developer.incode.com/release-notes/migration-guide/"
section: "release-notes"
group: "Releases Android SDK"
version: "v1.1"
status: "live"
---
# Migration Guide

This page details instructions for migrating to newer versions of the Android Welcome SDK. If you skip versions, apply every section between your current version and the target version, from oldest to newest.

:::info
If you are managing dependency versions manually (without the BOM), refer to the Module Versions section in the [release notes](/release-notes/releases-android-sdk/) for the updated version numbers.
:::

## Migration to 5.54.0



### 1. Renamed `IncodeColorPalette.positive800` to `positive750`

To align with Incode Studio's exported palette, `positive800` has been renamed to `positive750` (same color).

If you previously customized this color via Kotlin:

```kotlin
IncodeWelcome
    .getInstance()
    .setCommonConfig(
        CommonConfig.Builder()
            .setThemeConfig(
                IncodeThemeConfig(
                    colorPalette = IncodeColorPalette(
                        positive800 = Color(0xFF0C5030)
                    )
                )
            )
            .build()
    )
```

use `positive750` instead:

```kotlin
IncodeWelcome
    .getInstance()
    .setCommonConfig(
        CommonConfig.Builder()
            .setThemeConfig(
                IncodeThemeConfig(
                    colorPalette = IncodeColorPalette(
                        positive750 = Color(0xFF0C5030)
                    )
                )
            )
            .build()
    )
```

If you provide the theme via a JSON config, rename the key inside `colorPalette`:

**Before**
```json
{
    "colorPalette": {
        "positive800": "#0C5030"
    }
}
```

**After**
```json
{
    "colorPalette": {
        "positive750": "#0C5030"
    }
}
```

Theme JSON that still uses the old `positive800` key will silently fall back to the default, because unknown keys are ignored during parsing. Update your JSON to use `positive750` to keep your customization applied.

### 2. The Google Wallet flow no longer lives in the ID capture screen

The Google Wallet flow moved out of the ID capture screen and into its own screen. These symbols were public only because they sat in UI classes. They are now internal or removed:

| Symbol | Change |
|---|---|
| `WalletIdErrorScreen` | Now internal |
| `InitIdCaptureUiEvent.TryAgainFromWalletError` | Removed |
| `IdCaptureModuleViewModel.IdCaptureStep.UploadGoogleWalletCredential` | Removed |
| `IdCaptureModuleViewModel.IdCaptureStep.UploadGoogleWalletError` | Removed |
| `IdCaptureModuleViewModel.googleWalletRequestJson` | Removed |
| `IdCaptureModuleViewModel.onGoogleWalletCredentialCaptured(String)` | Removed |
| `IdCaptureModuleViewModel.requestGoogleWallet(Activity, String)` | Removed |

All of them were undocumented, and public only by Kotlin's default visibility rather than by design. The SDK has never offered a way to place its screens in your own layout, and it provides no factory or accessor that hands out an `IdCaptureModuleViewModel`, so there was no supported way to reach these members. If you call any of them, remove the call.

To drive the Google Wallet flow yourself, use the non-UI mode described in [Google Wallet ID](/sdk-reference/android-google-wallet-id/). It hands you a `NonUiGoogleWalletController` through `OnboardingListener.onGoogleWalletReady()` and reports every stage on `NonUiGoogleWalletListener`, which replaces all of the above.

**Kotlin:**
```kotlin
// Before 5.54.0: driving the ID capture ViewModel directly.
idCaptureModuleViewModel.googleWalletRequestJson.collect { requestJson ->
    val result = idCaptureModuleViewModel.requestGoogleWallet(activity, requestJson)
    if (result is WalletsWrapper.Result.Success) {
        idCaptureModuleViewModel.onGoogleWalletCredentialCaptured(result.credentialJson)
    }
}

// 5.54.0: the SDK runs the flow and reports each stage.
object : IncodeWelcome.OnboardingListener() {
    override fun onGoogleWalletReady(controller: NonUiGoogleWalletController) {
        controller.start(activity, lifecycleOwner, object : NonUiGoogleWalletListener {
            override fun onLoading() {}
            override fun onUploading() {}

            // Terminal. A rejected upload arrives here with a non-null exception: there is no SDK
            // screen to retry from in this mode, so start another ID module for another attempt.
            override fun onGoogleWalletCompleted(exception: IncodeException?) {}
            override fun onFallback(reason: GoogleWalletFallbackReason) {}
        })
    }
}
```

**Java:**
```java
// Before 5.54.0: driving the ID capture ViewModel directly.
// idCaptureModuleViewModel.getGoogleWalletRequestJson(), requestGoogleWallet(...),
// and onGoogleWalletCredentialCaptured(...) no longer exist.

// 5.54.0: the SDK runs the flow and reports each stage.
new IncodeWelcome.OnboardingListener() {
    @Override
    public void onGoogleWalletReady(NonUiGoogleWalletController controller) {
        controller.start(activity, lifecycleOwner, new NonUiGoogleWalletListener() {
            @Override public void onLoading() {}
            @Override public void onUploading() {}

            // Terminal. A rejected upload arrives here with a non-null exception: there is no SDK
            // screen to retry from in this mode, so start another ID module for another attempt.
            @Override public void onGoogleWalletCompleted(IncodeException exception) {}
            @Override public void onFallback(GoogleWalletFallbackReason reason) {}
        });
    }
};
```

The document chooser behaves as it did in 5.53.0. Cancelling the credential picker returns the user to document selection, and so does Try Again on the upload-error screen, so no code change is needed there. Direct and non-UI invocation cannot offer another document type, so a rejected upload is terminal there: the ID module ends and `OnboardingListener.onError()` reports it. In non-UI mode it also arrives on `onGoogleWalletCompleted` with a non-null exception. Start another ID module if the user should try again.

## Migration to 5.53.0

<Accordion title="Migration to 5.53.0">

> If you are managing dependency versions manually (without the BOM), refer to the Module Versions section in the [release notes](/release-notes/releases-android-sdk/) for the updated version numbers.

### 1. `IncodeWelcome.Builder.setGoogleWalletEnvironment(...)` and `GoogleWalletEnvironment` have been removed

`setGoogleWalletEnvironment` existed to make Google Wallet requests easier to debug, but its `SANDBOX`/`PRODUCTION` naming did not reflect that purpose, so it has been replaced by `IncodeWelcome.Builder.setGoogleWalletRequestSigned(Boolean)`, which directly controls whether the request declares the signed or unsigned protocol. If you were using `GoogleWalletEnvironment.SANDBOX` to exercise the unsigned protocol for debugging, use `setGoogleWalletRequestSigned(false)` instead.

**Kotlin - before:**
```kotlin
IncodeWelcome.Builder(application, apiUrl, apiKey)
    .setGoogleWalletEnvironment(GoogleWalletEnvironment.SANDBOX)
    .build()
```
**Kotlin - after:**
```kotlin
IncodeWelcome.Builder(application, apiUrl, apiKey)
    .setGoogleWalletRequestSigned(false)
    .build()
```

**Java - before:**
```java
new IncodeWelcome.Builder(application, apiUrl, apiKey)
    .setGoogleWalletEnvironment(GoogleWalletEnvironment.SANDBOX)
    .build();
```
**Java - after:**
```java
new IncodeWelcome.Builder(application, apiUrl, apiKey)
    .setGoogleWalletRequestSigned(false)
    .build();
```

### 2. The user-cancel analytics event is now `userCanceled`

The SDK reports the user-cancel event as `userCanceled`. Earlier versions reported it as `userCancelled`. The new spelling agrees with the other SDK events and with the iOS SDK.

The `Event.USER_CANCELLED` constant keeps its name. Only the reported string value changes. If your listener compares the `Event` enum, you need no change.

If you compare the string value, replace `userCancelled` with `userCanceled`:

**Kotlin - before:**
```kotlin
override fun onEvent(event: Event, eventData: HashMap<String, Any>?) {
    if (event.value == "userCancelled") {
        handleUserCancel()
    }
}
```
**Kotlin - after:**
```kotlin
override fun onEvent(event: Event, eventData: HashMap<String, Any>?) {
    if (event.value == "userCanceled") {
        handleUserCancel()
    }
}
```

**Java - before:**
```java
@Override
public void onEvent(Event event, HashMap<String, Object> eventData) {
    if ("userCancelled".equals(event.getValue())) {
        handleUserCancel();
    }
}
```
**Java - after:**
```java
@Override
public void onEvent(Event event, HashMap<String, Object> eventData) {
    if ("userCanceled".equals(event.getValue())) {
        handleUserCancel();
    }
}
```

Also update each dashboard query, saved report, or server-side rule that matches the literal `userCancelled` name.

### 3. Unit tests that load SQLCipher must run on JVM 17 or later

The SDK now uses `net.zetetic:sqlcipher-android:4.17.0`. This version makes the SDK download about 1.3 MB smaller per device, and it removes unused legacy crypto symbols from the bundled native library. It ships Java 17 bytecode.

Your Android application build is not affected. AGP 8 with JDK 17 reads the new bytecode correctly.

Your unit tests are affected if both of these are true:

* The tests run on the JVM. This includes plain JVM unit tests and Robolectric tests.
* The tests load SQLCipher classes. The tests can load them directly, or through the SDK code paths that use the encrypted database.

If those tests run on a JVM older than 17, raise the test toolchain to 17 or later:

```groovy
// build.gradle of the module that runs the tests
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}
```

Instrumented tests on a device or emulator are not affected.

### 4. Update the Signature V2 title and description string overrides

Signature V2 uses dedicated string resources for the title and description. V1 continues to use the existing string resources.

If you enable V2 and override the V1 strings, add these V2 overrides to your `strings.xml` file:

```xml
<string name="onboard_sdk_signature_capture_title">Your title</string>
<string name="onboard_sdk_signature_capture_description">Your description</string>
```

Keep these V1 overrides if your integration still uses V1:

```xml
<string name="onboard_sdk_signature_title">Your title</string>
<string name="onboard_sdk_signature_description">Your description</string>
```

No change is required if you do not override these strings. The deprecated `Signature.Builder.setTitle(...)` and `setDescription(...)` methods continue to apply to both versions.

### 5. Video Selfie V2 hides the close button on the camera steps

The Video Selfie V2 design hides the close (X) button on the camera steps. `CommonConfig.setShowCloseButton` therefore does not apply to those steps. Video Selfie V1 keeps the button over the camera.

This change applies only when you enable the V2 feature gate.

No code change is required. `setShowCloseButton` continues to apply to the other screens. If your users depend on the close button to leave a camera step, tell them about the new behavior, or keep Video Selfie V1.

</Accordion>

## Migration to 5.51.0

<Accordion title="Migration to 5.51.0">

> If you are managing dependency versions manually (without the BOM), refer to the Module Versions section in the [release notes](/release-notes/releases-android-sdk/) for the updated version numbers.

### 1. Minimum Kotlin version raised to 2.2.x

The SDK is now compiled with Kotlin 2.2.21 (up from 1.9.25), to support the OkHttp/logging-interceptor 5.3.2 upgrade. Any module that compiles Kotlin source with the SDK on its classpath must be able to read the SDK's Kotlin metadata, so your project's Kotlin Gradle plugin must be **2.2.x or newer**. This applies regardless of whether your code calls SDK Kotlin APIs directly - having the SDK on the classpath of a Kotlin compilation is enough to require the newer compiler. Pure-Java modules are unaffected.

No source-level API changes are required on your side - this only affects the minimum Kotlin compiler/plugin version your own build must use.

```groovy
plugins {
    id 'org.jetbrains.kotlin.android' version '2.2.21'
}
```

### 2. Add the required packaging exclusion for OkHttp 5.3.2's duplicate OSGi manifest

OkHttp/logging-interceptor 5.3.2's JPMS module-info jar and the JSpecify jar (a transitive dependency) both ship an identical OSGi manifest at `META-INF/versions/9/OSGI-INF/MANIFEST.MF`. Any app that assembles an APK with OkHttp/logging-interceptor 5.3.2 on its classpath **will** hit a duplicate-resource packaging error from this collision, unless that path is already excluded for another reason. This is a required step for adopting this SDK version, not a fallback for if you happen to see the error - add it to your app's `build.gradle`:

```groovy
android {
    packaging {
        resources {
            excludes += ['META-INF/versions/9/OSGI-INF/MANIFEST.MF']
        }
    }
}
```

This exclusion only drops the OSGi manifest (unused at runtime) and dedups *any* jar colliding on that path, so it also covers future additions (for example, if BouncyCastle is upgraded later and collides on the same manifest).

### 3. Upgrade compileSdk

With the update of the internal CameraX and Kotlin dependencies, you will need to upgrade your project's `compileSdk` to level 36:

```groovy
compileSdk 36
```

### 4. Update Android Gradle Plugin (AGP)

Bumping `compileSdk` to 36 requires Android Gradle Plugin (AGP) `8.9.1` or higher.
Update your project-level `build.gradle` or `settings.gradle` file to use the new plugin version.

```groovy
classpath "com.android.tools.build:gradle:8.9.1"
```

### 5. Update Gradle Wrapper

AGP `8.9.1` requires Gradle `8.14.5` or higher.
Update your Gradle wrapper configuration in `gradle/wrapper/gradle-wrapper.properties`:

```properties
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
```

### 6. Migrate to the Kotlin Compose compiler plugin

The SDK now uses the standalone `org.jetbrains.kotlin.plugin.compose` Gradle plugin (introduced with Kotlin 2.x) instead of `composeOptions.kotlinCompilerExtensionVersion`. If your app configures Compose compilation via `composeOptions`, migrate to the new plugin:

**Before:**
```groovy
android {
    buildFeatures { compose = true }
    composeOptions {
        kotlinCompilerExtensionVersion "1.5.15"
    }
}
```

**After:**
```groovy
plugins {
    id 'org.jetbrains.kotlin.plugin.compose' version '2.2.21'
}

android {
    buildFeatures { compose = true }
    // composeOptions block is no longer needed
}
```

### 7. Upgrade kotlinx-coroutines to 1.9.0

CameraX 1.6.1 requires `kotlinx-coroutines` 1.9.0 or higher. Upgrade your coroutines dependency to at least this version:

```groovy
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0'
```

### 8. Room bumped to 2.8.4

The SDK's internal local storage (Room) is upgraded from 2.6.1 to 2.8.4, to support KSP2 codegen under the Kotlin 2.2.x compiler. Room is used internally only - it is not part of the SDK's public API - so no action is required on your side.

### 9. `deviceStats` removed from `Result` classes that never populated it

The `deviceStats` field has been removed from `BaseResult`. It is now declared only on `IdScanResult` and `SelfieScanResult` - the only results that ever carried it. Every other `*Result` class (e.g. `FaceMatchResult`, `VideoSelfieResult`, `EKYCResult`, `NfcScanResult`) no longer exposes a `deviceStats` field.

If you read `deviceStats` from an `IdScanResult` or `SelfieScanResult`, no change is required. If you read it from any other result type, remove that access - those results always returned the default `DeviceStats(motionStatus = Status.UNCLEAR)` and never carried real device data.

**Kotlin - before:**
```kotlin
val motion = faceMatchResult.deviceStats.motionStatus // always UNCLEAR
```
**Kotlin - after:**
```kotlin
// deviceStats is available only on IdScanResult and SelfieScanResult
val motion = idScanResult.deviceStats.motionStatus
```

**Java - before:**
```java
Status motion = faceMatchResult.deviceStats.getMotionStatus(); // always UNCLEAR
```
**Java - after:**
```java
// deviceStats is available only on IdScanResult and SelfieScanResult
Status motion = idScanResult.deviceStats.getMotionStatus();
```

### 10. `QES` constructor is no longer public - use `QES.Builder`

The `QES` module is now constructed exclusively through `QES.Builder`, matching the other onboarding modules. The direct constructor is no longer accessible, and it also gained new parameters (`uploadDocument`, `providerCode`) in this release, so any previous positional call such as `QES(true)` would have silently changed meaning - the compile error steers you to the unambiguous `QES.Builder` instead.

**Kotlin - before:**
```kotlin
FlowConfig.Builder()
    .addQES(QES(true))
```
**Kotlin - after:**
```kotlin
FlowConfig.Builder()
    .addQES(
        QES.Builder()
            .setDownloadDocument(true)
            .build()
    )
```

**Java - before:**
```java
new FlowConfig.Builder()
    .addQES(new QES(true));
```
**Java - after:**
```java
new FlowConfig.Builder()
    .addQES(
        new QES.Builder()
            .setDownloadDocument(true)
            .build()
    );
```

### 11. `Status` enum gains `UNSUPPORTED` and `COULD_NOT_COMPLETE` values

`Status` gains two new constants, `UNSUPPORTED` and `COULD_NOT_COMPLETE`. `DeviceStats.motionStatus` never emits the new values, so existing runtime behavior for `DeviceStats.motionStatus` is unchanged.

It is source-breaking in Kotlin if you do an exhaustive `when` over `Status` without an `else` branch - such code will now fail to compile until it handles the new constants. A plain Java `switch` statement does not fail to compile when constants are added; it compiles and silently falls through to `default` (or does nothing if there is no `default`), so any Java `switch` over `Status` should add explicit handling for the new values to avoid silently mishandling them.

**Kotlin - before:**
```kotlin
val label = when (status) {
    Status.PASS -> "pass"
    Status.FAIL -> "fail"
    Status.UNCLEAR -> "unclear"
}
```
**Kotlin - after:**
```kotlin
val label = when (status) {
    Status.PASS -> "pass"
    Status.FAIL -> "fail"
    Status.UNCLEAR -> "unclear"
    Status.UNSUPPORTED -> "unsupported"
    Status.COULD_NOT_COMPLETE -> "could not complete"
}
```

**Java - before:**
```java
String label;
switch (status) {
    case PASS: label = "pass"; break;
    case FAIL: label = "fail"; break;
    case UNCLEAR: label = "unclear"; break;
    default: label = "unknown";
}
```
**Java - after:**
```java
String label;
switch (status) {
    case PASS: label = "pass"; break;
    case FAIL: label = "fail"; break;
    case UNCLEAR: label = "unclear"; break;
    case UNSUPPORTED: label = "unsupported"; break;
    case COULD_NOT_COMPLETE: label = "could not complete"; break;
    default: label = "unknown";
}
```

### 10. Minimum Compose Material3 and Compose versions raised to 1.4.0 / 1.8.0

The SDK's Compose-based (V2) screens are now built against **Compose Material3 1.4.0** and **Compose 1.8.0** (foundation / UI). This is not a change to any SDK API - your Kotlin/Java integration code is unaffected - but it raises the minimum versions of these libraries your app must resolve. Because Compose reaches your app transitively and Gradle resolves it with *highest-version-wins*, an app that resolves an older Compose Material3 / Compose set crashes at runtime (`NoSuchMethodError` / `NoClassDefFoundError` in `androidx.compose.*`) when opening a V2 screen such as the phone-number input, a dynamic-form dropdown or date field, or the CURP screen.

Most projects need no action: any app or dependency that pulls Compose Material3 1.4.0 (which itself depends on Compose 1.8.0+) already satisfies this. Act only if you have explicitly pinned these libraries below the required versions - remove the pin or raise it. The two floors are coupled, so aligning Material3 also pulls a compatible Compose set.

</Accordion>

## Migration to 5.50.0

<Accordion title="Migration to 5.50.0">

> If you are managing dependency versions manually (without the BOM), refer to the Module Versions section in the [release notes](/release-notes/releases-android-sdk/) for the updated version numbers.

### 1. Replace `setVideoLivenessRecordingEnabled()` with `setDeepsightConfiguration()`

`SelfieScan.Builder.setVideoLivenessRecordingEnabled()` is deprecated in favor of `setDeepsightConfiguration()`, available on both `SelfieScan.Builder` and `FaceAuthentication.Builder`.

> **Note:** Applies to both `SelfieScan` and `FaceAuthentication`.

| Old | New equivalent |
|-----|----------------|
| `setVideoLivenessRecordingEnabled(false)` | `setDeepsightConfiguration(DeepsightConfiguration.Builder().setModality(DeepsightConfiguration.Modality.SINGLE_FRAME).build())` |
| `setVideoLivenessRecordingEnabled(true)` | `setDeepsightConfiguration(DeepsightConfiguration.Builder().setModality(DeepsightConfiguration.Modality.VIDEO_LIVENESS).build())` |

The new API also exposes the `MULTIMODAL` modality (depth data collected, no video recording) and a `setMotionEnabled()` flag, which previously had no SDK equivalent.

**Before (deprecated):**

```kotlin
SelfieScan.Builder()
    .setVideoLivenessRecordingEnabled(true)
    .build()
```

**After:**

```kotlin
SelfieScan.Builder()
    .setDeepsightConfiguration(
        DeepsightConfiguration.Builder()
            .setModality(DeepsightConfiguration.Modality.VIDEO_LIVENESS)
            .build()
    )
    .build()
```

Java:

```java
new SelfieScan.Builder()
    .setDeepsightConfiguration(
        new DeepsightConfiguration.Builder()
            .setModality(DeepsightConfiguration.Modality.VIDEO_LIVENESS)
            .build()
    )
    .build();
```

</Accordion>

## Migration to 5.49.0

<Accordion title="Migration to 5.49.0">

> If you are managing dependency versions manually (without the BOM), refer to the Module Versions section in the [release notes](/release-notes/releases-android-sdk/) for the updated version numbers.

### 1. Renamed `neutral` and `black` color palette tokens

To align the V2 theme JSON with iOS so a single configuration file works across both platforms, the `neutral` and `black` keys in `IncodeColorPalette` have been renamed:

* `neutral` -> `neutralLight`
* `black`   -> `neutralDark`

If you previously customized these colors via Kotlin:

```kotlin
IncodeWelcome
    .getInstance()
    .setCommonConfig(
        CommonConfig.Builder()
            .setThemeConfig(
                IncodeThemeConfig(
                    colorPalette = IncodeColorPalette(
                        neutral = Color(0xFFFFFFFF),
                        black = Color(0xFF000000)
                    )
                )
            )
            .build()
    )
```

update the parameter names:

```kotlin
IncodeWelcome
    .getInstance()
    .setCommonConfig(
        CommonConfig.Builder()
            .setThemeConfig(
                IncodeThemeConfig(
                    colorPalette = IncodeColorPalette(
                        neutralLight = Color(0xFFFFFFFF),
                        neutralDark = Color(0xFF000000)
                    )
                )
            )
            .build()
    )
```

If you provide the theme via a JSON config, rename the keys inside `colorPalette`:

**Before**
```json
{
    "colorPalette": {
        "neutral": "#FFFFFF",
        "black": "#000000"
    }
}
```

**After**
```json
{
    "colorPalette": {
        "neutralLight": "#FFFFFF",
        "neutralDark": "#000000"
    }
}
```

Theme JSON that still uses the old `neutral` / `black` keys will silently fall back to the defaults (`#FFFFFF` and `#000000`), because unknown keys are ignored during parsing. Update your JSON to use the new keys to keep your customizations applied.

### 2. `SelfieScan.FaceAuthMode.SERVER` deprecated - migrate to `FaceAuthentication`

`SelfieScan.FaceAuthMode.SERVER` is now deprecated. If your integration calls `SelfieScan.Builder().setFaceAuthMode(SelfieScan.FaceAuthMode.SERVER)`, migrate to the [`FaceAuthentication`](/features-and-modules/face-authentication) module instead.

`SelfieScan.FaceAuthMode.LOCAL` (offline face login) and `startFaceLogin()` are not affected and remain fully supported.

### 3. Removed `IncodeWelcome.getReport(...)` and the `ReportListener` interface

The `getReport(interviewId, ReportListener)` method on `IncodeWelcome`, the `ReportListener` callback interface, and the `ResponseEventReport` class have been removed. The backing `/omni/get/report` backend endpoint is deprecated and reports can no longer be generated through the SDK. There is no in-SDK replacement.

If your integration previously invoked `IncodeWelcome.getReport(...)`, remove those calls:

```kotlin
// No longer compiles - remove the call
incodeWelcome.getReport(interviewId, object : ReportListener {
    override fun onReportFetched(uri: Uri?) { /* ... */ }
    override fun onError(error: Throwable) { /* ... */ }
    override fun onUserCancelled() { /* ... */ }
})
```
```java
// No longer compiles - remove the call
incodeWelcome.getReport(interviewId, new ReportListener() {
    @Override public void onReportFetched(Uri uri) { /* ... */ }
    @Override public void onError(Throwable error) { /* ... */ }
    @Override public void onUserCancelled() { /* ... */ }
});
```

### 4. `DocumentScan.Builder` chooser flags deprecated for V2 - migrate to `setDocumentSources(...)`

With the V2 `Document Capture` module enabled, `DocumentScan.Builder.setShowTutorials(...)` and `DocumentScan.Builder.setShowDocumentProviderOptions(...)` no longer affect the flow. The V2 module always opens on an intro screen and configures its source chooser exclusively via the new `setDocumentSources(...)` method. Both flags remain honored in V1.

If you previously used `setShowDocumentProviderOptions(false)` to send the user straight to the camera, configure a single-source set instead:

**Kotlin - before:**
```kotlin
DocumentScan.Builder()
    .setDocumentType(DocumentType.ADDRESS_STATEMENT)
    .setShowDocumentProviderOptions(false)
    .setShowTutorials(false)
    .build()
```
**Kotlin - after:**
```kotlin
DocumentScan.Builder()
    .setDocumentType(DocumentType.ADDRESS_STATEMENT)
    .setDocumentSources(setOf(DocumentScan.DocumentSource.CAMERA))
    .build()
```

**Java - before:**
```java
new DocumentScan.Builder()
    .setDocumentType(DocumentType.ADDRESS_STATEMENT)
    .setShowDocumentProviderOptions(false)
    .setShowTutorials(false)
    .build();
```
**Java - after:**
```java
Set<DocumentScan.DocumentSource> sources = new HashSet<>();
sources.add(DocumentScan.DocumentSource.CAMERA);
new DocumentScan.Builder()
    .setDocumentType(DocumentType.ADDRESS_STATEMENT)
    .setDocumentSources(sources)
    .build();
```

Pass any non-empty subset of `CAMERA`, `FILE_UPLOAD`, and `IMAGE_UPLOAD` to control which submission methods appear on the chooser. For document types that don't accept PDFs (e.g. `DocumentType.MEDICAL_DOC`), `FILE_UPLOAD` is dropped at runtime only when another source remains; configuring exactly `setOf(DocumentScan.DocumentSource.FILE_UPLOAD)` on such a type is rejected with a configuration error before the flow launches, so confirm a camera or image source remains for those document types.

</Accordion>

## Migration to 5.48.0

<Accordion title="Migration to 5.48.0">

> If you are managing dependency versions manually (without the BOM), refer to the Module Versions section in the [release notes](/release-notes/releases-android-sdk/) for the updated version numbers.

### 1. Changed default values for face capture checks in `SelfieScan` and `VideoSelfie` modules

The defaults for `setHeadCoverCheckEnabled(...)` and `setMaskCheckEnabled(...)` on both `SelfieScan.Builder` and `VideoSelfie.Builder` have been changed from `false` to `true`. If you did not previously configure these flags, head cover and face mask validation will now be enforced by default during face capture.

To preserve the previous behavior, explicitly disable them on the builder(s) you use:

```kotlin
SelfieScan.Builder()
    .setHeadCoverCheckEnabled(false)
    .setMaskCheckEnabled(false)
    .build()

VideoSelfie.Builder()
    .setHeadCoverCheckEnabled(false)
    .setMaskCheckEnabled(false)
    .build()
```
```java
new SelfieScan.Builder()
    .setHeadCoverCheckEnabled(false)
    .setMaskCheckEnabled(false)
    .build();

new VideoSelfie.Builder()
    .setHeadCoverCheckEnabled(false)
    .setMaskCheckEnabled(false)
    .build();
```

### 2. SQLCipher attribution required if you ship an open-source licenses screen

Local Room databases used by the SDK are now encrypted at rest with [SQLCipher for Android](https://github.com/sqlcipher/sqlcipher-android), distributed under a BSD-style license. The license requires consumers that redistribute binaries (i.e. your application) to reproduce its copyright notice "in the documentation and/or other materials provided with the distribution".

If your application includes an "Open Source Licenses" screen, please add the SQLCipher notice listed in [Licenses](/sdk-reference/android-licenses). No code change is needed if you do not ship such a screen.

</Accordion>

## Migration to 5.45.1

<Accordion title="Migration to 5.45.1">

> If you are managing dependency versions manually (without the BOM), refer to the Module Versions section in the [release notes](/release-notes/changelog-special/) for the updated version numbers.

</Accordion>

## Migration to 5.45.0

<Accordion title="Migration to 5.45.0">

> If you are managing dependency versions manually (without the BOM), refer to the Module Versions section in the [release notes](/release-notes/changelog-special/) for the updated version numbers.

### 1. Updates to document chooser behavior in `IdScan` module

The visibility of the document chooser screen is now controlled only by the "Show document chooser screen" flag on the Dashboard or `setShowIdTypeChooser(...)` in the SDK.
    * When using startFlow / startWorkflow, the Dashboard setting is respected.
    * When using startOnboarding / startOnboardingSection, the Dashboard setting is ignored and visibility is controlled exclusively via setShowIdTypeChooser(...).
Setting `idType` alone no longer hides the chooser and will be ignored.

If you previously pre-set `idType` (either using `builder.setIdType(...)` in the SDK or via the Dashboard) and expected the chooser to be hidden, you must now explicitly disable it or update the Flow configuration accordingly:

```kotlin
IdScan.Builder()
    .setIdType(IdScan.IdType.ID)
    .setShowIdTypeChooser(false)
    .build()
```
```java
new IdScan.Builder()
    .setIdType(IdScan.IdType.ID)
    .setShowIdTypeChooser(false)
    .build();
```

### 2. Color palette changes

If you previously customized the application appearance by updating these colors from the color palette:

```kotlin
IncodeWelcome
    .getInstance()
    .setCommonConfig(
        CommonConfig.Builder()
            .setThemeConfig(
                IncodeThemeConfig(
                    colorPalette = IncodeColorPalette(
                        negative500 = Color(0xffff5a5f),
                        negative600 = Color(0xffe71111),
                        positive500 = Color(0xff189f60),
                        positive600 = Color(0xff189f60)
                    )
                )
            )
            .build()
    )
```

or using a config JSON:

```json
{
    "colorPalette": {
        "negative500": "#FF5A5F",
        "negative600": "#E71111",
        "positive500": "#189F60",
        "positive600": "#189F60"
    }
}
```

These keys have now been migrated to the following values:
* `negative500` -> `negative400`
* `negative600` -> `negative500`
* `positive500` -> `positive400`
* `positive600` -> `positive500`

The mentioned colors are used for the following Color Modes:
* `Icon/Status/Negative`
* `Icon/Status/Positive`
* `Surface/Status/Positive`
* `Border/Status/Negative Static`
* `Border/Status/Positive Static`

### 3. `ID Capture` V2 - Error Screen Customization

**Wrong document side customization:**

If you previously customized the `Wrong document side` error screen, add the following new string resources:

**Add these strings:**
```xml
<string name="onboard_sdk_id_capture_error_side_front">Capture the front side of the ID</string>
<string name="onboard_sdk_id_capture_error_side_back">Capture the back side of the ID</string>
```

**Previous string:**
```xml
<string name="onboard_sdk_id_capture_error_side">You’ve scanned the wrong document side. Please scan your document again.</string>
```

This string is no longer used.

**No internet connection customization:**

If you previously customized the `No internet connection` error screen, add the following new string resource:

**Add this string:**
```xml
<string name="onboard_sdk_no_internet_title">No internet connection</string>
```

**Previous string:**
```xml
<string name="onboard_sdk_id_capture_error_title">There was a problem</string>
```

The previously used string is still in use for other error screens and should be kept in your resources.

**Retry button customization:**

The retry button is now customized using a different string resource:

```xml
<string name="onboard_sdk_id_capture_retry">Refresh</string>
```

**Previous string:**
```xml
<string name="onboard_sdk_no_network_snackbar_action_text">Retry</string>
```

The previously used string is still in use for other error screens and should be kept in your resources.

### 4. `Selfie` V2 - Error Screen Customization

**No internet connection customization:**

If you previously customized the `No internet connection` error screen, add the following new string resource:

**Add this string:**
```xml
<string name="onboard_sdk_no_internet_title">No internet connection</string>
```

**Previous string:**
```xml
<string name="onboard_sdk_face_scan_failed_feedback_selfie_capture_failed_title">There was a problem</string>
```
The previously used string is still in use for other error screens and should be kept in your resources.

### 5. Changes in behavior for device environment detection

The behavior when detecting device environment vulnerabilities has changed:

* **Hook or virtual environment detection**: Detecting hook or virtual environment vulnerabilities in the SDK triggers a native crash, which cannot be caught or handled by application code, resulting in immediate app termination.
* **Emulator and root detection**: The flow is not aborted when emulator and root checks are detected. The onboarding process continues normally.

### 6. API Changes

#### 6.1 The following `IncodeWelcome.Builder` methods have been removed

```kotlin
IncodeWelcome.Builder.disableVirtualEnvironmentDetection()
IncodeWelcome.Builder.disableRootDetection()
IncodeWelcome.Builder.disableEmulatorDetection()
IncodeWelcome.Builder.disableHookCheck()
```

```java
IncodeWelcome.Builder.disableVirtualEnvironmentDetection()
IncodeWelcome.Builder.disableRootDetection()
IncodeWelcome.Builder.disableEmulatorDetection()
IncodeWelcome.Builder.disableHookCheck()
```

If you were using these methods in your code, remove them from your `IncodeWelcome.Builder` configuration as device environment checks can no longer be disabled.

#### 6.2 The following exceptions have been removed

The following specific exceptions that extended `DeviceEnvironmentException` have been removed:

```kotlin
com.incode.welcome_sdk.commons.exceptions.IncodeException.EmulatorDetectedException
com.incode.welcome_sdk.commons.exceptions.IncodeException.RootDetectedException
com.incode.welcome_sdk.commons.exceptions.IncodeException.HookDetectedException
com.incode.welcome_sdk.commons.exceptions.IncodeException.VirtualEnvironmentDetectedException
```

If you were checking for these `Exceptions` in your code, remove any specific handling of them as your app will no longer get this direct feedback.

### 7. Expected crashes when running in a virtual environment

It is expected that the app crashes with the following stacktraces when a virtual environment is used. For example:

```
java.lang.NullPointerException
    at com.incode.welcome_sdk.ThemeConfiguration$Builder.setLabelSmallStyle(SourceFile:1066)
    at com.incode.welcome_sdk.f.c(SourceFile:150)
    at com.incode.welcome_sdk.data.local.m.as(SourceFile:22)
    at com.incode.welcome_sdk.IncodeWelcome.startOnboardingSection(SourceFile:18)
```

```
java.lang.NullPointerException: Attempt to get length of null array
    at com.incode.welcome_sdk.data.IncodeWelcomeRepository.d(SourceFile:320)
    at com.incode.welcome_sdk.data.IncodeWelcomeRepository.i(SourceFile:214)
```

### 8. `Selfie` V2 - No Internet Error Screen Retry Button Change

**Retry button customization:**

The retry button label shown on the Selfie Scan no internet error screen now uses a dedicated string resource:

```xml
<string name="onboard_sdk_face_scan_retry">Refresh</string>
```

**Previous string:**
```xml
<string name="onboard_sdk_try_again">Try again</string>
```

If you override `onboard_sdk_try_again` to customize the retry button on the no internet screen, you must now override `onboard_sdk_face_scan_retry` instead. 
The `onboard_sdk_try_again` string is still used for other retry scenarios.

### 9. `Selfie` V2 - Capture-Only Mode Success Screen Text Change

**Success label customization:**

In capture-only mode, the Selfie Scan success screen now uses a different string resource:

```xml
<string name="onboard_sdk_face_captured">Face captured!</string>
```

**Previous string:**
```xml
<string name="onboard_sdk_enroll_success">Success!</string>
```

The previously used string is still in use for non-capture-only mode and should be kept in your resources.

### 10. `ID Capture` and `Selfie` V2 - Permission Open Settings Screen Text Change

**Open settings button customization:**

The Open settings label shown on the Permission open settings screen now uses a dedicated string resource:

```xml
<string name="onboard_sdk_permission_allow_permission_action">Allow permission</string>
```

**Previous string:**
```xml
<string name="onboard_sdk_permission_open_setting_action">Open settings</string>
```

The previously used string is still in use for the `Geolocation` module and should be kept in your resources.

</Accordion>

## Migration to 5.44.0

<Accordion title="Migration to 5.44.0">

### 1. Upgrade dependencies

#### Consider migrating to the Incode BOM

To help facilitate easier version upgrades, 5.44.0 introduces the Incode Bill of Materials (BOM). This can be used to upgrade all relevant Incode library dependencies from one place instead of multiple steps to upgrade library dependency versions. To get started,

Replace:

```groovy
implementation 'com.incode.sdk:welcome:5.43.0'
implementation 'com.incode.sdk:core-light:3.0.7'
```
```kotlin
implementation("com.incode.sdk:welcome:5.43.0")
implementation("com.incode.sdk:core-light:3.0.7")
```

With:

```groovy
implementation platform('com.incode.sdk:bom:5.44.0')
implementation 'com.incode.sdk:welcome'
implementation 'com.incode.sdk:core-light'
// Plus any other Incode SDK dependencies... (without the version numbers)
```
```kotlin
implementation(platform("com.incode.sdk:bom:5.44.0"))
implementation("com.incode.sdk:welcome")
implementation("com.incode.sdk:core-light")
// Plus any other Incode SDK dependencies... (without the version numbers)
```

See the [Incode Bill of Materials (BOM) section of the Setup Guide](/sdk-reference/android-installation#add-the-incode-bill-of-materials-bom) for more info.

#### or, Upgrade dependencies manually

If you prefer to continue updating all dependencies manually, be sure to update `welcome` as usual:

```groovy
implementation 'com.incode.sdk:welcome:5.44.0'
```
```kotlin
implementation("com.incode.sdk:welcome:5.44.0")
```

and update the `core-light` dependency to the latest version:

```groovy
implementation 'com.incode.sdk:core-light:3.0.8'
```
```kotlin
implementation("com.incode.sdk:core-light:3.0.8")
```


### 2. Upload Digital ID V2

**Upload error screen customization:**

If you previously customized the upload error screen, add the following new string resource:

**Add this string:**
```xml
<string name="onboard_sdk_validation_error_button_text">Scan your ID</string>
```

**Previous string:**
```xml
<string name="onboard_sdk_id_capture_tutorial_title">Scan your ID</string>
```

This string is now used only for customizing the ID Capture V2 tutorial screen title.

### 3. DocumentType has moved

The `DocumentType` class has been moved from the `com.incode.welcome_sdk.ui.camera.id_validation.base` package
to `com.incode.welcome_sdk.data`.

Replace the following `import` statement

```kotlin
com.incode.welcome_sdk.ui.camera.id_validation.base.DocumentType
```

with

```kotlin
com.incode.welcome_sdk.data.DocumentType
```

### 4. Package name change: `selfie_scan` renamed to `selfie_capture`

The package name `selfie_scan` has been renamed to `selfie_capture`.

Update all imports from `com.incode.welcome_sdk.ui.selfie_scan.*` to `com.incode.welcome_sdk.ui.selfie_capture.*` throughout your codebase.

This change was required due to DexGuard constraints around package naming and obfuscation.

### 5. Transition/Loading screen removed

The transition/loading screen that was shown between modules has been completely removed. If your integration had any customization for this screen (e.g., overriding `onboard_sdk_activity_transition.xml`, setting `IncodeTransitionScreenState.isEnabled`, or customizing transition string resources), those customizations can be safely deleted.

### 6. Migrate any custom `ThemeConfiguration` to the V2 equivalent

If your previous integration had any UI customization in supported modules through [Theme Configuration](/sdk-reference/android-customization#configure-v2-theme-and-ux), these customizations will now need to be migrated to the equivalents in UXv2. See the [Migrating Theme Configurations to UXv2 Guide](/sdk-reference/android-customization#configure-v2-theme-and-ux) for more details.

### 7. Changed default values in `FaceMatch` module config (optional)

With the move to UxV2, the `showUserExists` config is now `false` by default.
To preserve the old behavior, you can use:

```kotlin
FaceMatch.Builder()
  .setShowUserExists(true)
  .build()
```
```java
new FaceMatch.Builder()
  .setShowUserExists(true)
  .build();
```

</Accordion>

## Migration to 5.43.0

<Accordion title="Migration to 5.43.0">

### 1. Update core-light dependency to the latest version

```groovy
implementation 'com.incode.sdk:core-light:3.0.7'
```
```kotlin
implementation("com.incode.sdk:core-light:3.0.7")
```

### 2. If you use any of the following optional dependencies, make sure to update to the latest versions

```groovy
implementation 'com.incode.sdk:nfc:1.5.2'
```
```kotlin
implementation("com.incode.sdk:nfc:1.5.2")
```

### 3. Update string resources for the "Need Help" screen in the `IdScan` v2 module

The "Need Help" screen has been redesigned, and the customizable strings have been replaced.
If you override any of the following strings in your app, replace them with the new ones listed below.

No action is required if you do not override these strings.

#### Replaced string resources

Replace overrides of:

```xml
<string name="onboard_sdk_id_capture_help_title">Need help?</string>
<string name="onboard_sdk_id_capture_help_subtitle">Some considerations</string>
<string name="onboard_sdk_id_capture_help_manual_photo_button_text">Take the photo manually</string>
<string name="onboard_sdk_id_capture_help_align_title">Center your document in the frame</string>
<string name="onboard_sdk_id_capture_help_align_subtitle">The photo will be taken automatically</string>
<string name="onboard_sdk_id_capture_help_blur_title">Avoid blurriness on the document</string>
<string name="onboard_sdk_id_capture_help_blur_subtitle">Zoom in and out, or tap on the document</string>
<string name="onboard_sdk_id_capture_help_glare_title">Avoid glare on the document</string>
<string name="onboard_sdk_id_capture_help_glare_subtitle">Find a better lighting to avoid reflections</string>
<string name="onboard_sdk_id_capture_help_darkness_title">Avoid darkness on the document</string>
<string name="onboard_sdk_id_capture_help_darkness_subtitle">Find a place with better lighting</string>
```

with:

```xml
<string name="onboard_sdk_id_capture_common_issues_title">Common issues</string>
<string name="onboard_sdk_id_capture_common_issues_glare_title">Glare present</string>
<string name="onboard_sdk_id_capture_common_issues_glare_subtitle">Tilt the ID slightly up or down to minimize the reflection</string>
<string name="onboard_sdk_id_capture_common_issues_blur_title">Blur present</string>
<string name="onboard_sdk_id_capture_common_issues_blur_subtitle">Move ID further away or closer to your phone until the image is focused</string>
<string name="onboard_sdk_id_capture_common_issues_info_not_readable_title">Info is not readable</string>
<string name="onboard_sdk_id_capture_common_issues_info_not_readable_subtitle">Minimize camera shake by holding your phone steady</string>
<string name="onboard_sdk_id_capture_common_issues_try_again_button">@string/onboard_sdk_try_again</string>
```

### 4. API Changes

`FaceAuthenticationResult.error` type changed from `FaceAuthenticationException?` to `Throwable?` to support non-domain failures.
Consumers should no longer assume the error is a `FaceAuthenticationException`.

</Accordion>

## Migration to 5.42.0

<Accordion title="Migration to 5.42.0">

### 1. Upgrade compileSdk

With the update of the internal CameraX dependencies, you will need to upgrade your project's `compileSdk` to level 35:

```groovy
compileSdk 35
```

### 2. Update Android Gradle Plugin (AGP)

Bumping `compileSdk` to 35 requires Android Gradle Plugin (AGP) `8.6.0` or higher.
Update your project-level `build.gradle` or `settings.gradle` file to use the new plugin version.

```groovy
classpath "com.android.tools.build:gradle:8.6.0"
```

### 3. Update Gradle Wrapper

AGP `8.6.0` requires Gradle `8.7` or higher.
Update your Gradle wrapper configuration in `gradle/wrapper/gradle-wrapper.properties`:

```properties
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
```

### 4. Update core-light dependency to the latest version

```groovy
implementation 'com.incode.sdk:core-light:3.0.6'
```

### 5. If you use any of the following optional dependencies, make sure to update to the latest versions

```groovy
implementation 'com.incode.sdk:nfc:1.5.1'
```

### 6. API changes

#### 6.1 DeviceEnvironmentException is moved and extended

The `DeviceEnvironmentException` class has been moved from the `com.incode.welcome_sdk.commons.exceptions` package
to `com.incode.welcome_sdk.commons.exceptions.IncodeException`.

Replace the following `import` statement

```kotlin
com.incode.welcome_sdk.commons.exceptions.DeviceEnvironmentException
```

with

```kotlin
com.incode.welcome_sdk.commons.exceptions.IncodeException.DeviceEnvironmentException
```

If you were catching `DeviceEnvironmentExceptions`, you can now also catch more specific exceptions that extend it:

* `EmulatorDetectedException`
* `RootDetectedException`
* `HookDetectedException`
* `VirtualEnvironmentDetectedException`

#### 6.2 PermissionsDeniedException has been replaced

Replace instances of

```kotlin
com.incode.welcome_sdk.commons.exceptions.video_selfie.PermissionsDeniedException
```

with

```kotlin
com.incode.welcome_sdk.commons.exceptions.IncodeException.PermissionNotGranted
```

#### 6.3 PermissionDeniedException has been replaced

Replace instances of

```kotlin
com.incode.welcome_sdk.commons.exceptions.PermissionDeniedException
```

with

```kotlin
com.incode.welcome_sdk.commons.exceptions.IncodeException.PermissionNotGranted
```

#### 6.4 CameraPermissionDeniedException has been replaced

Replace instances of

```kotlin
com.incode.welcome_sdk.commons.exceptions.video_selfie.CameraPermissionDeniedException
```

with

```kotlin
com.incode.welcome_sdk.commons.exceptions.IncodeException.PermissionNotGranted.CameraPermissionNotGranted
```

#### 6.5 MicrophonePermissionDeniedException has been replaced

Replace instances of

```kotlin
com.incode.welcome_sdk.commons.exceptions.video_selfie.MicrophonePermissionDeniedException
```

with

```kotlin
com.incode.welcome_sdk.commons.exceptions.IncodeException.PermissionNotGranted.RecordAudioPermissionNotGranted
```

#### 6.6 ScreenRecordingPermissionDeniedException has been replaced

Replace instances of

```kotlin
com.incode.welcome_sdk.commons.exceptions.video_selfie.ScreenRecordingPermissionDeniedException
```

with

```kotlin
com.incode.welcome_sdk.commons.exceptions.IncodeException.PermissionNotGranted.ScreenCapturePermissionNotGranted
```

#### 6.7 UnknownException has been renamed

Replace instances of

```kotlin
com.incode.welcome_sdk.commons.exceptions.IncodeException.UnknownException
```

with

```kotlin
com.incode.welcome_sdk.commons.exceptions.IncodeException.GenericException
```

#### 6.8 The following unused exceptions have been removed

```kotlin
com.incode.welcome_sdk.commons.exceptions.ApprovalForbiddenException
com.incode.welcome_sdk.commons.exceptions.ExistingSessionException
```

</Accordion>

## Migration to 5.41.0

<Accordion title="Migration to 5.41.0">

### 1. Update core-light dependency to the latest version

```groovy
implementation 'com.incode.sdk:core-light:3.0.5'
```

### 2. If you use any of the following optional dependencies, make sure to update to the latest versions

```groovy
implementation 'com.incode.sdk:nfc:1.5.0'
```

### 3. API changes

#### 3.1 Breaking change in SelfieScanListener

The `SelfieScanListener` interface has been updated to include a new method: `onSelfieScanReady(NonUiSelfieScanController)`.
This change requires all implementations of the `SelfieScanListener` interface to provide an implementation for this new method.
If you don't use non-ui mode, you can leave the method empty.
An example of build error: `does not override abstract method onSelfieScanReady(NonUiSelfieScanController) in SelfieScanListener`

</Accordion>