# dotLottie Android Player Usage Guide
Comprehensive guide for the dotLottie Android player. Covers loading sources, styling, event handling, playback control, slots, layout, multi-animation, and theming.

# dotLottie Android Player Usage Guide

This guide shows you how to load animations, control playback, handle events, customize slots, configure layout, and manage themes in your Android app using the `DotLottieAnimation` composable.

## Basic Usage

Common use cases for the `DotLottieAnimation` composable function include loading and displaying dotLottie animations within Jetpack Compose UIs. The following example demonstrates how to integrate a dotLottie animation into a Composable function using the `DotLottieAnimation` composable function.

### Loading from different sources

#### From a URL

`DotLottieSource.Url` is used to load an animation from a URL. It can load `.json` or `.lottie` files from a URL.

```kotlin
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.lottiefiles.dotlottie.core.compose.ui.DotLottieAnimation
import com.lottiefiles.dotlottie.core.util.DotLottieSource

@Preview()
@Composable
fun DotLottiePreview() {
    DotLottieAnimation(
        source = DotLottieSource.Url("https://lottie.host/5525262b-4e57-4f0a-8103-cfdaa7c8969e/VCYIkooYX8.json"),
        autoplay = true,
        loop = true,
    )
}
```

#### From an asset

`DotLottieSource.Asset` is used to load an animation from the assets folder in your Android project. It can load `.json` or `.lottie` files from the assets folder.

```kotlin
// imports

@Preview()
@Composable
fun DotLottiePreview() {
    DotLottieAnimation(
        source = DotLottieSource.Asset("file.json"),
    )
}
```

#### From a raw resource

`DotLottieSource.Res` is used to load an animation from the `res/raw` directory. Pass the resource ID directly.

```kotlin
// imports

@Preview()
@Composable
fun DotLottiePreview() {
    DotLottieAnimation(
        source = DotLottieSource.Res(R.raw.animation),
        autoplay = true,
        loop = true,
    )
}
```

#### From raw data

DotLottieSource.Data is used to load an animation from raw data. It can load `.lottie` files from raw data.

```kotlin
// imports

@Preview()
@Composable
fun DotLottiePreview() {
    DotLottieAnimation(
        source = DotLottieSource.Data(byteArrayOf(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09),
    )
}
```

#### From a JSON string

`DotLottieSource.Json` is used to load an animation from a JSON string. It can load `.json` files from a JSON string.

```kotlin
// imports

@Preview()
@Composable
fun DotLottiePreview() {
    DotLottieAnimation(
        source = DotLottieSource.Json("{ \"v\": \"5.5.4\", \"fr\": 30, \"ip\": 0, \"op\": 180, \"w\": 1920, \"h\": 1080, \"nm\": \"Lottie Animation\" .... }"),
    )
}
```

## Changing background color

Using the `Modifier` parameter, you can change the background color of the `DotLottieAnimation` composable function.

```kotlin
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
// other imports

@Preview()
@Composable
fun DotLottiePreview() {
    DotLottieAnimation(
        ...,
        modifier = Modifier.background(Color.LightGray)
    )
}
```

## Adjusting animation display size

If you want to control the animation size, you can use the `Modifier` parameter to set the size. You can use the `width`,`height` or `size` modifier to set the size of the `DotLottieAnimation` composable function. `fillMaxWidth()`, `fillMaxSize()`, etc. can also be used to control the size of the animation.

```kotlin
import androidx.compose.ui.Modifier
// other imports

@Preview()
@Composable
fun DotLottiePreview() {
    DotLottieAnimation(
        ...,
        modifier = Modifier.size(200.dp)
    )
}
```

## Event Handling

You can listen to various animation lifecycle events by providing a list of `DotLottieEventListener` implementations to the `eventListeners` parameter.

