# Understanding State Machines
Explains how state machines work in the dotLottie Flutter Player. Covers the core model, workflow, setting inputs, firing events, reading state, and event callbacks.

# Understanding State Machines

This page explains how state machines work in the Flutter player — what they are, how they integrate with animation playback, and when to use them. It also documents the `DotLottieViewController` 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/nicholasgasior/dotlottie-flutter/issues).

## Core Workflow

1. **Define:** Create your state machine logic (states, transitions, events, context) within your `.lottie` file.
2. **Load Animation:** Set up the `DotLottieView` widget, providing `source` and `sourceType`.
3. **Load State Machine:** Specify the `stateMachineId` prop or load programmatically via the controller after the animation loads.
4. **Start State Machine:** The state machine starts automatically when using `stateMachineId`, or call `stateMachineStart()` after programmatic loading.
5. **Interact:** Send inputs and fire events from your application code (e.g., button presses, gestures) using the controller.
6. **Listen (Optional):** Use event callbacks on `DotLottieView` to react to state changes, transitions, and input changes.
7. **Stop (Optional):** Stop the state machine using the controller when interactivity is no longer needed.

## Basic Setup

The simplest way to use a state machine is to set the `stateMachineId` prop and grab the controller via `onViewCreated`:

```dart
class StateMachineExample extends StatefulWidget {
  @override
  State<StateMachineExample> createState() => _StateMachineExampleState();
}

class _StateMachineExampleState extends State<StateMachineExample> {
  DotLottieViewController? _controller;
  String _currentState = 'idle';

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        SizedBox(
          width: 300,
          height: 300,
          child: DotLottieView(
            source: 'https://lottie.host/your-animation.lottie',
            sourceType: 'url',
            stateMachineId: 'myStateMachine',
            onViewCreated: (controller) {
              _controller = controller;
            },
            stateMachineOnStateEntered: (state) {
              setState(() {
                _currentState = state;
              });
            },
          ),
        ),
        const SizedBox(height: 20),
        Text('Current State: $_currentState'),
      ],
    );
  }
}
```

## Setting Inputs

State machines can use boolean, numeric, and string inputs to control transitions.

### Boolean Inputs

```dart
_controller?.stateMachineSetBooleanInput('isActive', true);
_controller?.stateMachineSetBooleanInput('isActive', false);
```

### Numeric Inputs

```dart
// Control speed or intensity with a slider
Slider(
  value: _speed,
  min: 0.0,
  max: 10.0,
  onChanged: (value) {
    setState(() => _speed = value);
    _controller?.stateMachineSetNumericInput('speed', value);
  },
)
```

### String Inputs

```dart
// Set a mode or category
_controller?.stateMachineSetStringInput('theme', 'dark');
```

## Interactions and Events

Pointer and click interactions defined in the state machine's `interactions` array (e.g., `Click`, `PointerDown`, `PointerEnter`) are handled **automatically** by the Flutter player based on user touches on the `DotLottieView` widget. You do not need to wire up gesture detectors for these.

Use `stateMachineFire` to trigger `Event`-type inputs explicitly from your application code — for example, responding to a button press or other app-level gesture outside the animation view:

### Firing Events

Events cause immediate transitions in the state machine:

```dart
// Fire a named event
_controller?.stateMachineFire('onTap');

// Use with gesture detectors for different interactions
GestureDetector(
  onTap: () => _controller?.stateMachineFire('tap'),
  onDoubleTap: () => _controller?.stateMachineFire('doubleTap'),
  onLongPress: () => _controller?.stateMachineFire('longPress'),
  child: YourAnimationWidget(),
)
```

## Reading State Machine State

### Get Current State

```dart
final currentState = await _controller?.stateMachineCurrentState();
print('Current state: $currentState');
```

### Get Input Values

```dart
final isActive = await _controller?.stateMachineGetBooleanInput('isActive');
final speed = await _controller?.stateMachineGetNumericInput('speed');
final theme = await _controller?.stateMachineGetStringInput('theme');
```

### Get All Inputs

```dart
final inputs = await _controller?.stateMachineGetInputs();
inputs?.forEach((name, type) {
  print('Input: $name (type: $type)');
});
```

## State Machine Event Callbacks

Listen to state machine events by adding callbacks to `DotLottieView`:

```dart
DotLottieView(
  source: 'assets/animation.lottie',
  sourceType: 'asset',
  stateMachineId: 'myStateMachine',

  // Lifecycle
  stateMachineOnStart: () {
    print('State machine started');
  },
  stateMachineOnStop: () {
    print('State machine stopped');
  },

  // State changes
  stateMachineOnStateEntered: (state) {
    print('Entered state: $state');
  },
  stateMachineOnStateExit: (state) {
    print('Exited state: $state');
  },
  stateMachineOnTransition: (previous, next) {
    print('Transition: $previous → $next');
  },

  // Input changes
  stateMachineOnBooleanInputValueChange: (name, oldValue, newValue) {
    print('$name: $oldValue → $newValue');
  },
  stateMachineOnNumericInputValueChange: (name, oldValue, newValue) {
    print('$name: $oldValue → $newValue');
  },

  // Events and errors
  stateMachineOnInputFired: (name) {
    print('Event fired: $name');
  },
  stateMachineOnError: (message) {
    print('Error: $message');
  },
  stateMachineOnCustomEvent: (message) {
    print('Custom event: $message');
  },
)
```

## Complete Interactive Example

Here's a complete example of an interactive button with multiple states:

```dart
class InteractiveButton extends StatefulWidget {
  @override
  State<InteractiveButton> createState() => _InteractiveButtonState();
}

class _InteractiveButtonState extends State<InteractiveButton> {
  DotLottieViewController? _controller;
  String _currentState = 'idle';
  bool _isEnabled = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Interactive Button')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            GestureDetector(
              onTapDown: (_) {
                if (_isEnabled) {
                  _controller?.stateMachineFire('pressDown');
                }
              },
              onTapUp: (_) {
                if (_isEnabled) {
                  _controller?.stateMachineFire('pressUp');
                }
              },
              onTapCancel: () {
                if (_isEnabled) {
                  _controller?.stateMachineFire('pressCancel');
                }
              },
              child: SizedBox(
                width: 200,
                height: 200,
                child: DotLottieView(
                  source: 'assets/button-animation.lottie',
                  sourceType: 'asset',
                  stateMachineId: 'buttonStateMachine',
                  onViewCreated: (controller) {
                    _controller = controller;
                  },
                  stateMachineOnStateEntered: (state) {
                    setState(() {
                      _currentState = state;
                    });
                    if (state == 'pressed') {
                      HapticFeedback.lightImpact();
                    }
                  },
                ),
              ),
            ),
            const SizedBox(height: 40),
            Text(
              'State: $_currentState',
              style: Theme.of(context).textTheme.titleLarge,
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {
                    setState(() => _isEnabled = !_isEnabled);
                    _controller?.stateMachineSetBooleanInput(
                      'isEnabled',
                      _isEnabled,
                    );
                  },
                  child: Text(_isEnabled ? 'Disable' : 'Enable'),
                ),
                const SizedBox(width: 10),
                ElevatedButton(
                  onPressed: () {
                    _controller?.stateMachineFire('reset');
                  },
                  child: const Text('Reset'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }
}
```

## Loading State Machines Programmatically

You can load state machines programmatically instead of using the `stateMachineId` prop:

```dart
// Load by ID after animation is ready
await _controller?.stateMachineLoad('myStateMachine');
await _controller?.stateMachineStart();

// Load from JSON data
final stateMachineJson = '{"states":[...],"transitions":[...]}';
await _controller?.stateMachineLoadData(stateMachineJson);
await _controller?.stateMachineStart();

// Stop when done
await _controller?.stateMachineStop();
```

## 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` or the `stateMachineId` prop) 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, fired via the `stateMachineFire` method or by setting inputs.
- **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.

## Debugging and Troubleshooting

1. **State Machine Not Loading/Starting:**
   - **Check ID:** Verify the `stateMachineId` matches the ID in the `.lottie` manifest.
   - **Animation Loaded?** Ensure the animation loaded correctly before programmatic loading. Use `onViewCreated` to confirm the controller is available.
2. **Events Not Triggering Transitions:**
   - **Check Event Name:** Ensure you're firing the correct event name that matches the state machine definition.
   - **Verify Guards:** Check guard conditions defined in the `.lottie` file.
   - **State Machine Started?** Confirm the state machine is running.
3. **Animation Playback Issues in States:**
   - **Check Marker/Segment:** Ensure `marker` names or `segment` frame numbers defined in states exist in the animation.
4. **Debugging Tip:** Use `stateMachineOnStateEntered`, `stateMachineOnStateExit`, and `stateMachineOnTransition` callbacks to log state changes.

## Related Topics

- [Flutter Examples](/en/runtimes/distributions/flutter/v0.x/examples)
- [Flutter API Reference](/en/runtimes/distributions/flutter/v0.x/api-reference)
