Command Palette

Search for a command to run...

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.

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.

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

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

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

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

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.

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.

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.

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 ValueDescription
Fit.CONTAINScale the animation to fit within the container while maintaining aspect ratio (default).
Fit.COVERScale the animation to cover the entire container while maintaining aspect ratio.
Fit.FILLStretch the animation to fill the container, ignoring aspect ratio.
Fit.FIT_WIDTHScale to match the container's width, maintaining aspect ratio.
Fit.FIT_HEIGHTScale to match the container's height, maintaining aspect ratio.
Fit.NONENo scaling applied — renders at original size.

Setting Layout

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

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

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:

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

Clearing Slots

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

@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↗ 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:

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:

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

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.

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.

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

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:

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.

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

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

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

Read them back if needed:

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:

controller.stateMachineFire("buttonClicked")

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

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.

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:

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:

controller.stateMachineStart(
    openUrl = OpenUrlPolicy(
        requireUserInteraction = true,
        whitelist = listOf("https://lottiefiles.com/*")
    ),
    onOpenUrl = { url ->
        val intent = Intent(Intent.ACTION_VIEW, url.toUri())
        context.startActivity(intent)
    }
)
PropertyDefaultDescription
requireUserInteractionfalseWhen 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

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")
            }
        }
    }
}
Last updated: April 10, 2026 at 9:12 AMEdit this page