```kotlin
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.lottiefiles.dotlottie.core.compose.ui.DotLottieAnimation
import com.lottiefiles.dotlottie.core.util.DotLottieSource
import com.lottiefiles.dotlottie.core.model.DotLottieEventListener
import android.util.Log

// Example Listener Implementation
class MyEventListener : DotLottieEventListener {
    override fun onLoad() { Log.d("DotLottieEvent", "onLoad") }
    override fun onPlay() { Log.d("DotLottieEvent", "onPlay") }
    override fun onPause() { Log.d("DotLottieEvent", "onPause") }
    override fun onStop() { Log.d("DotLottieEvent", "onStop") }
    override fun onFrame(frame: Float) { /* Log.d("DotLottieEvent", "onFrame: $frame") */ }
    override fun onLoop(loopCount: Int) { Log.d("DotLottieEvent", "onLoop: $loopCount") }
    override fun onComplete() { Log.d("DotLottieEvent", "onComplete") }
    override fun onRender(frameNo: Float) { /* Log.d("DotLottieEvent", "onRender: $frameNo") */ }
    override fun onLoadError(error: Throwable) { Log.e("DotLottieEvent", "onLoadError", error) }
    override fun onError(error: Throwable) { Log.e("DotLottieEvent", "onError", error) }
    override fun onFreeze() { Log.d("DotLottieEvent", "onFreeze") }
    override fun onUnFreeze() { Log.d("DotLottieEvent", "onUnFreeze") }
    override fun onDestroy() { Log.d("DotLottieEvent", "onDestroy") }
}

@Preview
@Composable
fun EventListenerPreview() {
    val listener = remember { MyEventListener() } // Remember listener instance

    DotLottieAnimation(
        source = DotLottieSource.Url("https://lottie.host/5525262b-4e57-4f0a-8103-cfdaa7c8969e/VCYIkooYX8.json"),
        autoplay = true,
        loop = false, // Set loop=false for onComplete to trigger naturally
        eventListeners = listOf(listener) // Pass the listener instance
    )
}
```

_Note: Implement only the methods you need. The `DotLottieEventListener` interface provides default empty implementations for all methods._

## Playback Control with `DotLottieController`

For fine-grained control over playback (play, pause, speed, seeking, etc.), use a `DotLottieController`.

```kotlin
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.foundation.gestures.detectTapGestures
// other imports from previous example...
import com.dotlottie.dlplayer.Mode
import com.lottiefiles.dotlottie.core.widget.DotLottieController

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

    // Example: Read state from controller
    val isPlaying by controller.isPlaying.collectAsState()
    val currentFrame by controller.currentFrame.collectAsState()

    Surface {
        Column {
            DotLottieAnimation(
                source = DotLottieSource.Url("https://lottie.host/294b684d-d6b4-4116-ab35-85ef566d4379/VkGHcqcMUI.lottie"),
                autoplay = false,
                loop = true,
                controller = controller, // Assign the controller
                modifier = Modifier.pointerInput(Unit) { // Example: Play/Pause on tap
                    detectTapGestures(
                        onPress = {
                            if (controller.isPlaying.value) {
                                controller.pause()
                            } else {
                                controller.play()
                            }
                        }
                    )
                }
            )
            Text("Playing: $isPlaying, Frame: ${currentFrame.toInt()}")
            Button(onClick = { controller.setSpeed(2f) }) {
                Text("Speed 2x")
            }
            Button(onClick = { controller.setPlayMode(Mode.REVERSE) }) {
                Text("Reverse")
            }
             Button(onClick = { controller.setFrame(50f) }) {
                Text("Go to Frame 50")
            }
        }
    }
}
```

_Note: The `DotLottieController` also provides properties like `isLoaded`, `isPaused`, `totalFrames`, `duration`, `currentState`, `width`, `height`, `markers`, and `stateMachineIsActive`._

## Layout and Fit

The layout system controls how the animation fits and aligns within its container. Use the `Fit` enum and alignment values to configure this.

### Fit Modes

| Fit Value        | Description                                                                               |
| ---------------- | ----------------------------------------------------------------------------------------- |
| `Fit.CONTAIN`    | Scale the animation to fit within the container while maintaining aspect ratio (default). |
| `Fit.COVER`      | Scale the animation to cover the entire container while maintaining aspect ratio.         |
| `Fit.FILL`       | Stretch the animation to fill the container, ignoring aspect ratio.                       |
| `Fit.FIT_WIDTH`  | Scale to match the container's width, maintaining aspect ratio.                           |
| `Fit.FIT_HEIGHT` | Scale to match the container's height, maintaining aspect ratio.                          |
| `Fit.NONE`       | No scaling applied — renders at original size.                                            |

