# Understanding State Machines
Explains how state machines work in the dotLottie Android Player. Covers the core model, workflow, API, event posting, inputs, and debugging.

# Understanding State Machines

This page explains how state machines work in the Android player — what they are, how they integrate with animation playback, and when to use them. It also documents the `DotLottieController` API for loading, starting, and interacting with state machines.

> **Note:** State machine support is an actively developing feature. APIs and capabilities might change. If your state machine isn't working as expected please [create an issue on Github↗](https://github.com/LottieFiles/dotlottie-android/issues).

## Core Workflow

1. **Define:** Create your state machine logic (states, transitions, events, context) within your `.lottie` file.
2. **Load Animation:** Set up the `DotLottieAnimation` composable or `DotLottieAnimation` view, providing a `DotLottieController` instance.
3. **Load State Machine:** Once the animation is loaded, load the desired state machine by its ID using the controller.
4. **Start State Machine:** Activate the state machine using the controller.
5. **Post Events:** Send events from your application code (e.g., button clicks, view interactions) to trigger transitions using the controller.
6. **Listen (Optional):** Add a `StateMachineEventListener` to the controller to react to state changes or transitions.
7. **Stop (Optional):** Stop the state machine using the controller when interactivity is no longer needed.

## Loading on Initialization

You can load a state machine automatically when the animation loads by passing the `stateMachineId` parameter to the composable:

```kotlin
DotLottieAnimation(
    source = DotLottieSource.Asset("interactive.lottie"),
    autoplay = true,
    stateMachineId = "main", // Loads and starts automatically
    controller = controller,
)
```

## Android Player API for State Machines (Using `DotLottieController`)

You control state machines primarily through the `DotLottieController` instance associated with your `DotLottieAnimation` composable:

| Method                                                                           | Return Type | Description                                                                                         |
| :------------------------------------------------------------------------------- | :---------- | :-------------------------------------------------------------------------------------------------- |
| `stateMachineLoad(stateMachineId: String)`                                       | `Boolean`   | Loads a specific state machine defined in the `.lottie` file by its ID. Returns `true` on success.  |
| `stateMachineLoadData(data: String)`                                             | `Boolean`   | Loads a state machine from a raw JSON string. Returns `true` on success.                            |
| `stateMachineStart(openUrl: OpenUrlPolicy, onOpenUrl: ((url: String) -> Unit)?)` | `Boolean`   | Starts the currently loaded state machine with URL policy configuration. Returns `true` on success. |
| `stateMachineStop()`                                                             | `Boolean`   | Stops the currently active state machine. Returns `true` on success.                                |
| `stateMachinePostEvent(event: Event)`                                            | `Boolean`   | Sends a typed `Event` to the active state machine. Returns `true` if posted.                        |
| `stateMachinePostEvent(event: Event, force: Boolean)`                            | `Boolean`   | Sends an event with an option to force the post regardless of current state.                        |
| `stateMachineFire(event: String)`                                                | `Boolean`   | Fires a named string event to the active state machine.                                             |
| `stateMachineSetBooleanInput(key: String, value: Boolean)`                       | `Boolean`   | Sets a boolean input value on the state machine.                                                    |
| `stateMachineSetNumericInput(key: String, value: Float)`                         | `Boolean`   | Sets a numeric input value on the state machine.                                                    |
| `stateMachineSetStringInput(key: String, value: String)`                         | `Boolean`   | Sets a string input value on the state machine.                                                     |
| `stateMachineGetBooleanInput(key: String)`                                       | `Boolean?`  | Gets the current value of a boolean input.                                                          |
| `stateMachineGetNumericInput(key: String)`                                       | `Float?`    | Gets the current value of a numeric input.                                                          |
| `stateMachineGetStringInput(key: String)`                                        | `String?`   | Gets the current value of a string input.                                                           |
| `stateMachineCurrentState()`                                                     | `String?`   | Returns the name of the currently active state.                                                     |
| `stateMachineAddEventListener(listener: StateMachineEventListener)`              | `Unit`      | Adds a listener to receive state machine events.                                                    |
| `stateMachineRemoveEventListener(listener: StateMachineEventListener)`           | `Unit`      | Removes a previously added state machine event listener.                                            |

