Command Palette

Search for a command to run...

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

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:

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

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

Numeric Inputs

// 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

// 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:

// 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

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

Get Input Values

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

Get All Inputs

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:

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:

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:

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

Last updated: April 10, 2026 at 9:12 AMEdit this page