### Setting Layout

You can set the layout using either a numeric alignment pair or the `LayoutUtil.Alignment` enum:

```kotlin
import com.dotlottie.dlplayer.Fit

// Numeric alignment (x, y) from 0.0 to 1.0
controller.setLayout(Fit.CONTAIN, Pair(0.5f, 0.5f)) // Centered

// Enum-based alignment
controller.setLayout(Fit.COVER, LayoutUtil.Alignment.CENTER)
```

The `LayoutUtil.Alignment` enum provides 9 standard positions: `TOP_LEFT`, `TOP_CENTER`, `TOP_RIGHT`, `CENTER_LEFT`, `CENTER`, `CENTER_RIGHT`, `BOTTOM_LEFT`, `BOTTOM_CENTER`, `BOTTOM_RIGHT`.

## Slots

The Slots API allows you to dynamically customize animation properties at runtime, such as colors, text, images, and more.

### Setting Individual Slots

```kotlin
val controller = remember { DotLottieController() }

// Set a color slot (ARGB integer)
controller.setColorSlot("primaryColor", Color.Red.toArgb())

// Set a text slot
controller.setTextSlot("headline", "Hello Android!")

// Set a scalar slot
controller.setScalarSlot("opacity", 0.8f)

// Set a vector slot
controller.setVectorSlot("direction", PointF(1.0f, 0.0f))

// Set a position slot
controller.setPositionSlot("anchor", PointF(100f, 200f))

// Set an image slot by file path
controller.setImageSlotPath("avatar", "/path/to/image.png")

// Set an image slot by data URL
controller.setImageSlotDataUrl("logo", "data:image/png;base64,...")
```

### Batch Setting Slots

You can set multiple slots at once using a JSON string:

```kotlin
controller.setSlots("""{"primaryColor": [1.0, 0.0, 0.0, 1.0], "headline": {"t": "Hello!"}}""")
```

### Clearing Slots

```kotlin
// Clear a specific slot
controller.clearSlot("primaryColor")

// Clear all slots
controller.clearSlots()
```

## Multiple Animations .lottie

Initially player renders defaultAnimation from .lottie file. You can change the animation by passing `animationId` to `loadAnimation` method on the controller.

Using the `manifest().animations` you can get the list of animations in the .lottie file.

```kotlin
@Preview()
@Composable
fun DotLottiePreview() {
    val controller = remember { DotLottieController() }
    val currentIndex = remember { mutableIntStateOf(0) }
    Column {
        DotLottieAnimation(
            source = DotLottieSource.Url("https://lottie.host/294b684d-d6b4-4116-ab35-85ef566d4379/VkGHcqcMUI.lottie"),
            autoplay = true,
            loop = true,
            controller = controller,
        )
        Button(onClick = {
            controller.manifest()?.animations?.let {
                currentIndex.intValue =
                    if (currentIndex.intValue == it.size - 1) 0 else currentIndex.intValue + 1
                controller.loadAnimation(it[currentIndex.intValue].id)
            }
        }) {
            Text(text = "Next")
        }
        Button(onClick = {
            controller.manifest()?.animations?.let {
                currentIndex.intValue =
                    if (currentIndex.intValue <= 0) it.size - 1 else currentIndex.intValue - 1
                controller.loadAnimation(it[currentIndex.intValue].id)
            }
        }) {
            Text(text = "Previous")
        }
    }
}
```

## Theming