**Important:** Ensure the animation is loaded before calling `stateMachineLoad`. You can use the `controller.isLoaded` property or listen for the `onLoad` event via a `DotLottieEventListener`.

### OpenUrlPolicy

The `OpenUrlPolicy` data class controls how URLs are handled when a state machine triggers a URL open action:

| Property                 | Type           | Description                                               |
| :----------------------- | :------------- | :-------------------------------------------------------- |
| `requireUserInteraction` | `Boolean`      | Whether user interaction is required before opening URLs. |
| `whitelist`              | `List<String>` | List of allowed URL patterns.                             |

```kotlin
controller.stateMachineStart(
    openUrl = OpenUrlPolicy(
        requireUserInteraction = true,
        whitelist = listOf("https://lottiefiles.com/*")
    ),
    onOpenUrl = { url -> /* handle URL */ }
)
```

### Example: Exploding Pigeon (Jetpack Compose)

This example demonstrates loading, starting, posting events, and listening to a state machine.

```kotlin
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.lottiefiles.dotlottie.core.compose.ui.DotLottieAnimation
import com.lottiefiles.dotlottie.core.util.DotLottieSource
import com.lottiefiles.dotlottie.core.widget.DotLottieController
import com.lottiefiles.dotlottie.core.model.StateMachineEventListener
import com.lottiefiles.dotlottie.core.model.Event
import android.util.Log

@Composable
fun StateMachineExample() {
    val controller = remember { DotLottieController() }

    val stateListener = remember {
        object : StateMachineEventListener {
            override fun onStart() {
                Log.i("DotLottie", "State machine started")
            }
            override fun onStop() {
                Log.i("DotLottie", "State machine stopped")
            }
            override fun onStateEntered(enteringState: String) {
                Log.i("DotLottie", "State Entered: $enteringState")
            }
            override fun onStateExit(leavingState: String) {
                Log.i("DotLottie", "State Exited: $leavingState")
            }
            override fun onTransition(previousState: String, newState: String) {
                Log.i("DotLottie", "Transition: $previousState -> $newState")
            }
            override fun onCustomEvent(message: String) {
                Log.i("DotLottie", "Custom event: $message")
            }
            override fun onError(message: String) {
                Log.e("DotLottie", "State machine error: $message")
            }
            override fun onInputFired(inputName: String) {
                Log.i("DotLottie", "Input fired: $inputName")
            }
            override fun onBooleanInputValueChange(inputName: String, oldValue: Boolean, newValue: Boolean) {
                Log.i("DotLottie", "Boolean input $inputName: $oldValue -> $newValue")
            }
            override fun onNumericInputValueChange(inputName: String, oldValue: Float, newValue: Float) {
                Log.i("DotLottie", "Numeric input $inputName: $oldValue -> $newValue")
            }
            override fun onStringInputValueChange(inputName: String, oldValue: String, newValue: String) {
                Log.i("DotLottie", "String input $inputName: $oldValue -> $newValue")
            }
        }
    }

    LaunchedEffect(controller) {
        controller.stateMachineAddEventListener(stateListener)
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        DotLottieAnimation(
            source = DotLottieSource.Asset("exploding_pigeon.lottie"),
            controller = controller,
        )

        Spacer(modifier = Modifier.height(16.dp))

        Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
            Button(onClick = {
                val result = controller.stateMachineLoad("pigeon_fsm")
                if (result) {
                    Log.i("DotLottie", "State machine loaded.")
                    if (controller.stateMachineStart(
                            openUrl = OpenUrlPolicy(requireUserInteraction = true, whitelist = emptyList()),
                            onOpenUrl = null
                        )
                    ) {
                       Log.i("DotLottie", "State machine started.")
                    }
                }
            }) {
                Text(text = "Load & Start")
            }

            Button(onClick = {
                controller.stateMachinePostEvent(Event.Click(100f, 100f))
            }) {
                Text(text = "Click")
            }

            Button(onClick = {
                controller.stateMachineStop()
            }) {
                Text(text = "Stop")
            }
        }

        // Working with state machine inputs
        Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
            Button(onClick = {
                controller.stateMachineSetBooleanInput("isActive", true)
            }) {
                Text(text = "Set Active")
            }

            Button(onClick = {
                val currentState = controller.stateMachineCurrentState()
                Log.i("DotLottie", "Current state: $currentState")
            }) {
                Text(text = "Log State")
            }
        }
    }
}
```

