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

# Android SDK

> Install and configure the FlowX.AI Android SDK to render process-driven UI in native Android apps. Requires Android 8.0+ (API 26).

## Android project requirements

System requirements:

* **minSdk = 26** (Android 8.0)
* **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**

## Installing the library

1. Add the maven repository in your project's `settings.gradle.kts` file:

```kotlin theme={"system"}
dependencyResolutionManagement {
    ...
    repositories {
        ...
        maven {
            url = uri("https://nexus-jx.dev.rd.flowx.ai/repository/flowx-maven-releases/")
            credentials {
                username = "your_username"
                password = "your_password"
            }
        }
    }
}
```

2. Add the library as a dependency in your `app/build.gradle.kts` file:

```kotlin theme={"system"}
dependencies {
    ...
    implementation("ai.flowx.android:sdk:<version>")
    ...
}
```

<Info>
  Replace `<version>` with the correct version corresponding to your platform version.<br />
  To find the right version, navigate to:
  <p>`Release Notes → Choose your platform version → Deployment guidelines → Component versions`</p>
</Info>

### 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**
* **[Accompanist Permissions](https://google.github.io/accompanist/permissions/) 0.37.3**
* **[Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) 1.10.2**
* **[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**
* **[Android Core Library Desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) 2.1.5**
* **[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**

### Public API

The SDK library is managed through the `Flowx` singleton instance, which exposes the following methods:

| Name                   | Description                                                                                                        | Definition                                                                                                                                                                                                                                                                                                                                     |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `init`                 | Initializes the FlowX SDK. Must be called in your application's `onCreate()`                                       | `fun init(context: Context, config: Config, customComponentsProvider: CustomComponentsProvider? = null, customStepperHeaderProvider: CustomStepperHeaderProvider? = null, customLoaderProvider: CustomLoaderProvider? = null, analyticsCollector: AnalyticsCollector? = null, onNewProcessStarted: NewProcessStartedHandler.Delegate? = null)` |
| `setAccessToken`       | Updates the access token                                                                                           | `fun setAccessToken(accessToken: String?)`                                                                                                                                                                                                                                                                                                     |
| `setupTheme`           | Sets up the theme to be used when rendering a process                                                              | `fun setupTheme(workspaceUuid: String, appearance: ThemeAppearance = ThemeAppearance.LIGHT, themeUuid: String, fallbackThemeJsonFileAssetsPath: String? = null, @MainThread onCompletion: () -> Unit)`                                                                                                                                         |
| `changeLocaleSettings` | Changes the current locale settings (i.e. locale and language)                                                     | `fun changeLocaleSettings(locale: Locale, language: String)`                                                                                                                                                                                                                                                                                   |
| `startProcess`         | Starts a FlowX process instance, by returning a `@Composable` function where the process is rendered.              | `fun startProcess(workspaceId: String, projectId: String, processName: String, params: JSONObject? = null, isModal: Boolean = false, onProcessStarted: ((String) -> Unit)? = null, onProcessEnded: (() -> Unit)? = null, closeModalFunc: (CloseModalProcessScope.(processName: String) -> Unit)? = null): @Composable () -> Unit`              |
| `continueProcess`      | Continues an existing FlowX process instance, by returning a `@Composable` function where the process is rendered. | `fun continueProcess(processUuid: String, isModal: Boolean = false, onProcessEnded: (() -> Unit)? = null, closeModalFunc: (CloseModalProcessScope.(processName: String) -> Unit)? = null): @Composable () -> Unit`                                                                                                                             |
| `startUiFlow`          | Starts a FlowX UI Flow, by returning a `@Composable` function where the UI Flow is rendered.                       | `fun startUiFlow(workspaceId: String, projectId: String, uiFlowName: String, params: JSONObject? = null): @Composable () -> Unit`                                                                                                                                                                                                              |

{/*| `configureUpdateState`   | Enables/disables updating a running process' state on return from background                                       | `fun configureUpdateState(enabled: Boolean)`                                                                                                                                                                                                                                                                                                   |*/}

## Configuring the library

To configure the SDK, there are two things needed in the project's application class:

1. first, make it implement the `FlowxOwner` interface:

```kotlin theme={"system"}
class MyApplication : Application(), FlowxOwner {
    override val flowx: Lazy<Flowx> = lazy { Flowx.getInstance() }
    // ...
}
```

2. then, call the `init` method inside the `onCreate()` method:

```kotlin theme={"system"}
fun init(
    context: Context,
    config: Config,
    customComponentsProvider: CustomComponentsProvider? = null,
    customStepperHeaderProvider: CustomStepperHeaderProvider? = null,
    customLoaderProvider: CustomLoaderProvider? = null,
    analyticsCollector: AnalyticsCollector? = null,
    onNewProcessStarted: NewProcessStartedHandler.Delegate? = null,
)
```

#### Parameters

| Name                          | Description                                                                                         | Type                                                                   | Requirement                   |
| ----------------------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------- |
| `context`                     | Android application `Context`                                                                       | `Context`                                                              | Mandatory                     |
| `config`                      | SDK configuration parameters                                                                        | `ai.flowx.android.sdk.api.Config`                                      | Mandatory                     |
| `customComponentsProvider`    | Provider for the `@Composable` custom components                                                    | `ai.flowx.android.sdk.api.custom.components.CustomComponentsProvider?` | Optional. Defaults to `null`. |
| `customStepperHeaderProvider` | Provider for the `@Composable` custom stepper header view                                           | `ai.flowx.android.sdk.api.custom.stepper.CustomStepperHeaderProvider?` | Optional. Defaults to `null`. |
| `customLoaderProvider`        | Provider for the `@Composable` custom loader view                                                   | `ai.flowx.android.sdk.api.custom.loader.CustomLoaderProvider?`         | Optional. Defaults to `null`. |
| `analyticsCollector`          | Collector interface for SDK analytics events                                                        | `ai.flowx.android.sdk.api.analytics.AnalyticsCollector`                | Optional. Defaults to `null`. |
| `onNewProcessStarted`         | Callback for when a new process was started as a consequence for executing a `START_PROJECT` action | `ai.flowx.android.sdk.api.NewProcessStartedHandler.Delegate`           | Optional. Defaults to `null`. |

• The `custom components` implementation is explained in [its own section](#custom-components).<br />
• The implementation for providing a `custom view for the header` of the [Stepper](../docs/building-blocks/process/navigation-areas#stepper) component is detailed in [its own section](#custom-header-view-for-the-stepper-component).<br />
• The implementation for providing a `custom loader` is explained in [its own section](#custom-loaders).<br />
• Collecting analytics events from the SDK is explained in [its own section](#collecting-analytics-events).<br />
• Handling the start of a new process while in a running process is explained in [its own section](#handling-“start-of-a-new-process”).<br />

#### Sample

```kotlin theme={"system"}
class MyApplication : Application(), FlowxOwner {
    override val flowx: Lazy<Flowx> = lazy { Flowx.getInstance() }

    override fun onCreate() {
        super.onCreate()
        initFlowXSdk()
    }

    private fun initFlowXSdk() {
        Flowx.getInstance().init(
            context = applicationContext,
            config = object : Config {
                override val organizationId: String = "organization id"
                override val baseUrl = "URL to FlowX backend",
                override val imageBaseUrl = "URL to FlowX CMS Media Library",
                override val enginePath = "some_path",
                override val language = "en",
                override val locale = Locale.getDefault(),
                override val validators: Map<String, (String) -> Boolean>? = mapOf("exact_25_in_length" to { it.length == 25 }),
                override val logEnabled: Boolean get() = 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE,
            },
            customComponentsProvider = object : CustomComponentsProvider {...},
            customStepperHeaderProvider = object : CustomStepperHeaderProvider {...},
            customLoaderProvider = object : CustomLoaderProvider {...},
            analyticsCollector = { event ->
                // Process / Send the event to some specialized Analytics platform
            },
            onNewProcessStarted = { processInstanceUuid ->
                // Send a broadcast message to notify the Activity currently displaying the running process.
                // The Activity should handle the broadcast to reload and display the newly started process identified by `processInstanceUuid`.
            }
        )
    }
}
```

The configuration properties that should be passed as `Config` data for the `config` parameter above are:

| Name             | Description                                                         | Type                                | Requirement                                 |
| ---------------- | ------------------------------------------------------------------- | ----------------------------------- | ------------------------------------------- |
| `organizationId` | Id of the organization used by FLOWX to identify your installation  | `String`                            | Mandatory                                   |
| `baseUrl`        | URL to connect to the FlowX back-end environment                    | `String`                            | Mandatory                                   |
| `imageBaseUrl`   | URL to connect to the FlowX Media Library module of the CMS         | `String`                            | Mandatory                                   |
| `enginePath`     | URL path segment used to identify the process engine service        | `String`                            | Mandatory                                   |
| `language`       | The language used for retrieving enumerations and substitution tags | `String`                            | Optional. Defaults to `en`.                 |
| `locale`         | The locale used for date, number and currency formatting            | `java.util.Locale`                  | Optional. Defaults to `Locale.getDefault()` |
| `validators`     | Custom validators for form elements                                 | `Map<String, (String) -> Boolean>?` | Optional.                                   |
| `logEnabled`     | Flag indicating if logs should be printed                           | `Boolean`                           | Optional. Defaults to `false`               |

#### Custom validators

The `custom validators` map is a collection of lambda functions, referenced by *name* (i.e. the value of the `key` in this map), each returning a `Boolean` based on the `String` which needs to be validated.
For a custom validator to be evaluated for a form field, its *name* must be specified in the form field process definition.

<Tip>
  By looking at the example from above:

  ```kotlin theme={"system"}
  mapOf("exact_25_in_length" to { it.length == 25 })
  ```

  if a form element should be validated using this lambda function, a custom validator named `"exact_25_in_length"` should be specified in the process definition.
</Tip>

## Using the library

### Authentication

To be able to use the SDK, **authentication is required**. Therefore, before calling any other method on the singleton instance, make sure that the access token is set by calling:

```kotlin theme={"system"}
Flowx.getInstance().setAccessToken(accessToken = "your access token")
```

<Tip>Whenever the access token changes based on your own authentication logic, it must be updated in the renderer by calling the `setAccessToken` method again.</Tip>
<Tip>Passing `null` or empty string (`""`) as an argument to the `setAccessToken` method clears the token</Tip>

### Theming

<Warning>Prior setting up the theme, make sure the `access token` was set.<br />Check the [authentication](#authentication) section for details.</Warning>

To be able to use styled components while rendering a process, the theming mechanism must be invoked by calling the `suspend`-ing `setupTheme(...)` method over the singleton instance of the SDK:

```kotlin theme={"system"}
suspend fun setupTheme(
    workspaceUuid: String,
    themeUuid: String,
    fallbackThemeJsonFileAssetsPath: String? = null,
    appearance: ThemeAppearance = ThemeAppearance.LIGHT,
    @MainThread onCompletion: () -> Unit
)
```

#### Parameters

| Name                              | Description                                                                                                                                                  | Type                    | Requirement                                        |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- | -------------------------------------------------- |
| `workspaceUuid`                   | UUID string identifier of the workspace that contains the theme to be loaded                                                                                 | `String`                | Mandatory. Should not be empty                     |
| `themeUuid`                       | UUID string of the theme configured in FlowX Designer                                                                                                        | `String`                | Mandatory. Can be empty                            |
| `fallbackThemeJsonFileAssetsPath` | Android asset relative path to the corresponding JSON file to be used as fallback, in case fetching the theme fails and there is no cached version available | `String?`               | Optional. Defaults to `null`                       |
| `appearance`                      | Indicator for the appearance of the theme (LIGHT, DARK)                                                                                                      | `Flowx.ThemeAppearance` | Options. Defaults to `Flowx.ThemeAppearance.LIGHT` |
| `onCompletion`                    | `@MainThread` invoked closure, called when setting up the theme completes                                                                                    | `() -> Unit`            | Mandatory                                          |

<Note>
  If the `themeUuid` parameter value is empty (`""`), no theme will be fetched, and the mechanism will rely only on the fallback file, if set.<br /><br />
  If the `fallbackThemeJsonFileAssetsPath` parameter value is `null`, there will be no fallback mechanism set in place, meaning if fetching the theme fails, the redered process will have no style applied over it's displayed components.
</Note>

<Note>
  The SDK caches the fetched themes, so if a theme fetch fails, a cached version will be used, if available. Otherwise, it will use the file given as fallback.
</Note>

#### Sample

```kotlin theme={"system"}
viewModelScope.launch {
    Flowx.getInstance().setupTheme(
        workspaceUuid = "some workspace uuid string",
        themeUuid = "some uuid string",
        fallbackThemeJsonFileAssetsPath = "theme/a_fallback_theme.json",
        appearance = Flowx.ThemeAppearance.LIGHT,
    ) {
        // theme setup complete
        // do specific logic
    }
}
```

<Tip>
  The `fallbackThemeJsonFileAssetsPath` always search for files under your project's `assets/` directory, meaning the example parameter value is translated to `file://android_asset/theme/a_fallback_theme.json` before being evaluated.
</Tip>

<Warning>Do not [start](#start-a-flowx-process) or [resume](#resume-a-flowx-process) a process before the completion of the theme setup mechanism.</Warning>

### Changing current locale settings

The current `locale` and `language` can be also changed after the [initial setup](#configuring-the-library), by calling the `changeLocaleSettings` function:

```kotlin theme={"system"}
fun changeLocaleSettings(locale: Locale, language: String)
```

#### Parameters

| Name       | Description                   | Type               | Requirement |
| ---------- | ----------------------------- | ------------------ | ----------- |
| `locale`   | The new locale                | `java.util.Locale` | Mandatory   |
| `language` | The code for the new language | `String`           | Mandatory   |

<Warning>
  **Do not change the locale or the language while a process is rendered.**<br />
  The change is successful only if made before [starting](#start-a-flowx-process) or [resuming](#resume-a-flowx-process) a process.
</Warning>

#### Sample

```kotlin theme={"system"}
Flowx.getInstance().changeLocaleSettings(locale = Locale("en", "US"), language = "en")
```

<Info>
  The `Locale` satisfies the IETF BCP 47 standard for representing language and country/region codes.<br /><br />
  More information regarding the standard can be found by reading [RFC 4647 "Matching of Language Tags"](https://datatracker.ietf.org/doc/html/rfc4647) and [RFC 5646 "Tags for Identifying Languages"](https://datatracker.ietf.org/doc/html/rfc5646).<br /><br />
  An example of BCP 47 is `en-US` (language code `en` and country `US`).
</Info>

### Start a FlowX process

<Warning>Prior starting a process, make sure the [authentication](#authentication) and [theming](#theming) were correctly set up</Warning>

After performing all the above steps and all the prerequisites are fulfilled, a new instance of a FlowX process can be started, by using the `startProcess` function:

```kotlin theme={"system"}
fun startProcess(
    workspaceId: String,
    projectId: String,
    processName: String,
    params: JSONObject? = null,
    isModal: Boolean = false,
    onProcessStarted: ((String) -> Unit)? = null,
    onProcessEnded: (() -> Unit)? = null,
    closeModalFunc: (CloseModalProcessScope.(processName: String) -> Unit)? = null,
): @Composable () -> Unit
```

#### Parameters

| Name               | Description                                                                                        | Type                                                      | Requirement                                         |
| ------------------ | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------- |
| `workspaceId`      | The id of the workspace that contains the project and process to be started                        | `String`                                                  | Mandatory                                           |
| `projectId`        | The id of the project containing the process to be started                                         | `String`                                                  | Mandatory                                           |
| `processName`      | The name of the process                                                                            | `String`                                                  | Mandatory                                           |
| `params`           | The starting params for the process, if any                                                        | `JSONObject`                                              | Optional. If omitted, if defaults to `JSONObject()` |
| `isModal`          | Flag indicating whether the process can be closed at anytime by tapping the top-right close button | `Boolean`                                                 | Optional. It defaults to `false`.                   |
| \`onProcessStarted | Lambda function where you can do additional processing when the process starts                     | `((String) -> Unit)?`                                     | Optional. It defaults to `null`.                    |
| `onProcessEnded`   | Lambda function where you can do additional processing when the started process ends               | `(() -> Unit)?`                                           | Optional. It defaults to `null`.                    |
| `closeModalFunc`   | Lambda function where you should handle closing the process when `isModal` flag is `true`          | `(CloseModalProcessScope.(processName: String) -> Unit)?` | Optional. It defaults to `null`.                    |

<Tip>
  The returned **[@Composable](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable)** function must be included in its own **[Activity](https://developer.android.com/reference/android/app/Activity)**, which is part of (controlled and maintained by) the container application.<br /><br />
  This wrapper activity must display only the `@Composable` returned from the SDK (i.e. it occupies the whole activity screen space).
</Tip>

#### Sample

```kotlin theme={"system"}
class ProcessActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        setContent {
            Flowx.getInstance().startProcess(
                workspaceId = "your workspace id",
                projectId = "your project id",
                processName = "your process name",
                params: JSONObject = JSONObject(),
                isModal = true,
                onProcessStarted = { processInstanceUuid ->
                    // NOTE: possible processing could involve doing something based on the `processInstanceUuid` value received as argument
                },
                onProcessEnded = {
                    // NOTE: possible processing could involve doing something in the container app (i.e. navigating to a different screen)
                },
                closeModalFunc = { processName ->
                    // NOTE: possible handling could involve doing something differently based on the `processName` value
                },
            ).invoke()
        }
    }
    ...
}
```

### Resume a FlowX process

<Warning>Prior resuming process, make sure the [authentication](#authentication) and [theming](#theming) were correctly set up</Warning>

To resume an existing instance of a FlowX process, after fulfilling all the prerequisites, use the `continueProcess` function:

```kotlin theme={"system"}
fun continueProcess(
    processUuid: String,
    isModal: Boolean = false,
    onProcessEnded: (() -> Unit)? = null,
    closeModalFunc: (CloseModalProcessScope.(processName: String) -> Unit)? = null,
): @Composable () -> Unit
```

#### Parameters

| Name             | Description                                                                                        | Type                                                      | Requirement                       |
| ---------------- | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | --------------------------------- |
| `processUuid`    | The UUID string of the process                                                                     | `String`                                                  | Mandatory                         |
| `isModal`        | Flag indicating whether the process can be closed at anytime by tapping the top-right close button | `Boolean`                                                 | Optional. It defaults to `false`. |
| `onProcessEnded` | Lambda function where you can do additional processing when the continued process ends             | `(() -> Unit)?`                                           | Optional. It defaults to `null`.  |
| `closeModalFunc` | Lambda function where you should handle closing the process when `isModal` flag is `true`          | `(CloseModalProcessScope.(processName: String) -> Unit)?` | Optional. It defaults to `null`.  |

<Tip>
  The returned **[@Composable](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable)** function must be included in its own **[Activity](https://developer.android.com/reference/android/app/Activity)**, which is part of (controlled and maintained by) the container application.<br /><br />
  This wrapper activity must display only the `@Composable` returned from the SDK (i.e. it occupies the whole activity screen space).
</Tip>

#### Sample

```kotlin theme={"system"}
class ProcessActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        setContent {
            Flowx.getInstance().continueProcess(
                processUuid = "some process UUID string",
                isModal = true,
                onProcessEnded = {
                    // NOTE: possible processing could involve doing something in the container app (i.e. navigating to a different screen)
                },
                closeModalFunc = { processName ->
                    // NOTE: possible handling could involve doing something differently based on the `processName` value
                },
            ).invoke()
        }
    }
    ...
}
```

### `closeModalFunc` parameter

The `closeModalFunc` parameter is a function defined within the `CloseModalProcessScope` context.

This gives the ability to query for substitution tags or media library items to use them when handling this callback (i.e. showing an snackbar or an alert).

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

#### Get a substitution tag value by key

```kotlin theme={"system"}
fun replaceSubstitutionTag(key: String): String
```

All substitution tags will be retrieved by the SDK before starting the process and will be stored in memory.

Whenever the container app needs a substitution tag value for populating the UI of the custom components, it can request the substitution tag, through the `CustomComponentScope` context, using the method above, by providing the `key`.

It returns:

* the key's counterpart, if the `key` is valid and found
* the empty string, if the `key` is valid, but not found
* the unaltered string, if the key has the wrong format (i.e. not starting with `@@`)

#### Get a media item url by key

```kotlin theme={"system"}
fun getMediaResourceUrl(key: String): String?
```

All media items will be retrieved by the SDK before starting the process and will be stored in memory.

Whenever the container app needs a media item url for populating the UI of the custom components, it can request the url, through the `CustomComponentScope` context, using the method above, by providing the `key`.

It returns the `URL` string of the media resource, or `null`, if not found.

#### Sample

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

    Flowx.getInstance().startProcess(
        workspaceId = "your workspace id",
        projectId = "your project id",
        processName = "your process name",
        isModal = true,
        onProcessStarted= { ... },
        onProcessEnded = { ... },
        onCloseProcessModalFunc = { processName ->
            closeModalProcessScope = this
            showCloseModalProcessAlert.value = true
        },
    ).invoke()

    closeModalProcessScope?.CloseModalProcessConfirmAlert(show = showCloseModalProcessAlert)
}

@Composable
fun CloseModalProcessScope.CloseModalProcessConfirmAlert(show: MutableState<Boolean>) {
    if (show.value) {
        AlertDialog(
            onDismissRequest = {},
            title = null,
            text = {
                Text(this@CloseModalProcessConfirmAlert.replaceSubstitutionTag("@@close_message")) // IMPORTANT: call `replaceSubstitutionTag` using the `CloseModalProcessScope` context
            },
            confirmButton = { ... },
            dismissButton = {
                Button(onClick = { show.value = false }) {
                    Text("Cancel")
                }
            }
        )
    }
}
```

### Start an UI Flow

<Warning>Prior starting an UI Flow, make sure the [authentication](#authentication) and [theming](#theming) were correctly set up</Warning>

After performing all the above steps and all the prerequisites are fulfilled, an UI Flow can be started, by using the `startUiFlow` function:

```kotlin theme={"system"}
fun startUiFlow(
    workspaceId: String,
    projectId: String,
    uiFlowName: String,
    params: JSONObject? = null,
): @Composable () -> Unit
```

#### Parameters

| Name          | Description                                                                 | Type         | Requirement                                         |
| ------------- | --------------------------------------------------------------------------- | ------------ | --------------------------------------------------- |
| `workspaceId` | The id of the workspace that contains the project and UI Flow to be started | `String`     | Mandatory                                           |
| `projectId`   | The id of the project containing the UI Flow to be started                  | `String`     | Mandatory                                           |
| `uiFlowName`  | The name of the UI Flow                                                     | `String`     | Mandatory                                           |
| `params`      | The starting params for the process, if any                                 | `JSONObject` | Optional. If omitted, if defaults to `JSONObject()` |

<Tip>
  The returned **[@Composable](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable)** function must be included in its own **[Activity](https://developer.android.com/reference/android/app/Activity)**, which is part of (controlled and maintained by) the container application.<br /><br />
  This wrapper activity must display only the `@Composable` returned from the SDK (i.e. it occupies the whole activity screen space).
</Tip>

#### Sample

```kotlin theme={"system"}
class UiFlowActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        setContent {
            Flowx.getInstance().startUiFlow(
                workspaceId = "your workspace id",
                projectId = "your project id",
                uiFlowName = "your ui flow name",
                params = JSONObject(),
            ).invoke()
        }
    }
    ...
}
```

## Custom components

The container application should decide which custom component view to provide using the `componentIdentifier` configured in the UI designer.

A custom component receives `data` to populate the view and `actions` available to execute, as described below.<br />
It can also be validated and provide data back into the process when executing an action.

To handle custom components, an *implementation* of the `CustomComponentsProvider` interface should be passed as a parameter when initializing the SDK:

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

#### Sample

```kotlin theme={"system"}
class FxCustomComponentsProvider : CustomComponentsProvider {
    override fun provideCustomComponent(componentIdentifier: String): CustomComponent? =
        when (componentIdentifier) {
            "myCustomComponent" -> object : CustomComponent {...}
            else -> null
        }
}
```

### CustomComponent

The implementation for providing a custom component is based on creating and binding a user defined [@Composable](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable) function, through the `CustomComponent` interface:

```kotlin theme={"system"}
interface CustomComponent {
    /**
     * Returns the [Composable]s for every custom component identifier defined in the FlowX Designer
     */
    val composable: @Composable CustomComponentScope.() -> Unit

    /**
     * This will be called when data is available for the custom component i.e. when the
     * User Task that contains the custom component is displayed.
     *
     * @param data used to populate the custom component
     */
    fun populateUi(data: Any?)

    /**
     * This will be called when actions are available for the custom component i.e. when the
     * User Task that contains the custom component is displayed.
     *
     * @param actions that need to be attached to the custom component (e.g. onClick events)
     */
    fun populateUi(actions: Map<String, CustomComponentAction>)

    /**
     * This will be called when executing an action, when the platform needs to know if the specified/marked components are valid.
     * Defaults to `true`.
     */
    fun validate(): Boolean = true

    /**
     * This will be called when executing an action, on computing the data to be sent as body on the network request.
     * Returning `null` (i.e. default) means it does not contribute with any data to be sent.
     */
    fun saveData(): JSONObject? = null
}
```

<Tip>
  The value for the `data` parameter received in the `populateUi(data: Any?)` could be:<br />

  * `Boolean`
  * `String`
  * `java.lang.Number`
  * `org.json.JSONObject`
  * `org.json.JSONArray`

  The appropriate way to check and cast the data accordingly to the needs must belong to the implementation details of the custom component.
</Tip>

<Tip>
  Both validation and providing data back into process are optional, and, based on the needs, it may be included in the implementation or not.
</Tip>

### CustomComponentScope

The `composable` property of the [CustomComponent](#customcomponent) is a [@Composable](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable) function which may be defined and run only within the context of a `CustomComponentScope` receiver.

```kotlin theme={"system"}
val composable: @Composable CustomComponentScope.() -> Unit
```

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

This allows calling predefined SDK methods for executing actions, querying for substitution tags, media resource URLs or obtaining enumerations data, directly from the custom component through the scope itself.

#### Execute action

The custom components which the container app provides may contain FlowX actions available for execution.<br /><br />
These actions are received through the `actions` parameter of the `populateUi(actions: Map<String, CustomComponentAction>)` method.<br /><br />
To run an action (e.g., on a click of a button in the custom component) you need to call the `executeAction` method, through the `CustomComponentScope` context:

```kotlin theme={"system"}
fun executeAction(action: CustomComponentAction, params: JSONObject? = null)
```

##### Parameters

| Name     | Description                                                                 | Type                                                               | Requirement                     |
| -------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------- |
| `action` | Action object extracted from the `actions` received in the custom component | `ai.flowx.android.sdk.api.custom.components.CustomComponentAction` | Mandatory                       |
| `params` | Parameters needed to execute the `action`                                   | `JSONObject?`                                                      | Optional. It defaults to `null` |

#### Execute upload action

A specific use case for executing actions from custom components involves uploading files into the FlowX platform.<br /><br />
To run an upload action (e.g., on a click of a button in the custom component) you need to call the `executeUploadAction` method, through the `CustomComponentScope` context:

```kotlin theme={"system"}
fun executeUploadAction(action: CustomComponentAction, fileUri: Uri, mimeType: String? = null, params: JSONObject? = null)
```

##### Parameters

| Name       | Description                                                                 | Type                                                               | Requirement                     |
| ---------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------- |
| `action`   | Action object extracted from the `actions` received in the custom component | `ai.flowx.android.sdk.api.custom.components.CustomComponentAction` | Mandatory                       |
| `fileUri`  | The local URI for the file to be uploaded                                   | `android.net.Uri`                                                  | Mandatory                       |
| `mimeType` | The mimeType of the file to be uploaded                                     | `String?`                                                          | Optional. It defaults to `null` |
| `params`   | Parameters needed to execute the `action`                                   | `JSONObject?`                                                      | Optional. It defaults to `null` |

<Tip>
  It is recommended that a value for the `mimeType` parameter is always provided.<br />
  When missing, the SDK tries computing it internally.
</Tip>

##### Sample

```kotlin expandable theme={"system"}
class FileUploadCustomComponent : CustomComponent {
    var actions: MutableMap<String, CustomComponentAction> = mutableMapOf()

    private val viewModel: FileUploadViewModel by lazy {
        FileUploadViewModel(actions = actions)
    }

    override val composable: @Composable CustomComponentScope.() -> Unit =
        @Composable {
            val rememberedViewModel = remember { viewModel }
            FileUpload(viewModel = rememberedViewModel)
        }

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

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

@Composable
private fun CustomComponentScope.FileUpload(viewModel: FileUploadViewModel) {
    viewModel.setFlowxScope(this@FileUpload)
    val context = LocalContext.current
    val openDocumentLauncher =
        rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
            uri?.let { viewModel.executeUploadAction(fileUri = it, mimeType = it.mimeType(context)) }
            Flowx.getInstance().configureUpdateState(enabled = true) // IMPORTANT: restore the SDK internal update mechanism after selecting a file to be uploaded
        }

    Column(
        modifier = Modifier.fillMaxWidth(),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Button(
            onClick = {
                Flowx.getInstance().configureUpdateState(enabled = false) // IMPORTANT: disable SDK internal update mechanism while selecting a file to be uploaded
                openDocumentLauncher.launch(listOf("application/pdf").toTypedArray()) // select PDF files only
            }
        ) {
            Text(text = "Upload File")
        }
    }
}

private class FileUploadViewModel(
    private var actions: Map<String, CustomComponentAction> = emptyMap(),
) : ViewModel() {
    private lateinit var flowxScope: CustomComponentScope

    fun executeUploadAction(fileUri: Uri, mimeType: String?) {
        actions["uploadFile"]?.let { action ->
            if (this@FileUploadViewModel::flowxScope.isInitialized) {
                flowxScope.executeUploadAction(
                    action = action,
                    params = null,
                    fileUri = fileUri,
                    mimeType = mimeType,
                )
            }
        }
    }

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

private fun Uri.mimeType(context: Context): String? = context.contentResolver.getType(this)
```

<Info>In order for the upload behave correctly, it's highly IMPORTANT to disable the SDK internal update mechanism while selecting a file.</Info>

#### Get a substitution tag value by key

```kotlin theme={"system"}
fun replaceSubstitutionTag(key: String): String
```

All substitution tags will be retrieved by the SDK before starting the process and will be stored in memory.

Whenever the container app needs a substitution tag value for populating the UI of the custom components, it can request the substitution tag, through the `CustomComponentScope` context, using the method above, by providing the `key`.

It returns:

* the key's counterpart, if the `key` is valid and found
* the empty string, if the `key` is valid, but not found
* the unaltered string, if the key has the wrong format (i.e. not starting with `@@`)

#### Get a media item url by key

```kotlin theme={"system"}
fun getMediaResourceUrl(key: String): String?
```

All media items will be retrieved by the SDK before starting the process and will be stored in memory.

Whenever the container app needs a media item url for populating the UI of the custom components, it can request the url, through the `CustomComponentScope` context, using the method above, by providing the `key`.

It returns the `URL` string of the media resource, or `null`, if not found.

#### Obtain enumeration data

```kotlin theme={"system"}
suspend fun getEnumeration(name: String, parentName: String? = null): FxEnumeration?
```

Whenever the container app needs an enumeration data for populating the UI of the custom components, it can request the url, through the `CustomComponentScope` context, using the method above, by providing the `name` (and the `parentName`, if there's a hierarchy defined and the desired enumeration data (`name` is child of `parentName`).

It returns the enumeration data, as an `FxEnumeration` object, or `null`, if not found.

```kotlin 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
    }
}
```

#### Sample

Among multiple existing custom components, there may be one that:

* allows the input of a value representing an `age`
* the value should be validated (e.g. to be at least 35 years old)
* the value will be passed back into the process
* execute an action, through the `CustomComponentScope` context, to skip setting the age

```kotlin expandable theme={"system"}
class FxCustomComponentsProvider : CustomComponentsProvider {
    override fun provideCustomComponent(componentIdentifier: String): CustomComponent? =
        when (componentIdentifier) {
            when (componentIdentifier) {
                "other-custom-component-identifier" -> OtherCustomComponent()
                "age" -> AgeCustomComponent()
                else -> null
            }
        }
}

private class AgeCustomComponent() : CustomComponent {
    private val data: MutableStateFlow<Any?> = MutableStateFlow(null)
    private var actions: MutableMap<String, CustomComponentAction> = mutableMapOf()

    private val viewModel = AgeViewModel(data = data, actions = actions)

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

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

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

    override fun validate(): Boolean = viewModel.isValid()

    override fun saveData(): JSONObject? = viewModel.buildDataToSave()
}

@Composable
private fun CustomComponentScope.Age(viewModel: AgeViewModel) {
    viewModel.setFlowxScope(this@Age) // IMPORTANT: keep a reference to the custom context/scope in order to access the available predefined SDK methods

    val age by viewModel.ageFlow.collectAsState()
    val error by viewModel.errorFlow.collectAsState()
    val isError by remember(error) { mutableStateOf(error.isNotBlank()) }

    Column {
        OutlinedTextField(
            value = age,
            onValueChange = { viewModel.updateAge(it) },
            modifier = Modifier.fillMaxWidth(),
            label = { Text("Age") },
        )

        if (isError) {
            Text(
                modifier = Modifier.fillMaxWidth(),
                text = error,
                style = TextStyle(fontSize = 12.sp),
                color = Color.Red,
                textAlign = TextAlign.Start,
            )
        }

        TextButton(
            onClick = { viewModel.executeSkipAction() }
        ) {
            Text(text = "Skip")
        }
    }
}

private class AgeViewModel(
    private val data: MutableStateFlow<Any?> = MutableStateFlow(null),
    private val actions: Map<String, CustomComponentAction> = emptyMap(),
) : ViewModel() {
    private lateinit var flowxScope: CustomComponentScope // IMPORTANT: keeps the custom context/scope which allows to call the available predefined methods exposed to custom component

    private val _ageFlow = MutableStateFlow("")
    val ageFlow: StateFlow<String> = _ageFlow.asStateFlow()

    private val _error = MutableStateFlow("")
    val errorFlow: StateFlow<String> = _error.asStateFlow()

    fun updateAge(text: String) {
        _ageFlow.value = text
    }

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

    fun isValid(): Boolean = ageFlow.value.toIntOrNull().let {
        when {
            it == null -> false.also { _error.update { "Unrecognized format" } }
            it < 35 -> false.also { _error.update { "You have to be at least 35 years old" } }
            else -> true.also { _error.update { "" } }
        }
    }

    fun buildDataToSave(): JSONObject? = ageFlow.value.takeUnless { it.isBlank() }
        ?.let {
            JSONObject(
                """
            {
                "app": {
                    "age": "$it"
                }
            }
            """.trimIndent()
            )
        }

    fun executeSkipAction() {
        actions["skipAction"]?.let {
            if (this@AgeViewModel::flowxScope.isInitialized) { // IMPORTANT: if the custom context/scope was initialized, the action can be safely executed
                flowxScope.executeAction(
                    action = it,
                    params = JSONObject() // e.g. JSONObject("{\"someParameter\": \"someValue\"}")
                )
            }
        } ?: println("AgeCustomComponent: `skipAction` action was not found")
    }
}

private class OtherCustomComponent() : CustomComponent {
    override val composable: @Composable (CustomComponentScope.() -> Unit)
        get() = @Composable {
            /* add some @Composable implementation */
        }

    override fun populateUi(data: Any?) {
        // extract the necessary data to be used for displaying the custom components
    }

    override fun populateUi(actions: Map<String, CustomComponentAction>) {
        // extract the available actions that may be executed from the custom components
    }

    // Optional override, defaults to `true`.
    // Here one can pass validation logic from viewModel (e.g. by calling `viewModel.isValid()`)
    override fun validate(): Boolean = true

    // Optional override, defaults to `null`.
    // Here one can pass data to save from viewModel (e.g. by calling `viewModel.getDataToSave()`)
    override fun saveData(): JSONObject? = null
}
```

## Custom header view for the [STEPPER](../docs/building-blocks/process/navigation-areas#stepper) component

The container app can opt for providing a custom view to use for all the [Stepper](../docs/building-blocks/process/navigation-areas#stepper) components, as a replacement for the built-in header.<br />
The custom view receives `data` to populate its UI, as described below.

To provide a custom header for the [Stepper](../docs/building-blocks/process/navigation-areas#stepper), an *implementation* of the `CustomStepperHeaderProvider` interface should be passed as a parameter when initializing the SDK:

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

#### Sample

```kotlin theme={"system"}
class FxCustomStepperHeaderProvider : CustomStepperHeaderProvider {
    override fun provideCustomStepperHeader(): CustomStepperHeader? {
        return object : CustomStepperHeader {...}
    }
}
```

### CustomStepperHeader

To provide the custom header view as a [@Composable](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable) function, you have to implement the `CustomStepperHeader` interface:

```kotlin theme={"system"}
interface CustomStepperHeader {
    /**
     * Returns the [Composable]s used to render the stepper header.
     * The received argument contains the stepper header necessary data to render the view.
     */
    val composable: @Composable (data: Data) -> Unit

    interface Data {
        // title for the current step; can be empty or null
        val stepTitle: String?
        // title for the current selected substep; optional;
        // can be empty ("") if not defined or `null` if currently there is no selected substep
        val substepTitle: String?
        // 1-based index of the current step
        val step: Int
        // total number of steps
        val totalSteps: Int
        // 1-based index of the current substep; can be `null` when there are no defined substeps
        val substep: Int?
        // total number of substeps in the current step; can be `null` or `0`
        val totalSubsteps: Int?
    }
}
```

#### Sample

```kotlin theme={"system"}
override fun provideCustomStepperHeader(): CustomStepperHeader? {
    return object : CustomStepperHeader {
        override val composable: @Composable ((CustomStepperHeader.Data) -> Unit)
            get() = @Composable { data ->
                /* add some @Composable implementation which displays the `data` */
            }
    }
}
```

## Custom loaders

The container application can decide to provide custom loaders to be displayed at certain moments based on a given predefined `actionName`.<br />
To provide custom loaders, an *implementation* of the `CustomLoaderProvider` interface should be passed as a parameter when initializing the SDK:

```kotlin theme={"system"}
interface CustomLoaderProvider {
    fun provideCustomLoader(loaderType: CustomLoaderProvider.LoaderType?): CustomLoader?
}
```

where the `CustomLoaderProvider.LoaderType` is defined like this:

```kotlin theme={"system"}
interface LoaderType {
    interface StartProcess : LoaderType
    interface ReloadProcess : LoaderType
    interface Action : LoaderType { val name: String }
    interface Upload : LoaderType { val name: String }
}
```

<Info>
  The available types for the `loaderType` parameter are:

  * `LoaderType.StartProcess` - received for overriding the loader displayed when starting a new process
  * `LoaderType.ReloadProcess` - received for overriding the loader displayed when resuming an existing process
  * `LoaderType.Action` - received for overriding the loader displayed while the action having the `name` name is executed
  * `LoaderType.Upload` - received for overriding the loader displayed while the **upload** action having the `name` name is executed

  The `name` property received for the `LoaderType.Action` and `LoaderType.Upload` types represents the name of an action, as defined at process definition time, received for being able to override the loader displayed while that action is executed.
</Info>

<Info>
  Returning an implementation of a `CustomLoader` replaces the built-in platform loader with the provided one for the specified use cases.<br /><br />
  Returning `null` keeps the built-in platform loader for the specified use cases.
</Info>

### CustomLoader

The implementation for providing a custom loader is based on creating and binding a user defined [@Composable](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable) function, through the `CustomLoader` interface:

```kotlin theme={"system"}
interface CustomLoader {
    val composable: @Composable CustomLoaderScope.() -> Unit
}
```

### CustomLoaderScope

The `composable` property of the [CustomLoader](#customloader) is a [@Composable](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable) function which may be defined and run only within the context of a `CustomLoaderScope` receiver.

```kotlin theme={"system"}
val composable: @Composable CustomLoaderScope.() -> Unit
```

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

This allows calling predefined SDK methods for querying for substitution tags and media resource URLs directly from the custom loader through the scope itself.

#### Sample

```kotlin expandable theme={"system"}
class FxCustomLoaderProvider : CustomLoaderProvider {
    override fun provideCustomLoader(loaderType: CustomLoaderProvider.LoaderType?): CustomLoader? =
        when (loaderType) {
            is CustomLoaderProvider.LoaderType.StartProcess -> MyCustomLoader(backgroundColor = Color.Black.copy(alpha = 0.38f), indicatorColor = Color.Red)
            is CustomLoaderProvider.LoaderType.ReloadProcess -> MyCustomLoader(backgroundColor = Color.Yellow.copy(alpha = 0.38f), indicatorColor = Color.Green)
            is CustomLoaderProvider.LoaderType.Action -> {
                when (loaderType.name) {
                    "action1" -> ActionCustomLoader()
                    "action2" -> ComplexCustomLoader()
                    else -> null
                }
            }
            is CustomLoaderProvider.LoaderType.Upload -> {
                when (loaderType.name) {
                    "upload1" -> ActionCustomLoader()
                    "upload2" -> ComplexCustomLoader()
                    else -> null
                }
            }
            else -> null
        }
}

class MyCustomLoader(val backgroundColor: Color, val indicatorColor: Color) : CustomLoader {
    override val composable: @Composable (CustomLoaderScope.() -> Unit)
        get() = @Composable {
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .background(color = backgroundColor),
                contentAlignment = Alignment.Center,
            ) {
                CircularProgressIndicator(color = indicatorColor)
                BackHandler(enabled = true) {} // block back navigation
            }
        }
}

class ActionCustomLoader() : CustomLoader {...}

class ComplexCustomLoader() : CustomLoader {
    private val animatedViewModel = AnimatedLoaderViewModel(...)
    override val composable: @Composable (CustomLoaderScope.() -> Unit)
        get() = @Composable {
            AnimatedLoader(viewModel = animatedViewModel)
        }
}

@Composable
private fun CustomLoaderScope.AnimatedLoader(
    viewModel: AnimatedLoaderViewModel
) {
    viewModel.setFlowxScope(this@AnimatedLoader) // IMPORTANT: keep a reference to the custom context/scope in order to access the available predefined SDK methods

    val state by viewModel.loaderState.collectAsStateWithLifecycle()

    Box {
        // use state to build what is to be displayed
    }
}

private class AnimatedLoaderViewModel(...) : ViewModel() {
    private lateinit var flowxScope: CustomLoaderScope // IMPORTANT: keeps the custom context/scope which allows to call the available predefined methods exposed to custom component

    val loaderState: StateFlow<AnimatedLoaderState?> =
        someData
            .process {
                if (this@AnimatedLoaderViewModel::flowxScope.isInitialized) { // IMPORTANT: if the custom context/scope was initialized, the substitution tag can be safely queried
                    flowxScope.replaceSubstitutionTag(it)
                } else {
                    it
                }
            }
            .stateIn(...)

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

```

## Collecting analytics events

To be able to collect analytics events from the SDK, an implementation for the `AnalyticsCollector` functional interface may be provided when initializing the SDK:

```kotlin theme={"system"}
fun interface AnalyticsCollector {
    fun onEvent(event: Event)
}
```

There are two types of events, `Screen` and `Action`, both of them containing some `Data` and an optional `CustomPayload`, as defined at process definition time.

The `Event` is structured like this:

```kotlin theme={"system"}
sealed interface Event {
    interface Screen : Event {
        val data: Screen.Data

        interface Data {
            val value: String
            val customPayload: CustomPayload?
        }
    }

    interface Action : Event {
        val data: Action.Data

        interface Data {
            val value: String
            val screen: String?
            val component: String?
            val label: String?
            val customPayload: CustomPayload?
        }
    }

    sealed interface CustomPayload {
        interface Object : CustomPayload {
            val data: JSONObject
        }

        interface Array : CustomPayload {
            val data: JSONArray
        }
    }
}
```

#### Sample

The implementation can be passed as a lambda, like:

```kotlin theme={"system"}
analyticsCollector = { event ->
    // do whatever is needed (e.g. log the event)
    when (it) {
        is Event.Screen -> {
            val customPayload: String? =
                when (val payload = it.data.customPayload) {
                    is Event.CustomPayload.Object -> payload.data.toString()
                    is Event.CustomPayload.Array -> payload.data.toString()
                    else -> null
                }
            Log.i("Analytics", "Event.Screen(value = ${it.data.value}, customPayload = $customPayload)")
        }

        is Event.Action -> {
            val customPayload: String? =
                when (val payload = it.data.customPayload) {
                    is Event.CustomPayload.Object -> payload.data.toString()
                    is Event.CustomPayload.Array -> payload.data.toString()
                    else -> null
                }
            Log.i("Analytics", "Event.Action(value = ${it.data.value}, screen = ${it.data.screen}, component = ${it.data.component}, label = ${it.data.label}, customPayload = $customPayload)")
        }
    }
}
```

<Info>
  The `value` property represents the identifier set in the process definition.

  For action type events there are some additional properties provided:

  * `component` - The type of component triggering the action
  * `label` - The label of the component, if available. (E.g. title of a button or label of a form element)
  * `screen` - The identifier of the screen containing the component, if set

  The `customPayload` is defined at process definition time, and then processed inside the platform before sending it to being collected.
</Info>

## Handling "Start of a new process"

When an action of type `START_PROJECT` is executed, the `onNewProcessStarted` lambda provided in the `Flowx.getInstance().init(...)` function is invoked.

This callback provides the UUID of the newly started process, which can be used to resume the process by calling the `Flowx.getInstance().continueProcess(...)` method.

It is the responsibility of the container application's developer to implement the necessary logic for displaying the appropriate UI for the newly started process.

#### Sample

One way to handle this is to send a broadcast message to notify the Activity currently displaying the running process.
The Activity should handle the broadcast to reload and display the newly started process identified by `processInstanceUuid` (received in the broadcast intent).

```kotlin theme={"system"}
Flowx.getInstance().init(
    ...
    onNewProcessStarted = { processInstanceUuid ->
        applicationContext.sendBroadcast(
            Intent("some.intent.filter.indentifier").apply {
                putExtra("processInstanceUuid", processInstanceUuid)
                setPackage("your.application.package")
            }
        )
    }
    ...
)

class ProcessActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...

        setContent {
            val yourBroadcastReceiver = remember {
                YourBroadcastReceiver(handler = { processInstanceUuid -> /* do your own logic to refresh `ProcessContent()` */ })
            }

            val context = LocalContext.current
            LifecycleStartEffect(true) {
                ContextCompat.registerReceiver(context.applicationContext, yourBroadcastReceiver, IntentFilter("some.intent.filter.indentifier"), ContextCompat.RECEIVER_NOT_EXPORTED)
                onStopOrDispose {
                    runCatching {
                        context.applicationContext.unregisterReceiver(yourBroadcastReceiver)
                    }
                }
            }

            ProcessContent()
        }
    }

    @Composable
    private fun ProcessContent(...) {...}
}

class YourBroadcastReceiver(private val handler: (String) -> Unit) : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        intent?.extras?.getString("processInstanceUuid")?.let { processUuid -> handler.invoke(processUuid) }
    }
}
```

## ProGuard / R8

The SDK 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.

## Known issues

* shadows are rendered only on **Android >= 28** having [hardware acceleration](https://developer.android.com/topic/performance/hardware-accel) **enabled**
