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

# Renderer SDKs

> This guide assists in migrating from FlowX.AI v4.7.x to v5.1.0.

## Angular SDK migration guide

### Upgrading to the new SDK libraries

The Angular SDK node packages have been updated to support Workspaces, introduced in FlowX.AI v5.1.0. All container apps that want to use the new SDKs should update to the latest package versions and implement the required workspace configuration.

<Steps>
  <Step title="Remove old packages and install new FlowX.AI v5.1.0 packages">
    * Remove old FlowX.AI SDK libraries (if upgrading from earlier versions):

    ```bash theme={"system"}
    npm uninstall @flowx/angular-sdk @flowx/core-sdk @flowx/core-theme @flowx/angular-theme @flowx/angular-ui-toolkit
    ```

    * Install new FlowX.AI v5.1.0 packages:

    ```bash theme={"system"}
    npm install \
      @flowx/core-sdk@<version> \
      @flowx/core-theme@<version> \
      @flowx/angular-sdk@<version> \
      @flowx/angular-theme@<version> \
      @flowx/angular-ui-toolkit@<version> \
      @angular/cdk@19 \
      @types/event-source-polyfill
    ```

    <Warning>
      Replace `<version>` with the correct version corresponding to your FlowX.AI v5.1.0 platform version. Check: **Release Notes → v5.1.0 → Deployment guidelines → Component versions**.
    </Warning>
  </Step>

  <Step title="Verify Angular dependencies">
    * Ensure your Angular version is compatible:

    ```bash theme={"system"}
    npm install -g @angular/cli@19
    ```

    * Verify system requirements:
      * Node.js: v20.9.0 or higher
      * npm: v10.1.0 or higher
      * Angular: \~19
  </Step>

  <Step title="Update flx-process-renderer configuration">
    * Run through all the migration steps in the [New SDK API changes](#new-sdk-api-changes) section below.
  </Step>
</Steps>

### SDK API changes

In the Angular SDK, the `<flx-process-renderer>` component has a new mandatory parameter: `workspaceId`.

| Name          | Description                                                                  | Type     | Requirement |
| ------------- | ---------------------------------------------------------------------------- | -------- | ----------- |
| `workspaceId` | **Workspace identifier** that contains the project and process to be started | `string` | Mandatory   |

Add the definition for this property in your component:

```typescript theme={"system"}
export class AppComponent {
  workspaceId = 'your-workspace-id';
  // ... other existing properties
}
```

Use this parameter as input for the `<flx-process-renderer>` component:

```html theme={"system"}
<flx-process-renderer
  [workspaceId]="workspaceId"
  [projectInfo]="projectInfo"
  <!-- ... other existing inputs -->
>
</flx-process-renderer>
```

### Task management component changes

The task management component now also requires workspace context:

```html theme={"system"}
<flx-task-management
  [apiUrl]="apiUrl"
  [authToken]="accessToken"
  [appId]="appId"
  [viewDefinitionId]="viewDefinitionId"
  [workspaceId]="workspaceId"
  <!-- ... other inputs -->
>
</flx-task-management>
```

***

## React SDK migration guide

### Upgrading to the new SDK libraries

The React SDK node packages have been updated to support Workspaces, introduced in FlowX.AI v5.1.0. All container apps that want to use the new SDKs should update to the latest package versions and implement the required workspace configuration.

<Steps>
  <Step title="Remove old packages and install new FlowX.AI v5.1.0 packages">
    * Remove old FlowX.AI SDK libraries:

    ```bash theme={"system"}
    npm uninstall @flowx/react-sdk @flowx/core-sdk @flowx/core-theme @flowx/react-theme @flowx/react-ui-toolkit
    ```

    * Install new FlowX.AI v5.1.0 packages:

    ```bash theme={"system"}
    npm install \
      react@18 \
      react-dom@18 \
      @flowx/core-sdk@<version> \
      @flowx/core-theme@<version> \
      @flowx/react-sdk@<version> \
      @flowx/react-theme@<version> \
      @flowx/react-ui-toolkit@<version> \
      air-datepicker@3 \
      axios \
      ag-grid-react@32
    ```

    <Warning>
      Replace `<version>` with the correct version corresponding to your FlowX.AI v5.1.0 platform version. Check: **Release Notes → v5.1.0 → Deployment guidelines → Component versions**.
    </Warning>
  </Step>

  <Step title="Update React dependencies (if needed)">
    * Ensure your React version is compatible:

    ```bash theme={"system"}
    npm install react@~18 react-dom@~18
    ```

    * Verify Node.js version compatibility:
      * Node.js: v18.16.9 or higher
      * npm: v10.8.0 or higher
  </Step>

  <Step title="Update FlxProcessRenderer configuration">
    * Run through all the migration steps in the [New SDK API changes](#new-sdk-api-changes) section below.
  </Step>
</Steps>

### New SDK API changes

In the React SDK, the `<FlxProcessRenderer>` component has a new mandatory parameter: `workspaceId`.

| Name          | Description                                                                  | Type     | Requirement |
| ------------- | ---------------------------------------------------------------------------- | -------- | ----------- |
| `workspaceId` | **Workspace** identifier that contains the project and process to be started | `string` | Mandatory   |

Add the definition for this property in your component:

```typescript theme={"system"}
const workspaceId = 'your-workspace-id';
```

Use this parameter as input for the `<FlxProcessRenderer>` component:

```tsx theme={"system"}
<FlxProcessRenderer
    workspaceId={workspaceId}
    projectInfo={{ projectId: 'your-project-id' }}
    // ... other existing props
/>
```

### Task management component changes

The task management component now also requires workspace context:

```tsx theme={"system"}
<FlxTaskManager
    apiUrl={baseUrl}
    authToken={authToken}
    appInfo={appInfo}
    viewId={viewId}
    workspaceId={workspaceId}
    // ... other existing props
/>
```

## Android SDK migration guide

### System requirements

System requirements:

* **minSdk = 26**
* **compileSdk = 35**

The SDK library was build using:

* **[Android Gradle Plugin](https://developer.android.com/build/releases/gradle-plugin) 8.11.0**
* **[Gradle](https://gradle.org/releases/) 8.14.2**
* **[Kotlin](https://kotlinlang.org/) 2.2.0**

### Library dependencies

Impactful dependencies:

* **[Android Core KTX](https://developer.android.com/kotlin/ktx#core) 1.16.0**
* **[Android Activity Compose](https://developer.android.com/jetpack/androidx/releases/activity) 1.10.1**
* **[Compose BOM](https://developer.android.com/jetpack/compose/bom/bom-mapping) 2025.09.01**
* **[Compose Navigation](https://developer.android.com/develop/ui/compose/navigation) 2.9.1**
* **[Android Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle) 2.9.1**
* **[Accompanist Permissions](https://google.github.io/accompanist/permissions/) 0.37.3**
* **[Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) 1.10.2**
* **[Android DataStore](https://developer.android.com/jetpack/androidx/releases/datastore) 1.1.7**
* **[Android Preference](https://developer.android.com/jetpack/androidx/releases/preference) 1.2.1**
* **[Android Security](https://developer.android.com/jetpack/androidx/releases/security) 1.1.0-beta01**
* **[OkHttp BOM](https://square.github.io/okhttp/) 4.12.0**
* **[Retrofit](https://square.github.io/retrofit/) 2.12.0**
* **[Moshi](https://github.com/square/moshi) 1.15.2**
* **[Coil BOM](https://coil-kt.github.io/coil/) 3.2.0**
* **[Android Core Library Desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) 2.1.5**

Other dependencies:

* **[Android Annotation](https://developer.android.com/jetpack/androidx/releases/annotation) 1.9.1**
* **[Google Protobuf](https://github.com/protocolbuffers/protobuf/tree/main/java#use-java-protocol-buffers-on-android) 4.31.1**
* **[Mozilla Rhino](https://github.com/mozilla/rhino) 1.8.0**
* **[JetBrains Markdown](https://github.com/JetBrains/markdown) 0.7.3**
* **[CommonMark](https://github.com/commonmark/commonmark-java) 0.25.0**

<Info>
  - **[Koin](https://insert-koin.io/)** dependency has been removed
  - **[Gson](https://github.com/google/gson)** dependency has been removed
</Info>

<Tip>
  Some dependencies were removed, others got updated.<br />
  It is highly recommended that the container projects to be aligned with these versions to avoid any compatibility issues.
</Tip>

### New SDK Migration Guide

To successfully build and run your project with the updated SDK, please follow these sequential steps within your container project:

<Steps>
  <Step title="Update dependency coordinates">
    ```kotlin [rootProject]/app/build.gradle.kts lines theme={"system"}
    implementation("ai.flowx.android:android-sdk:4.0.25") // [!code --]
    implementation("ai.flowx.android:sdk:9.0.2") // [!code ++]
    ```
  </Step>

  <Step title="Increase the `compileSdk` version to `35`">
    ```kotlin [rootProject]/app/build.gradle.kts lines theme={"system"}
    android {
        compileSdk = 34 // [!code --]
        compileSdk = 35 // [!code ++]
    }
    ```
  </Step>

  <Step title="Recommendation: Upgrade JVM compatibility to Java 17">
    ```kotlin [rootProject]/app/build.gradle.kts theme={"system"}
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8 // [!code --]
        sourceCompatibility = JavaVersion.VERSION_17 // [!code ++]
        targetCompatibility = JavaVersion.VERSION_1_8 // [!code --]
        targetCompatibility = JavaVersion.VERSION_17 // [!code ++]
    }

    kotlinOptions { // [!code --]
        jvmTarget = "1.8" // [!code --]
    } // [!code --]
    kotlin { // [!code ++]
        compilerOptions { // [!code ++]
            jvmTarget.set(JvmTarget.JVM_17) // [!code ++]
        } // [!code ++]
    } // [!code ++]
    ```
  </Step>

  <Step title="Update the `Android Gradle plugin` version to at least `8.10.0`">
    ```kotlin [rootProject]/build.gradle.kts theme={"system"}
    plugins {
        id("com.android.application") version "M.m.p" apply false // where M.m.p < 8.10.0 // [!code --]
        id("com.android.application") version "8.10.0" apply false // [!code ++]
    }
    ```
  </Step>

  <Step title="Update `Gradle` version to at least `8.11.1`">
    ```properties [root-project]/gradle/wrapper/gradle-wrapper.properties icon="ini" theme={"system"}
    distributionUrl=https\://services.gradle.org/distributions/gradle-M.m.p-bin.zip # where M.m.p < 8.11.1 [!code --]
    distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip # [!code ++]
    ```
  </Step>

  <Step title="Update `Kotlin` version to at least `2.2.0`">
    ```kotlin [rootProject]/build.gradle.kts theme={"system"}
    plugins {
        id("org.jetbrains.kotlin.android") version "1.m.p" apply false // [!code --]
        id("org.jetbrains.kotlin.android") version "2.2.0" apply false // [!code ++]
    }
    ```
  </Step>

  <Step title="Apply the `Compose Compiler Gradle plugin`">
    ```kotlin [rootProject]/build.gradle.kts theme={"system"}
    plugins {
        id("org.jetbrains.kotlin.plugin.compose") version "2.2.0" apply false // [!code ++]
    }
    ```

    ```kotlin [rootProject]/app/build.gradle.kts theme={"system"}
    plugins {
        id("org.jetbrains.kotlin.plugin.compose") // [!code ++]
    }
    ```

    ```kotlin [rootProject]/app/build.gradle.kts theme={"system"}
    android {
        composeOptions { // [!code --]
            kotlinCompilerExtensionVersion = "1.m.p" // [!code --]
        } // [!code --]
    }
    ```

    <Tip>It is recommended to use the same version as the one configured for the **Kotlin** plugin</Tip>
    <Tip>Consult the [Compose Compiler Migration Guide](https://kotlinlang.org/docs/compose-compiler-migration-guide.html)</Tip>
  </Step>

  <Step title="Enable core library desugaring">
    ```kotlin [rootProject]/app/build.gradle.kts theme={"system"}
    android {
        compileOptions { // [!code ++]
            isCoreLibraryDesugaringEnabled = true // [!code ++]
        } // [!code ++]
    }
    dependencies {
        coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") // [!code ++]
    }
    ```
  </Step>

  <Step title="Ensure your `android.app.Application` extending class implements the `FlowxOwner` interface">
    ```kotlin [rootProject]/app/src/main/[java|kotlin]/com/example/MyApp.kt theme={"system"}
    import ai.flowx.android.sdk.main.FlowxOwner

    class MyApp : Application(), FlowxOwner { // [!code ++]
        override val flowx: Lazy<Flowx> = lazy { Flowx.getInstance() } // [!code ++]
    }
    ```
  </Step>

  <Step title="Rename `FlowxSdkApi` class to `Flowx` and update imports accordingly">
    As a result of this change, all calls to `FlowxSdkApi.getInstance()` must be replaced with `Flowx.getInstance()`.

    ```kotlin theme={"system"}
    import ai.flowx.android.sdk.FlowxSdkApi // [!code --]
    import ai.flowx.android.sdk.main.Flowx // [!code ++]

    FlowxSdkApi.getInstance() // [!code --]
    Flowx.getInstance() // [!code ++]
    ```
  </Step>

  <Step title="Update the `Flowx.getInstance().init()` method parameters">
    <Accordion title="Detailed step-by-step changes" defaultOpen="true">
      <Steps>
        <Step title="Rename the `SdkConfig` class to `Config` and update imports accordingly">
          ```kotlin theme={"system"}
          import ai.flowx.android.sdk.process.model.SdkConfig // [!code --]
          import ai.flowx.android.sdk.api.Config // [!code ++]

          Flowx.getInstance().init(
              context = applicationContext,
              config = SdkConfig(...), // [!code --]
              config = object : Config { ... }, // [!code ++]
          )
          ```

          <Warning>
            The `config` parameter is no longer a `data class`. It is now an `interface`.

            ```kotlin theme={"system"}
            data class SdkConfig(...) // [!code --]
            interface Config { ... } // [!code ++]
            ```
          </Warning>

          <Tip>When passing custom validators as a configuration parameter, the linter might show errors related to inferring the type of the `validators` property. To fix, use `Map<String, (String) -> Boolean>?` as a type.</Tip>
        </Step>

        <Step title="The `enableLog` flag from the configuration parameters has been replaced by the `logEnabled` flag">
          ```kotlin focus={5,9} theme={"system"}
          FlowxSdkApi.getInstance().init( // [!code --]
          Flowx.getInstance().init( // [!code ++]
              context = applicationContext,
              config = SdkConfig( // [!code --]
                  enableLog = true, // [!code --]
              ), // [!code --]
              config = object : Config {
                  //...
                  override val logEnabled: Boolean get() = 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE // [!code ++]
              }, // [!code ++]
          )
          ```
        </Step>

        <Step title="Remove the `accessTokenProvider` parameter from the initialization parameters (if previously used)">
          ```kotlin focus={1,4,5} theme={"system"}
          Flowx.getInstance().init(
              context = applicationContext,
              config = object : Config { ... },
              accessTokenProvider = null, // null by default; can be set later, depending on the existing authentication logic // [!code --]
          )
          ```

          <Tip>Authentication will now be managed by calling the `Flowx.getInstance().setAccessToken(accessToken: String?)` method with the appropriate argument<br /><br />Refer to the following steps for detailed information about the authentication process migration</Tip>
        </Step>

        <Step title="Update main imports for `Custom` provided UI components (if used explicitly)">
          ```kotlin theme={"system"}
          import ai.flowx.android.sdk.ui.components.custom.CustomComponentsProvider // [!code --]
          import ai.flowx.android.sdk.api.custom.components.CustomComponentsProvider // [!code ++]

          import ai.flowx.android.sdk.ui.components.custom.CustomStepperHeaderProvider // [!code --]
          import ai.flowx.android.sdk.api.custom.stepper.CustomStepperHeaderProvider // [!code ++]
          ```
        </Step>

        <Step title="Update `Analytics` related imports">
          ```kotlin theme={"system"}
          // if used explicitly
          import ai.flowx.android.sdk.analytics.AnalyticsCollector // [!code --]
          import ai.flowx.android.sdk.api.analytics.AnalyticsCollector // [!code ++]

          import ai.flowx.android.sdk.analytics.Event // [!code --]
          import ai.flowx.android.sdk.api.analytics.Event // [!code ++]
          ```
        </Step>

        <Step title="Update `NewProcessStartedHandler` related imports (if used explicitly)">
          ```kotlin theme={"system"}
          import ai.flowx.android.sdk.NewProcessStartedHandler // [!code --]
          import ai.flowx.android.sdk.api.NewProcessStartedHandler // [!code ++]
          ```
        </Step>
      </Steps>
    </Accordion>

    **Summary of changes:**

    ```kotlin diff v4.0.25..v9.0.2 focus={2,5,9,13,17,20,24,27,41-52,63} theme={"system"}
    import ai.flowx.android.sdk.FlowxSdkApi // [!code --]
    import ai.flowx.android.sdk.main.FlowxSdk // [!code ++]

    import ai.flowx.android.sdk.process.model.SdkConfig // [!code --]
    import ai.flowx.android.sdk.api.Config // [!code ++]

    // if used explicitly
    import ai.flowx.android.sdk.ui.components.custom.CustomComponentsProvider // [!code --]
    import ai.flowx.android.sdk.api.custom.components.CustomComponentsProvider // [!code ++]

    // if used explicitly
    import ai.flowx.android.sdk.ui.components.custom.CustomStepperHeaderProvider // [!code --]
    import ai.flowx.android.sdk.api.custom.stepper.CustomStepperHeaderProvider // [!code ++]

    // if used explicitly
    import ai.flowx.android.sdk.analytics.AnalyticsCollector // [!code --]
    import ai.flowx.android.sdk.api.analytics.AnalyticsCollector // [!code ++]

    import ai.flowx.android.sdk.analytics.Event // [!code --]
    import ai.flowx.android.sdk.api.analytics.Event // [!code ++]

    // if used explicitly
    import ai.flowx.android.sdk.NewProcessStartedHandler // [!code --]
    import ai.flowx.android.sdk.api.NewProcessStartedHandler // [!code ++]

    FlowxSdkApi.getInstance().init( // [!code --]
    Flowx.getInstance().init( // [!code ++]
        context = applicationContext,
        config = SdkConfig( // [!code --]
            baseUrl = "flowx base url", // [!code --]
            enginePath = "flowx engine path", // [!code --]
            imageBaseUrl = "flowx image base url", // [!code --]
            language = "en", // [!code --]
            locale = Locale.getDefault(), // [!code --]
            validators = mapOf("cnp" to { it.length == 13 }), // a simplified example for custom validator, named "cnp", which checks only the length of the given data // [!code --]
            customHeaders = mapOf("Custom-Header" to "custom header value"), // [!code --]
            updateStateEnabled = true, // [!code --]
            cacheDocuments = true, // [!code --]
            enableLog = true, // [!code --]
        ), // [!code --]
        config = object : Config { // [!code ++]
            override val baseUrl: String = "flowx base url" // [!code ++]
            override val enginePath: String = "flowx engine path" // [!code ++]
            override val imageBaseUrl: String = "flowx image base url" // [!code ++]
            override val language: String = "en" // defaults to "en" // [!code ++]
            override val locale: Locale = Locale.getDefault() // defaults to Locale.getDefault() // [!code ++]
            override val validators: Map<String, (String) -> Boolean>? = mapOf("cnp" to { it.length == 13 }) // a simplified example for custom validator, named "cnp", which checks only the length of the given data // defaults to null // [!code ++]
            override val customHeaders: Map<String, String>? = mapOf("Custom-Header" to "custom header value") // defaults to null // [!code ++]
            override val updateStateEnabled: Boolean = true // defaults to true // [!code ++]
            override val cacheDocuments: Boolean = true // defaults to true // [!code ++]
            override val logEnabled: Boolean get() = 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE // or whatever value fits your use case // [!code ++]
        }, // [!code ++]
        accessTokenProvider = null, // null by default; can be set later, depending on the existing authentication logic // [!code --]
        customComponentsProvider = object : CustomComponentsProvider {...},
        customStepperHeaderProvider = object : CustomStepperHeaderProvider { ... },
        analyticsCollector = { event ->
            when (event) {
                is Event.Screen -> Log.i("Analytics", "Event.Screen(value = ${event.data.value})")
                is Event.Action -> Log.i("Analytics", "Event.Action(value = ${event.data.value}, screen = ${event.data.screen}, component = ${event.data.component}, label = ${event.data.label})")
            }
        },
        onNewProcessStarted = object : NewProcessStartedHandler.Delegate { ... }
    )
    ```
  </Step>

  <Step title="Update the method for providing access tokens">
    The `setAccessTokenProvider` method on the SDK instance has been replaced with `setAccessToken`.

    ```kotlin theme={"system"}
    import ai.flowx.android.sdk.FlowxSdkApi.Companion.AccessTokenProvider // [!code --]

    fun setAccessTokenProvider(accessTokenProvider: AccessTokenProvider) // [!code --]
    fun setAccessToken(accessToken: String?) // null or empty argument clears the token // [!code ++]
    ```

    Consequently, all calls to `setAccessTokenProvider` must be replaced with calls to `setAccessToken`:

    ```kotlin theme={"system"}
    FlowxSdkApi.getInstance().setAccessTokenProvider(accessTokenProvider = { "accessTokenValue" }) // [!code --]
    Flowx.getInstance().setAccessToken("accessTokenValue") // [!code ++]
    ```
  </Step>

  <Step title="`closeModalFunc` is now scoped to the `CloseModalProcessScope`">
    To be able to query for substitution tags or media library items to display them in the `CloseModalProcessConfirmAlert`, the `closeModalFunc` lambda passed as a parameter when starting (i.e. `Flowx.getInstance.startProcess(...)`) or continuing (i.e. `Flowx.getInstance.continueProcess(...)`) is now scoped to the `CloseModalProcessScope` interface, which allows that.

    ```kotlin theme={"system"}
    interface CloseModalProcessScope {
        public fun replaceSubstitutionTag(string: String): String
        public fun getMediaResourceUrl(key: String): String?
    }
    ```

    <Info>This restricts the usage of previously unrestricted exposed functions to only within the context of the `CloseModalProcessScope` receiver (i.e. they can now be called only from inside a @Composable running in this scope).</Info>

    <Tip>
      Methods within the `CloseModalProcessScope` are now called through the actual scope itself, rather than relying on `Flowx.getInstance()`, since they are no longer visible on the SDK instance.
    </Tip>

    ```kotlin theme={"system"}
    import ai.flowx.android.sdk.api.CloseModalProcessScope

    @Composable
    private fun ProcessContent(
        uiState: ProcessViewModel.UiState,
        onProcessEnded: (() -> Unit)? = null,
        onCloseProcessModalFunc: ((processName: String) -> Unit)? = null, // [!code --]
        onCloseProcessModalFunc: (CloseModalProcessScope.(processName: String) -> Unit)? = null, // [!code ++]
    ) {
        when {
            !uiState.projectId.isNullOrBlank() && !uiState.processName.isNullOrBlank() -> {
                Flowx.getInstance().startProcess(
                    projectId = uiState.projectId,
                    processName = uiState.processName,
                    isModal = true,
                    onProcessEnded = { onProcessEnded?.invoke() },
                    closeModalFunc = { processName -> onCloseProcessModalFunc?.invoke(processName) }, // [!code --]
                    closeModalFunc = { processName -> onCloseProcessModalFunc?.invoke(this, processName) }, // [!code ++]
                ).invoke()
            }
            !uiState.processUuid.isNullOrBlank() -> {
                Flowx.getInstance().continueProcess(
                    processUuid = uiState.processUuid,
                    isModal = true,
                    onProcessEnded = { onProcessEnded?.invoke() },
                    closeModalFunc = { processName -> onCloseProcessModalFunc?.invoke(processName) }, // [!code --]
                    closeModalFunc = { processName -> onCloseProcessModalFunc?.invoke(this, processName) }, // [!code ++]
                ).invoke()
            }
        }
    }
    ```

    When using it, an approach could be this:

    ```kotlin ProcessActivity.kt theme={"system"}
    setContent {
        var closeModalProcessScope by remember { mutableStateOf<CloseModalProcessScope?>(null) }
        val showCloseModalProcessAlert = remember { mutableStateOf(false) }

        ProcessContent(
            ...
            onCloseProcessModalFunc = { processName ->
                closeModalProcessScope = this
                showCloseModalProcessAlert.value = true
            },
        )
        closeModalProcessScope?.CloseModalProcessConfirmAlert(show = showCloseModalProcessAlert)
    }

    @Composable
    private fun CloseModalProcessScope.CloseModalProcessConfirmAlert(show: MutableState<Boolean>) {
        if (show.value) {
            AlertDialog(
                ...
                text = { Text(replaceSubstitutionTag("@@close_message")) } // `replaceSubstitutionTag` is now accessible through the `CloseModalProcessScope` scope
            )
        }
    }
    ```
  </Step>

  <Step title="Update custom components implementation">
    Update the related imports:

    ```kotlin theme={"system"}
    import ai.flowx.android.sdk.ui.components.custom.CustomComponentsProvider // [!code --]
    import ai.flowx.android.sdk.api.custom.components.CustomComponentsProvider // [!code ++]

    import ai.flowx.android.sdk.ui.components.custom.CustomComposable // [!code --]
    import ai.flowx.android.sdk.api.custom.components.CustomComponent // [!code ++]

    import ai.flowx.android.sdk.ui.components.custom.CustomComposableComponent // [!code --]
    import ai.flowx.android.sdk.ui.components.custom.CustomView // [!code --]
    import ai.flowx.android.sdk.ui.components.custom.CustomViewComponent // [!code --]

    import ai.flowx.android.sdk.ui.components.custom.CustomComponentAction // [!code --]
    import ai.flowx.android.sdk.api.custom.components.CustomComponentAction // [!code ++]

    import ai.flowx.android.sdk.ui.components.custom.FxEnumerationItem // [!code --]
    import ai.flowx.android.sdk.api.custom.components.FxEnumeration // [!code ++]

    import ai.flowx.android.sdk.api.custom.components.CustomComponentScope // [!code ++]
    ```

    The new class hierarchy structure is as follows:

    <Columns cols={2}>
      ```kotlin v4.0.25 theme={"system"}
      interface CustomComponentsProvider {
          fun provideCustomComposableComponent(): CustomComposableComponent?
          @Deprecated(...) fun provideCustomViewComponent(): CustomViewComponent? = null
      }
      interface CustomComposableComponent {
          fun provideCustomComposable(componentIdentifier: String): CustomComposable?
      }
      interface CustomComposable {
          @Deprecated(...) val isDefined: Boolean
          val composable: @Composable () -> Unit
          fun populateUi(data: Any?)
          fun populateUi(actions: Map<String, CustomComponentAction>)
          fun validate(): Boolean = true
          fun saveData(): JSONObject? = null
      }
      @Deprecated(...)
      interface CustomViewComponent {
          @Deprecated(...) fun provideCustomView(componentIdentifier: String): CustomView
      }
      @Deprecated(...)
      interface CustomView {...}
      ```

      ```kotlin v9.0.2 theme={"system"}
      interface CustomComponentsProvider {
          fun provideCustomComponent(componentIdentifier: String): CustomComponent?
      }




      interface CustomComponent {

          val composable: @Composable CustomComponentScope.() -> Unit
          fun populateUi(data: Any?)
          fun populateUi(actions: Map<String, CustomComponentAction>)
          fun validate(): Boolean = true
          fun saveData(): JSONObject? = null
      }







      ```
    </Columns>

    The structural changes include:

    * The `CustomComposable` class has been renamed to `CustomComponent`.
    * All deprecated properties, methods, and interfaces have been removed. Support for the Android classical View system has been completely discontinued.
    * The `provideCustomComposableComponent()` method within `CustomComponentsProvider` has been replaced with `fun provideCustomComponent(): CustomComponent?`.
    * The `CustomComposableComponent` class has been removed.
    * The provided @Composable function passed to the `composable` property of the `CustomComponent` interface can now only exist and be called within the context of a `CustomComponentScope` receiver.

    ```kotlin theme={"system"}
    interface CustomComponent {
        val composable: @Composable () -> Unit // [!code --]
        val composable: @Composable CustomComponentScope.() -> Unit // [!code ++]
    }
    ```

    ```kotlin theme={"system"}
    interface CustomComponentScope {
        fun executeAction(action: CustomComponentAction, params: JSONObject? = null)
        fun replaceSubstitutionTag(string: String): String
        fun getMediaResourceUrl(key: String): String?
        suspend fun getEnumeration(name: String, parentName: String? = null): FxEnumeration?
    }
    ```

    <Info>This restricts the usage of previously unrestricted exposed functions to only within the context of the `CustomComponentScope` receiver (i.e., they can now be called only from within a custom component implementation).</Info>

    <Tip>
      Methods within the `CustomComponentScope` are now called through the actual scope itself, rather than relying on `Flowx.getInstance()`, since they are no longer visible on the SDK instance.

      ```kotlin theme={"system"}
      Flowx.getInstance().executeAction(...) // [!code --]
      flowxScope.executeAction(...) // [!code ++]

      Flowx.getInstance().replaceSubstitutionTag(...) // [!code --]
      flowxScope.replaceSubstitutionTag(...) // [!code ++]

      Flowx.getInstance().getMediaResourceUrl(...) // [!code --]
      flowxScope.getMediaResourceUrl(...) // [!code ++]

      Flowx.getInstance().getEnumeration(...) // [!code --]
      flowxScope.getEnumeration(...) // [!code ++]
      ```

      Consequently, the following changes should be made to access the scope within the custom component ViewModel:

      ```kotlin MyCustomComponentViewModel.kt theme={"system"}
      class MyCustomComponentViewModel() : ViewModel() {
          private lateinit var flowxScope: CustomComponentScope

          fun setFlowxScope(scope: CustomComponentScope) {
              flowxScope = scope
          }

          fun executeSomeRealAction() {
              actions["someRealAction"]?.let {
                  if (this@MyCustomComponentViewModel::flowxScope.isInitialized) {
                      flowxScope.executeAction(
                          action = it,
                          params = JSONObject() // e.g. JSONObject("{\"someParameter\": \"someValue\"}")
                      )
                  }
              }
          }
      }
      ```

      ```kotlin MyCustomComponent.kt theme={"system"}
      @Composable
      private fun CustomComponentScope.MyCustomComponent(
          viewModel: MyCustomComponentViewModel
      ) {
          viewModel.setFlowxScope(this@MyCustomComponent)

          // here goes the rest of the UI implementation
      }
      ```
    </Tip>

    * The `CustomComponentAction` is no longer a `data class` as before. It is now an `interface`.<br />
      The exposed data has been reduced to the maximum required for executing the action:

    <Columns cols={2}>
      ```kotlin v4.0.25 theme={"system"}
      data class CustomComponentAction(
          internal val actionName: String?,
          internal val type: ActionType?,
          internal var tokenUuid: String?,
          internal var uiActionFlowxUuid: String,
          internal val context: String,
          internal val params: Params?,
          internal val keys: List<String>?,
          internal val customBody: JsonElement?,
          internal val collectionItemData: JsonObject?,
          internal val componentTemplateConfigId: Int,
      )
      ```

      ```kotlin v9.0.2 theme={"system"}
      interface CustomComponentAction {
          val name: String
          val uiTemplateId: Int
          val uiTemplateContext: String
          val uiTemplateReusableContext: String?
      }







      ```
    </Columns>

    * When querying for an enumeration by calling the `CustomComponentScope.getEnumeration(...)` method, the returned type has changed from `List<FxEnumerationItem>` to `FxEnumeration`

    <Columns cols={2}>
      ```kotlin v4.0.25 theme={"system"}
      data class FxEnumerationItem(
          val type: String? = null,
          val order: Int? = null,
          val childContentDescription: ChildContentDescription? = null,
          val code: String? = null,
          val content: String? = null
      ) {
          data class ChildContentDescription(
              val name: String? = null
          )
      }

      ```

      ```kotlin v9.0.2 theme={"system"}
      interface FxEnumeration {
          val name: String
          val items: List<Item>
          val parentName: String?

          interface Item {
              val code: String
              val content: String?
              val childNomenclatorName: String?
              val order: Int
          }
      }
      ```
    </Columns>

    These changes are reflected in an actual implementation as shown below:

    ```kotlin v4.0.25 expandable theme={"system"}
    import ai.flowx.android.sdk.FlowxSdkApi
    import ai.flowx.android.sdk.ui.components.custom.CustomComponentAction
    import ai.flowx.android.sdk.ui.components.custom.CustomComponentsProvider
    import ai.flowx.android.sdk.ui.components.custom.CustomComposable
    import ai.flowx.android.sdk.ui.components.custom.CustomComposableComponent
    import ai.flowx.android.sdk.ui.components.custom.CustomView
    import ai.flowx.android.sdk.ui.components.custom.CustomViewComponent
    import ai.flowx.external.android.template.app.R
    import android.content.Context
    import android.util.AttributeSet
    import android.view.View
    import android.widget.Button
    import android.widget.LinearLayout
    import android.widget.TextView
    import android.widget.Toast
    import androidx.compose.foundation.background
    import androidx.compose.foundation.layout.Arrangement
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.Spacer
    import androidx.compose.foundation.layout.fillMaxWidth
    import androidx.compose.foundation.layout.height
    import androidx.compose.foundation.layout.padding
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Text
    import androidx.compose.material3.TextButton
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.collectAsState
    import androidx.compose.runtime.getValue
    import androidx.compose.runtime.remember
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.graphics.Color
    import androidx.compose.ui.platform.LocalContext
    import androidx.compose.ui.unit.dp
    import androidx.lifecycle.Lifecycle
    import androidx.lifecycle.LifecycleOwner
    import androidx.lifecycle.LifecycleRegistry
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.ViewModelProvider
    import androidx.lifecycle.ViewModelStore
    import androidx.lifecycle.ViewModelStoreOwner
    import androidx.lifecycle.lifecycleScope
    import androidx.lifecycle.viewModelScope
    import androidx.lifecycle.viewmodel.compose.viewModel
    import kotlinx.coroutines.Job
    import kotlinx.coroutines.flow.MutableStateFlow
    import kotlinx.coroutines.flow.asStateFlow
    import kotlinx.coroutines.flow.combine
    import kotlinx.coroutines.launch
    import org.json.JSONObject

    class CustomComponentsProviderImpl : CustomComponentsProvider {
        override fun provideCustomComposableComponent(): CustomComposableComponent? {
            return object : CustomComposableComponent {
                override fun provideCustomComposable(componentIdentifier: String): CustomComposable =
                    object : CustomComposable {

                        val data: MutableStateFlow<Any?> = MutableStateFlow(null)
                        var actions: Map<String, CustomComponentAction> = emptyMap()

                        override val isDefined: Boolean
                            get() = when (componentIdentifier) {
                                "myCustomComponent" -> true // NOTE: set this to false to use the legacy view system instead of compose, which is mainstream now
                                else -> false
                            }

                        override val composable: @Composable () -> Unit = when (componentIdentifier) {
                            "myCustomComponent" -> { {
                                val viewModel = remember { MyCustomComponentViewModel(data, actions) }
                                MyCustomComponent(viewModel = viewModel)
                            } }

                            else -> { {} }
                        }

                        override fun populateUi(data: Any?) {
                            this.data.value = data
                        }

                        override fun populateUi(actions: Map<String, CustomComponentAction>) {
                            this.actions = actions
                        }

                        // Optional override, defaults to `true`.
                        override fun validate(): Boolean = true

                        // Optional override, defaults to `null`.
                        override fun saveData(): JSONObject? = null
                    }
            }
        }

        override fun provideCustomViewComponent(): CustomViewComponent? {
            return object : CustomViewComponent {
                override fun provideCustomView(componentIdentifier: String) = object : CustomView {

                    val data: MutableStateFlow<Any?> = MutableStateFlow(null)
                    var actions: Map<String, CustomComponentAction> = emptyMap()

                    override val isDefined: Boolean
                        get() = when (componentIdentifier) {
                            "myCustomComponent" -> true // NOTE: set the compose equivalent component to false to use the legacy view system instead of compose (which is mainstream now)
                            else -> false
                        }

                    override fun getView(context: Context): View = when (componentIdentifier) {
                        "myCustomComponent" -> myCustomComponent(context, data, actions)
                        else -> View(context)
                    }

                    override fun populateUi(data: Any?) {
                        this.data.value = data
                    }

                    override fun populateUi(actions: Map<String, CustomComponentAction>) {
                        this.actions = actions
                    }
                }
            }
        }
    }

    @Composable
    private fun MyCustomComponent(
        viewModel: MyCustomComponentViewModel = viewModel<MyCustomComponentViewModel>()
    ) {
        val firstName by viewModel.firstName.collectAsState()
        val lastName by viewModel.lastName.collectAsState()
        val dateOfBirth by viewModel.dateOfBirth.collectAsState()

        Column(
            modifier = Modifier.fillMaxWidth(),
            horizontalAlignment = Alignment.CenterHorizontally,
        ) {
            Text(
                text = "Compose Custom Component",
                style = MaterialTheme.typography.titleLarge,
            )
            Spacer(modifier = Modifier.height(16.dp))
            Column(
                modifier = Modifier
                    .background(color = Color(0x80FFFF00))
                    .padding(16.dp)
                    .fillMaxWidth(),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.spacedBy(4.dp)
            ) {
                Text(
                    text = "Client: $firstName $lastName",
                    style = MaterialTheme.typography.titleMedium,
                )
                Text(
                    text = "Date of Birth: $dateOfBirth",
                    style = MaterialTheme.typography.titleMedium,
                )
            }
            val context = LocalContext.current
            TextButton(
                onClick = {
                    // enable and adjust values to test the action (which was prior defined in the process)
    //                viewModel.executeSomeRealAction()
                    Toast.makeText(context, "Define action in the process and enable its execution in the code", Toast.LENGTH_LONG).show()
                }
            ) {
                Text(text = "Confirm")
            }
        }
    }

    private fun myCustomComponent(
        context: Context,
        data: MutableStateFlow<Any?> = MutableStateFlow(null),
        actions: Map<String, CustomComponentAction> = emptyMap(),
    ): View {
        return CustomComponentView(context = context, data = data, actions = actions)
    }

    class CustomComponentView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0,
        data: MutableStateFlow<Any?> = MutableStateFlow(null),
        actions: Map<String, CustomComponentAction> = emptyMap(),
    ) : LinearLayout(context, attrs, defStyleAttr), ViewModelStoreOwner, LifecycleOwner {

        private val registry = LifecycleRegistry(this)

        private var job: Job? = null
        private lateinit var client: TextView
        private lateinit var dateOfBirth: TextView

        private val viewModel: MyCustomComponentViewModel by lazy {
            ViewModelProvider(
                store = viewModelStore,
                factory = object : ViewModelProvider.Factory {
                    override fun <T : ViewModel> create(modelClass: Class<T>): T {
                        @Suppress("UNCHECKED_CAST")
                        return MyCustomComponentViewModel(data, actions) as T
                    }
                }
            )[MyCustomComponentViewModel::class.java]
        }

        init {
            initView()
        }

        private fun initView() {
            View.inflate(context, R.layout.my_custom_component, this)
            client = findViewById<TextView>(R.id.tvClient)
            dateOfBirth = findViewById<TextView>(R.id.tvDateOfBirth)

            findViewById<Button>(R.id.btnConfirm).also {
                it.setOnClickListener {
                    // enable and adjust values to test the action (which was prior defined in the process)
    //                viewModel.executeSomeRealAction()
                    Toast.makeText(context, "Define action in the process and enable its execution in the code", Toast.LENGTH_LONG).show()
                }
            }
        }

        override fun onAttachedToWindow() {
            super.onAttachedToWindow()

            if (job?.isActive == true) {
                job?.cancel()
            }

            job = lifecycleScope.launch {
                combine(
                    viewModel.firstName,
                    viewModel.lastName,
                    viewModel.dateOfBirth
                ) { firstName, lastName, dateOfBirth ->
                    Triple(firstName, lastName, dateOfBirth)
                }.collect { (fn, ln, dob) ->
                    client.text = String.format("Client: %s %s", fn, ln)
                    dateOfBirth.text = String.format("Date of Birth: %s", dob)
                }
            }
        }

        override fun onDetachedFromWindow() {
            job?.cancel()
            job = null
            super.onDetachedFromWindow()
        }

        override val viewModelStore: ViewModelStore = ViewModelStore()

        override val lifecycle: Lifecycle = registry
    }

    class MyCustomComponentViewModel(
        private val data: MutableStateFlow<Any?> = MutableStateFlow(null),
        val actions: Map<String, CustomComponentAction> = emptyMap(),
    ) : ViewModel() {

        private val _firstName = MutableStateFlow("")
        val firstName = _firstName.asStateFlow()

        private val _lastName = MutableStateFlow("")
        val lastName = _lastName.asStateFlow()

        private val _dateOfBirth = MutableStateFlow("")
        val dateOfBirth = _dateOfBirth.asStateFlow()

        init {
            viewModelScope.launch {
                data.collect {
                    _firstName.value = (it as? JSONObject)?.optString("firstname") ?: ""
                    _lastName.value = (it as? JSONObject)?.optString("lastname") ?: ""
                    _dateOfBirth.value = (it as? JSONObject)?.optString("dob") ?: ""
                }
            }
        }

        fun executeSomeRealAction() {
            actions["someRealAction"]?.let {
                FlowxSdkApi.getInstance().executeAction(
                    action = it,
                    params = JSONObject() // e.g. JSONObject("{\"someParameter\": \"someValue\"}")
                )
            } ?: println("MyCustomComponent: `someRealAction` action was not found")
        }
    }
    ```

    ```kotlin v9.0.2 expandable theme={"system"}
    import ai.flowx.android.sdk.api.custom.components.CustomComponent
    import ai.flowx.android.sdk.api.custom.components.CustomComponentAction
    import ai.flowx.android.sdk.api.custom.components.CustomComponentScope
    import ai.flowx.android.sdk.api.custom.components.CustomComponentsProvider
    import android.widget.Toast
    import androidx.compose.foundation.background
    import androidx.compose.foundation.layout.Arrangement
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.Spacer
    import androidx.compose.foundation.layout.fillMaxWidth
    import androidx.compose.foundation.layout.height
    import androidx.compose.foundation.layout.padding
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Text
    import androidx.compose.material3.TextButton
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.getValue
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.graphics.Color
    import androidx.compose.ui.platform.LocalContext
    import androidx.compose.ui.unit.dp
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.compose.collectAsStateWithLifecycle
    import androidx.lifecycle.viewModelScope
    import kotlinx.coroutines.flow.MutableStateFlow
    import kotlinx.coroutines.flow.asStateFlow
    import kotlinx.coroutines.flow.update
    import kotlinx.coroutines.launch
    import org.json.JSONObject

    class CustomComponentsProviderImpl : CustomComponentsProvider {
        override fun provideCustomComponent(componentIdentifier: String): CustomComponent? =
            when (componentIdentifier) {
                "myCustomComponent" -> MyCustomComponent()
                else -> null
            }
    }

    [private|internal] class MyCustomComponent : CustomComponent {
        private val data: MutableStateFlow<Any?> = MutableStateFlow(null)
        private var actions: MutableMap<String, CustomComponentAction> = mutableMapOf()

        private val viewModel = MyCustomComponentViewModel(data, actions)

        override val composable: @Composable (CustomComponentScope.() -> Unit)
            get() = @Composable {
                MyCustomComponent(viewModel = viewModel)
            }

        override fun populateUi(data: Any?) {
            this@MyCustomComponent.data.update { _ -> data }
        }

        override fun populateUi(actions: Map<String, CustomComponentAction>) {
            this@MyCustomComponent.actions.apply {
                clear()
                putAll(actions)
            }
        }

        // Optional override, defaults to `true`.
        override fun validate(): Boolean = true

        // Optional override, defaults to `null`.
        override fun saveData(): JSONObject? = null
    }

    @Composable
    private fun CustomComponentScope.MyCustomComponent(
        viewModel: MyCustomComponentViewModel
    ) {
        viewModel.setFlowxScope(this@MyCustomComponent)

        val firstName by viewModel.firstName.collectAsStateWithLifecycle()
        val lastName by viewModel.lastName.collectAsStateWithLifecycle()
        val dateOfBirth by viewModel.dateOfBirth.collectAsStateWithLifecycle()

        Column(
            modifier = Modifier.fillMaxWidth(),
            horizontalAlignment = Alignment.CenterHorizontally,
        ) {
            Text(
                text = "Compose Custom Component",
                style = MaterialTheme.typography.titleLarge,
            )
            Spacer(modifier = Modifier.height(16.dp))
            Column(
                modifier = Modifier
                    .background(color = Color(0x80FFFF00))
                    .padding(16.dp)
                    .fillMaxWidth(),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.spacedBy(4.dp)
            ) {
                Text(
                    text = "Client: $firstName $lastName",
                    style = MaterialTheme.typography.titleMedium,
                )
                Text(
                    text = "Date of Birth: $dateOfBirth",
                    style = MaterialTheme.typography.titleMedium,
                )
            }
            val context = LocalContext.current
            TextButton(
                onClick = {
                    // enable and adjust values to test the action (which was prior defined in the process)
    //                viewModel.executeSomeRealAction()
                    Toast.makeText(context, "Define action in the process and enable its execution in the code", Toast.LENGTH_LONG).show()
                }
            ) {
                Text(text = "Confirm")
            }
        }
    }

    class MyCustomComponentViewModel(
        val data: MutableStateFlow<Any?> = MutableStateFlow(null),
        private var actions: Map<String, CustomComponentAction> = emptyMap(),
    ) : ViewModel() {
        private lateinit var flowxScope: CustomComponentScope

        private val _firstName = MutableStateFlow("")
        val firstName = _firstName.asStateFlow()

        private val _lastName = MutableStateFlow("")
        val lastName = _lastName.asStateFlow()

        private val _dateOfBirth = MutableStateFlow("")
        val dateOfBirth = _dateOfBirth.asStateFlow()

        fun setFlowxScope(scope: CustomComponentScope) {
            flowxScope = scope
        }

        init {
            viewModelScope.launch {
                data.collect {
                    _firstName.value = (it as? JSONObject)?.optString("firstname") ?: ""
                    _lastName.value = (it as? JSONObject)?.optString("lastname") ?: ""
                    _dateOfBirth.value = (it as? JSONObject)?.optString("dob") ?: ""
                }
            }
        }

        fun executeSomeRealAction() {
            actions["someRealAction"]?.let {
                if (this@MyCustomComponentViewModel::flowxScope.isInitialized) {
                    flowxScope.executeAction(
                        action = it,
                        params = JSONObject() // e.g. JSONObject("{\"someParameter\": \"someValue\"}")
                    )
                }
            } ?: println("MyCustomComponent: `someRealAction` action was not found")
        }
    }
    ```
  </Step>

  <Step title="Update custom stepper header implementation">
    Update the related imports:

    ```kotlin theme={"system"}
    import ai.flowx.android.sdk.ui.components.custom.CustomComposableStepperHeader // [!code --]
    import ai.flowx.android.sdk.ui.components.custom.CustomStepperHeaderData // [!code --]

    import ai.flowx.android.sdk.ui.components.custom.ComposableStepperHeader // [!code --]
    import ai.flowx.android.sdk.api.custom.stepper.CustomStepperHeader // [!code ++]

    import ai.flowx.android.sdk.ui.components.custom.CustomStepperHeaderProvider // [!code --]
    import ai.flowx.android.sdk.api.custom.stepper.CustomStepperHeaderProvider // [!code ++]
    ```

    The new class hierarchy structure is as follows:

    <Columns cols={2}>
      ```kotlin v4.0.25 theme={"system"}
      interface CustomStepperHeaderProvider {
          fun provideCustomComposableStepperHeader(): CustomComposableStepperHeader?
      }
      interface CustomComposableStepperHeader {
          fun provideComposableStepperHeader(): ComposableStepperHeader
      }
      interface ComposableStepperHeader {
          val composable: @Composable (data: CustomStepperHeaderData) -> Unit
      }
      interface CustomStepperHeaderData { ... }
      ```

      ```kotlin v9.0.2 theme={"system"}
      interface CustomStepperHeaderProvider {
          fun provideCustomStepperHeader(): CustomStepperHeader?
      }



      interface CustomStepperHeader {
          val composable: @Composable (data: Data) -> Unit
          interface Data { ... }
      }
      ```
    </Columns>

    The structural changes include:

    * The `ComposableStepperHeader` class has been renamed to `CustomStepperHeader`.
    * The `CustomStepperHeaderData` class has been moved under the `CustomStepperHeader` and renamed to `Data`.
    * The `provideCustomComposableStepperHeader()` method within `CustomStepperProvider` has been replaced with `fun provideCustomStepperHeader(): CustomStepperHeader?`.
    * The `CustomComposableStepperHeader` class has been removed.

    These changes are reflected in an actual implementation as shown below:

    ```kotlin diff v4.0.25..v9.0.2 focus={2,5,10,14,16,18,22,23,26} theme={"system"}
    import ai.flowx.android.sdk.ui.components.custom.CustomStepperHeaderProvider // [!code --]
    import ai.flowx.android.sdk.api.custom.stepper.CustomStepperHeaderProvider // [!code ++]

    import ai.flowx.android.sdk.ui.components.custom.ComposableStepperHeader // [!code --]
    import ai.flowx.android.sdk.api.custom.stepper.CustomStepperHeader // [!code ++]

    import ai.flowx.android.sdk.ui.components.custom.CustomComposableStepperHeader // [!code --]
    import ai.flowx.android.sdk.ui.components.custom.CustomStepperHeaderData

    class CustomStepperHeaderProviderImpl : CustomStepperHeaderProvider {
        override fun provideCustomComposableStepperHeader(): CustomComposableStepperHeader? { // [!code --]
            return object : CustomComposableStepperHeader { // [!code --]
                override fun provideComposableStepperHeader(): ComposableStepperHeader { // [!code --]
                override fun provideCustomStepperHeader(): CustomStepperHeader? { // [!code ++]
                    return object : ComposableStepperHeader { // [!code --]
                    return object : CustomStepperHeader { // [!code ++]
                        override val composable: @Composable (data: CustomStepperHeaderData) -> Unit // [!code --]
                        override val composable: @Composable ((CustomStepperHeader.Data) -> Unit) // [!code ++]
                            get() = @Composable { data ->
                                // custom header implementation
                            }
                    }
                }
            } // [!code --]
        } // [!code --]
    }
    ```
  </Step>

  <Step title="Remove usage of the undocumented `updateConfig(config: SdkConfig)` function">
    For runtime environment changes, use the `changeEnvironment` method available on the SDK instance.

    ```kotlin theme={"system"}
    fun updateConfig(config: SdkConfig) // [!code --]
    fun changeEnvironment(baseUrl: String, imageBaseUrl: String, enginePath: String) // [!code ++]
    ```

    <Warning>Do not change the environment while displaying a running process</Warning>
    <Warning>When changing the environment, ensure the access token is updated properly</Warning>
  </Step>

  <Step title="Update the start process API call, which now requires an extra `workspaceId` parameter">
    ```kotlin diff v4.0.25..v9.0.2 theme={"system"}
    fun startProcess(
        workspaceId: String, // [!code ++]
        projectId: String,
        processName: String,
        params: JSONObject? = null,
        isModal: Boolean = false,
        onProcessEnded: (() -> Unit)? = null,
        closeModalFunc: (CloseModalProcessScope.(processName: String) -> Unit)? = null,
    ): @Composable () -> Unit
    ```

    <Note>The `workspaceId` is the identifier of the workspace that contains the project and process to be started</Note>
  </Step>

  <Step title="Update the theme setup API call, which now requires an extra `workspaceId` parameter">
    ```kotlin diff v4.0.25..v9.0.2 theme={"system"}
    fun setupTheme(
        workspaceUuid: String, // [!code ++]
        themeUuid: String,
        fallbackThemeJsonFileAssetsPath: String? = null,
        appearance: Flowx.ThemeAppearance = Flowx.ThemeAppearance.LIGHT, // or DARK
        @MainThread onCompletion: () -> Unit,
    )
    ```

    <Note>The `workspaceId` is the identifier of the workspace that contains the theme to be loaded</Note>
  </Step>
</Steps>

### ProGuard / R8

The SDK now ships with its own `consumer-rules.pro` file containing the required keep rules for correct functionality.<br />
These rules are automatically merged into the container app’s ProGuard/R8 configuration and applied during code shrinking and obfuscation.<br />
The container app may add additional rules if its specific logic requires them.

## iOS SDK migration guide

### SDK API changes

#### Start process

The start process API require a `workspaceId`.

```swift theme={"system"}
public func startProcess(navigationController: UINavigationController,
                         workspaceId: String, 
                         projectId: String,
                         name: String,
                         params: [String: Any]?,
                         isModal: Bool = false,
                         showLoader: Bool = false,
                         onProcessEnded: (() -> Void)? = nil)
```

```swift theme={"system"}
public func startProcess(tabBarController: UITabBarController,
                         workspaceId: String,
                         projectId: String,
                         name: String,
                         params: [String: Any]?,
                         isModal: Bool = false,
                         showLoader: Bool = false,
                         onProcessEnded: (() -> Void)? = nil)
```

#### Setup theme

The setup theme API requires a `workspaceId`.

```swift theme={"system"}
public func setupTheme(withUuid uuid: String,
                       workspaceId: String,
                       localFileUrl: URL? = nil,
                       appearance: SwiftUI.ColorScheme? = .light,
                       completion: (() -> Void)?)
```