Adding themes to your Lottie animations can significantly enhance your designs. See this [step-by-step guide↗](https://lottiefiles.com/blog/working-with-lottie-animations/how-to-add-themes-to-lottie-animations-in-lottie-creator) for creating themes.

### Using themes in your dotLottie animations

You can apply a theme by its ID either during initialization via the `themeId` parameter or programmatically using the controller.

**1. Apply Theme on Load:**

```kotlin
import androidx.compose.foundation.isSystemInDarkTheme

@Preview
@Composable
fun ThemedAnimationOnLoad() {
    // Example: Sync with system dark mode
    val currentThemeId = if (isSystemInDarkTheme()) "dark" else null // Use null for default

    DotLottieAnimation(
        source = DotLottieSource.Asset("themed_animation.lottie"),
        autoplay = true,
        loop = true,
        themeId = currentThemeId // Apply theme ID here
    )
}
```

**2. Control Theme with Controller:**

```kotlin
@Preview
@Composable
fun ThemeControllerPreview() {
    val controller = remember { DotLottieController() }
    var currentTheme by remember { mutableStateOf<String?>("light") }

    LaunchedEffect(currentTheme) {
        if (currentTheme == null) {
            controller.resetTheme()
        } else {
            controller.setTheme(currentTheme!!) // Apply theme via controller
        }
    }

    Column {
        DotLottieAnimation(
            source = DotLottieSource.Asset("themed_animation.lottie"),
            autoplay = true,
            loop = true,
            controller = controller
        )
        Row {
            Button(onClick = { currentTheme = "light" }) { Text("Light") }
            Button(onClick = { currentTheme = "dark" }) { Text("Dark") }
            Button(onClick = { currentTheme = null }) { Text("Reset") }
        }
        Text("Active Theme: ${controller.activeThemeId.collectAsState().value ?: "Default"}")
    }
}
```

**3. Load Theme Data Dynamically:**

You can also load theme configurations directly from a JSON string.

```kotlin
val themeJson = """{"id":"runtimeTheme","values":{"color":"#ff0000"}} """
// In a coroutine scope or LaunchedEffect:
val success = controller.setThemeData(themeJson)
if (success) {
    Log.d("Theming", "Runtime theme applied")
} else {
    Log.e("Theming", "Failed to apply runtime theme data")
}
```

## State Machines

State machines let you define interactive animation logic — states, transitions, guards, and interactions — inside a `.lottie` file. The Android player executes that logic at runtime.

### Basic Setup

The simplest way to use a state machine is to pass its ID via the `stateMachineId` parameter. The player loads and starts it automatically once the animation is ready. A `DotLottieController` is required so you can interact with the state machine at runtime.

```kotlin
import com.lottiefiles.dotlottie.core.widget.DotLottieController

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

    DotLottieAnimation(
        source = DotLottieSource.Asset("interactive.lottie"),
        autoplay = true,
        stateMachineId = "myStateMachine",
        controller = controller,
        modifier = Modifier.fillMaxWidth().aspectRatio(1f)
    )
}
```

Pointer and click interactions defined in the state machine's `interactions` array (e.g. `Click`, `PointerDown`, `PointerEnter`) are handled **automatically** by the composable. You do not need to add gesture handlers for these.

### Programmatic Loading

To load or restart a state machine at a specific point in your app's lifecycle, use the controller directly. Call these methods after the animation has loaded.

```kotlin
@Composable
fun StateMachineProgrammatic() {
    val controller = remember { DotLottieController() }

    LaunchedEffect(Unit) {
        // Wait for animation to load, then load and start the state machine
        val loaded = controller.stateMachineLoad("myStateMachine")
        if (loaded) {
            controller.stateMachineStart()
        }
    }

    DotLottieAnimation(
        source = DotLottieSource.Asset("interactive.lottie"),
        autoplay = true,
        controller = controller,
        modifier = Modifier.fillMaxWidth().aspectRatio(1f)
    )
}
```

You can also load a state machine from a raw JSON string — useful for state machines not bundled inside the `.lottie` file:

```kotlin
val json = context.assets.open("my_state_machine.json").bufferedReader().readText()
val loaded = controller.stateMachineLoadData(json)
if (loaded) {
    controller.stateMachineStart()
}
```

To stop the state machine:

```kotlin
controller.stateMachineStop()
```

### Setting Inputs

State machine inputs are named variables (`Boolean`, `Numeric`, `String`) that guard conditions read to decide whether a transition fires. Set them from your app code at any time after the state machine has started.

```kotlin
// Boolean
controller.stateMachineSetBooleanInput("isActive", true)

// Numeric
controller.stateMachineSetNumericInput("progress", 75f)

// String
controller.stateMachineSetStringInput("mode", "dark")
```

Read them back if needed:

```kotlin
val isActive = controller.stateMachineGetBooleanInput("isActive")
val progress = controller.stateMachineGetNumericInput("progress")
val mode     = controller.stateMachineGetStringInput("mode")
```

### Firing Events

`Event`-type inputs are edge-triggered: setting one causes a transition to fire immediately if a matching guard exists. Use `stateMachineFire` to trigger them by name:

```kotlin
controller.stateMachineFire("buttonClicked")
```

A common pattern is wiring a UI element to fire an event:

```kotlin
Button(onClick = { controller.stateMachineFire("next") }) {
    Text("Next")
}
```

### Listening to State Machine Events

Implement `StateMachineEventListener` and register it on the controller to observe state changes, input changes, and custom events. All methods have default empty implementations — only override what you need.

```kotlin
val smListener = object : StateMachineEventListener {
    override fun onStateEntered(enteringState: String) {
        Log.d("SM", "Entered: $enteringState")
    }
    override fun onStateExit(leavingState: String) {
        Log.d("SM", "Exited: $leavingState")
    }
    override fun onTransition(previousState: String, newState: String) {
        Log.d("SM", "$previousState → $newState")
    }
    override fun onInputFired(inputName: String) {
        Log.d("SM", "Event fired: $inputName")
    }
    override fun onCustomEvent(message: String) {
        Log.d("SM", "Custom event: $message")
    }
    override fun onBooleanInputValueChange(inputName: String, oldValue: Boolean, newValue: Boolean) {
        Log.d("SM", "$inputName: $oldValue → $newValue")
    }
    override fun onNumericInputValueChange(inputName: String, oldValue: Float, newValue: Float) {
        Log.d("SM", "$inputName: $oldValue → $newValue")
    }
    override fun onError(message: String) {
        Log.e("SM", "Error: $message")
    }
}

LaunchedEffect(Unit) {
    controller.stateMachineAddEventListener(smListener)
}
```

Remove listeners when they are no longer needed to avoid leaks:

```kotlin
DisposableEffect(Unit) {
    onDispose { controller.stateMachineRemoveEventListener(smListener) }
}
```

### URL Opening

If your state machine uses `OpenUrl` actions, configure how URLs are handled via `OpenUrlPolicy` when calling `stateMachineStart`:

```kotlin
controller.stateMachineStart(
    openUrl = OpenUrlPolicy(
        requireUserInteraction = true,
        whitelist = listOf("https://lottiefiles.com/*")
    ),
    onOpenUrl = { url ->
        val intent = Intent(Intent.ACTION_VIEW, url.toUri())
        context.startActivity(intent)
    }
)
```

| Property                 | Default | Description                                                           |
| ------------------------ | ------- | --------------------------------------------------------------------- |
| `requireUserInteraction` | `false` | When `true`, the URL opens only as a direct result of a user gesture. |
| `whitelist`              | `[]`    | List of allowed URL patterns. An empty list allows all URLs.          |

### Complete Example

```kotlin
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
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 android.util.Log

@Composable
fun InteractiveAnimation() {
    val controller = remember { DotLottieController() }
    var currentState by remember { mutableStateOf("—") }

    val smListener = remember {
        object : StateMachineEventListener {
            override fun onStateEntered(enteringState: String) {
                currentState = enteringState
            }
            override fun onTransition(previousState: String, newState: String) {
                Log.d("SM", "$previousState → $newState")
            }
            override fun onCustomEvent(message: String) {
                Log.d("SM", "Custom event: $message")
            }
            override fun onError(message: String) {
                Log.e("SM", "Error: $message")
            }
        }
    }

    LaunchedEffect(Unit) {
        controller.stateMachineAddEventListener(smListener)
    }

    DisposableEffect(Unit) {
        onDispose { controller.stateMachineRemoveEventListener(smListener) }
    }

    Column(
        modifier = Modifier.fillMaxSize().padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        DotLottieAnimation(
            source = DotLottieSource.Asset("interactive.lottie"),
            autoplay = true,
            stateMachineId = "mainFSM",
            controller = controller,
            modifier = Modifier.fillMaxWidth().aspectRatio(1f)
        )

        Text("State: $currentState")

        Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
            Button(onClick = { controller.stateMachineSetBooleanInput("isActive", true) }) {
                Text("Activate")
            }
            Button(onClick = { controller.stateMachineFire("reset") }) {
                Text("Reset")
            }
        }
    }
}
```