### Exploding Pigeon State Machine Definition (in `.lottie`)

```json
{
  "descriptor": { "id": "pigeon_fsm", "initial": 0 },
  "states": [
    {
      "name": "pigeon",
      "type": "PlaybackState",
      "marker": "bird",
      "loop": true,
      "autoplay": true,
      "speed": 1
    },
    {
      "name": "explosion",
      "type": "PlaybackState",
      "marker": "explosion",
      "loop": false,
      "autoplay": true,
      "speed": 0.5
    },
    {
      "name": "feathers",
      "type": "PlaybackState",
      "marker": "feathers",
      "loop": false,
      "autoplay": true,
      "speed": 1
    }
  ],
  "transitions": [
    {
      "type": "Transition",
      "from_state": 0,
      "to_state": 1,
      "string_event": { "value": "explosion" }
    },
    {
      "type": "Transition",
      "from_state": 1,
      "to_state": 2,
      "on_complete_event": {}
    },
    {
      "type": "Transition",
      "from_state": 2,
      "to_state": 0,
      "on_complete_event": {}
    }
  ]
}
```

## Understanding State Machine Components (Defined in `.lottie`)

Understanding the structure defined within the `.lottie` file helps in using the player API effectively:

- **Descriptor:** Contains the state machine's unique `id` (used in `stateMachineLoad`) and the index of the `initial` state.
- **States:** Defines possible animation states. The main type `PlaybackState` controls animation aspects like `marker`, `segment`, `loop`, `autoplay`, `speed`, and `mode`.
- **Transitions:** Rules for moving between states (`from_state`, `to_state`) based on a trigger `event`.
- **Events:** Triggers for transitions, posted via the `stateMachinePostEvent` method using the `Event` sealed class.
- **Guards (Optional):** Conditions within transitions that check context variables to allow or prevent the transition.
- **Context Variables (Optional):** Stored data (`Numeric`, `String`, `Boolean`) used by guards.
- **Listeners (Optional):** Defined actions tied to events within the `.lottie` file (player execution support evolving).

