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
Define: Create your state machine logic (states, transitions, events, context) within your
.lottiefile.Load Animation: Set up the
DotLottieViewwidget, providingsourceandsourceType.Load State Machine: Specify the
stateMachineIdprop or load programmatically via the controller after the animation loads.Start State Machine: The state machine starts automatically when using
stateMachineId, or callstateMachineStart()after programmatic loading.Interact: Send inputs and fire events from your application code (e.g., button presses, gestures) using the controller.
Listen (Optional): Use event callbacks on
DotLottieViewto react to state changes, transitions, and input changes.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 instateMachineLoador thestateMachineIdprop) and the index of theinitialstate.States: Defines possible animation states. The main type
PlaybackStatecontrols animation aspects likemarker,segment,loop,autoplay,speed, andmode.Transitions: Rules for moving between states (
from_state,to_state) based on a trigger event.Events: Triggers for transitions, fired via the
stateMachineFiremethod 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
State Machine Not Loading/Starting:
Check ID: Verify the
stateMachineIdmatches the ID in the.lottiemanifest.Animation Loaded? Ensure the animation loaded correctly before programmatic loading. Use
onViewCreatedto confirm the controller is available.
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
.lottiefile.State Machine Started? Confirm the state machine is running.
Animation Playback Issues in States:
Check Marker/Segment: Ensure
markernames orsegmentframe numbers defined in states exist in the animation.
Debugging Tip: Use
stateMachineOnStateEntered,stateMachineOnStateExit, andstateMachineOnTransitioncallbacks to log state changes.