Command Palette

Search for a command to run...

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

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:

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:

MethodReturn TypeDescription
stateMachineLoad(stateMachineId: String)BooleanLoads a specific state machine defined in the .lottie file by its ID. Returns true on success.
stateMachineLoadData(data: String)BooleanLoads a state machine from a raw JSON string. Returns true on success.
stateMachineStart(openUrl: OpenUrlPolicy, onOpenUrl: ((url: String) -> Unit)?)BooleanStarts the currently loaded state machine with URL policy configuration. Returns true on success.
stateMachineStop()BooleanStops the currently active state machine. Returns true on success.
stateMachinePostEvent(event: Event)BooleanSends a typed Event to the active state machine. Returns true if posted.
stateMachinePostEvent(event: Event, force: Boolean)BooleanSends an event with an option to force the post regardless of current state.
stateMachineFire(event: String)BooleanFires a named string event to the active state machine.
stateMachineSetBooleanInput(key: String, value: Boolean)BooleanSets a boolean input value on the state machine.
stateMachineSetNumericInput(key: String, value: Float)BooleanSets a numeric input value on the state machine.
stateMachineSetStringInput(key: String, value: String)BooleanSets 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)UnitAdds a listener to receive state machine events.
stateMachineRemoveEventListener(listener: StateMachineEventListener)UnitRemoves 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:

PropertyTypeDescription
requireUserInteractionBooleanWhether user interaction is required before opening URLs.
whitelistList<String>List of allowed URL patterns.
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.

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)

{
  "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↗ or dotlottie-js documentation↗ 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:

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:

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:

MethodSignatureDescription
onStartfun onStart()Fired when the state machine starts.
onStopfun onStop()Fired when the state machine stops.
onStateEnteredfun onStateEntered(enteringState: String)Fired when entering a state.
onStateExitfun onStateExit(leavingState: String)Fired when exiting a state.
onTransitionfun onTransition(previousState: String, newState: String)Fired on a state transition.
onCustomEventfun onCustomEvent(message: String)Fired when a custom event is emitted.
onErrorfun onError(message: String)Fired when the state machine encounters an error.
onInputFiredfun onInputFired(inputName: String)Fired when an input fires.
onBooleanInputValueChangefun onBooleanInputValueChange(inputName: String, oldValue: Boolean, newValue: Boolean)Fired when a boolean input changes.
onNumericInputValueChangefun onNumericInputValueChange(inputName: String, oldValue: Float, newValue: Float)Fired when a numeric input changes.
onStringInputValueChangefun 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 PlaybackStates 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.

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