Refer to the [dotLottie format specification↗](https://dotlottie.io) or [dotlottie-js documentation↗](https://github.com/LottieFiles/dotlottie-js) for complete definition details.

## Event Type for `stateMachinePostEvent`

Pointer and click interactions defined in the state machine's `interactions` array (e.g., `Click`, `PointerDown`, `PointerEnter`) are handled **automatically** by the Android player based on user touches on the animation view. You do not need to post these events manually for normal interactions.

The `stateMachinePostEvent` method is available for cases where you need to synthesize pointer events programmatically — for example, triggering an interaction from custom gesture logic outside the animation view.

The Android player uses a specific `Event` sealed class for posting events:

```kotlin
sealed class Event(val type: Int) {
    data class PointerDown(val x: Float, val y: Float) : Event(0)
    data class PointerUp(val x: Float, val y: Float) : Event(1)
    data class PointerMove(val x: Float, val y: Float) : Event(2)
    data class PointerEnter(val x: Float, val y: Float) : Event(3)
    data class PointerExit(val x: Float, val y: Float) : Event(4)
    data class Click(val x: Float, val y: Float) : Event(5)
    object OnComplete : Event(6)
    object OnLoopComplete : Event(7)
}
```

Example:

```kotlin
controller.stateMachinePostEvent(Event.Click(100f, 200f))
controller.stateMachinePostEvent(Event.PointerDown(10f, 20f))
controller.stateMachinePostEvent(Event.PointerUp(10f, 20f))
controller.stateMachinePostEvent(Event.PointerMove(50f, 60f))
controller.stateMachinePostEvent(Event.OnComplete)
controller.stateMachinePostEvent(Event.OnLoopComplete)
```

## StateMachineEventListener Interface

The `StateMachineEventListener` interface defines all callbacks for state machine events:

| Method                      | Signature                                                                                | Description                                       |
| :-------------------------- | :--------------------------------------------------------------------------------------- | :------------------------------------------------ |
| `onStart`                   | `fun onStart()`                                                                          | Fired when the state machine starts.              |
| `onStop`                    | `fun onStop()`                                                                           | Fired when the state machine stops.               |
| `onStateEntered`            | `fun onStateEntered(enteringState: String)`                                              | Fired when entering a state.                      |
| `onStateExit`               | `fun onStateExit(leavingState: String)`                                                  | Fired when exiting a state.                       |
| `onTransition`              | `fun onTransition(previousState: String, newState: String)`                              | Fired on a state transition.                      |
| `onCustomEvent`             | `fun onCustomEvent(message: String)`                                                     | Fired when a custom event is emitted.             |
| `onError`                   | `fun onError(message: String)`                                                           | Fired when the state machine encounters an error. |
| `onInputFired`              | `fun onInputFired(inputName: String)`                                                    | Fired when an input fires.                        |
| `onBooleanInputValueChange` | `fun onBooleanInputValueChange(inputName: String, oldValue: Boolean, newValue: Boolean)` | Fired when a boolean input changes.               |
| `onNumericInputValueChange` | `fun onNumericInputValueChange(inputName: String, oldValue: Float, newValue: Float)`     | Fired when a numeric input changes.               |
| `onStringInputValueChange`  | `fun onStringInputValueChange(inputName: String, oldValue: String, newValue: String)`    | Fired when a string input changes.                |

## Transitions and Guards

Transitions and guards function conceptually the same as on other platforms. They are defined in the `.lottie` file and automatically evaluated by the player when an event is posted.

## Context Variables

Context variables (`Numeric`, `String`, `Boolean`) are defined in the `.lottie` file and used primarily by guards. **Currently, the Android player does not expose public methods to manually modify context variables at runtime.** Context modification is expected to be handled via `ListenerActions` (a planned feature) defined within the state machine itself.

## Debugging and Troubleshooting

1. **State Machine Not Loading/Starting:**
   - **Check ID:** Verify the `stateMachineId` passed to `stateMachineLoad` matches the ID in the `.lottie` manifest.
   - **Check Return Values:** Log the boolean results from `stateMachineLoad` and `stateMachineStart`.
   - **Animation Loaded?** Ensure the animation loaded correctly (check `controller.isLoaded` or use `DotLottieEventListener`).
2. **Events Not Triggering Transitions:**
   - **Check Event Type:** Ensure you are using the correct `Event` sealed class case (e.g., `Event.Click(x, y)`, `Event.PointerDown(x, y)`).
   - **Verify Guards:** Check guard conditions defined in the `.lottie` file.
   - **State Machine Started?** Confirm `stateMachineStart()` returned `true`.
3. **Animation Playback Issues in States:**
   - **Check Marker/Segment:** Ensure `marker` names or `segment` frame numbers defined in `PlaybackState`s exist in the animation JSON.
4. **Debugging Tip: Use `StateMachineEventListener`:** Implement and add a `StateMachineEventListener` (as shown in the example) to log state entries, exits, transitions, and input changes in Logcat.

## Known Limitations & Future Enhancements

- **Context Modification:** No public API to manually change context variables.

## Best Practices

- **Design:** Keep state machines simple and document them within the `.lottie` file.
- **Loading:** Check return values from `stateMachineLoad` and `stateMachineStart`.
- **Events:** Use the `Event` sealed class correctly with the current API (`PointerDown`, `PointerUp`, `Click`, etc.).
- **Lifecycle:** Consider adding/removing listeners (`StateMachineEventListener`) in appropriate lifecycle scopes (e.g., `LaunchedEffect`, `rememberUpdatedState`) to prevent leaks if the controller or listener instances change.
- **Cleanup:** Call `stateMachineStop()` when the interactive feature is no longer needed.
- **Debugging:** Utilize `StateMachineEventListener` and Logcat.

## Related Topics

- [Android API Reference](/en/runtimes/distributions/android/v0.x/api-reference)
- [Android Usage Guide](/en/runtimes/distributions/android/v0.x/usage-guide)
