How the C API works

Understand the dotLottie C API design - opaque player handles, result codes, feature gating, string conventions, and how it relates to the other players.

The native runtime is not a separate player. It is the C surface of dotlottie-rs, the Rust core that renders every dotLottie animation on every platform. Understanding that relationship explains most of the API's shape.

Where it sits

The Android, iOS, Flutter, and React Native players are thin platform wrappers over this same C API. The web player is the exception — it uses wasm-bindgen bindings to the same core instead.

Two consequences follow. Rendering is identical to every other dotLottie player, because it is literally the same code. And the C API is production-tested rather than a side path — it is what ships in the mobile players.

Opaque handles

The API is handle-based. dotlottie_new_player() returns a DotLottiePlayer *, an opaque pointer whose layout is deliberately not exposed, and nearly every other function takes it as its first argument.

DotLottiePlayer *player = dotlottie_new_player(0);
/* ... */
dotlottie_destroy(player);

There are exactly two handle types, and each has its own destructor:

HandleCreated byReleased by
DotLottiePlayer *dotlottie_new_player()dotlottie_destroy()
dotlottieDotLottieStateMachine *dotlottie_state_machine_load() or ..._load_data()dotlottie_state_machine_release()

Font loading is the one exception to the handle rule: dotlottie_load_font() and dotlottie_unload_font() take no player, because fonts live in a process-wide registry shared by every player.

Result codes

Fallible functions return dotlottieDotLottieResult. Zero means success, and the header defines DOTLOTTIE_SUCCESS for it.

ValueNumericMeaning
Success0The call succeeded
Error1A general failure
InvalidParameter2A null or out-of-range argument, including an invalid handle
ManifestNotAvailable3The loaded file has no manifest
AnimationNotLoaded4The operation needs a loaded animation
InsufficientCondition5The player is not in a state where the call makes sense
FeatureNotEnabled6The library was compiled without the required feature

Compare against DOTLOTTIE_SUCCESS rather than testing for truthiness, since every failure is a non-zero value:

if (dotlottie_load_animation_path(player, path) != DOTLOTTIE_SUCCESS) {
  /* handle the failure */
}

Getters that cannot fail

A handful of simple getters return the value directly instead of a result code, and substitute a default when the handle is invalid:

float speed = dotlottie_get_speed(player);   /* 1.0 if player is NULL */

dotlottie_get_mode(), dotlottie_get_speed(), dotlottie_get_loop(), dotlottie_get_loop_count(), dotlottie_get_autoplay(), dotlottie_get_use_frame_interpolation(), dotlottie_status(), and dotlottie_is_complete() all behave this way.

A default value is indistinguishable from a real one

A speed of 1.0 might mean the animation plays at normal speed, or that you passed a null player. These getters cannot report an error, so validate your handle separately rather than inferring validity from the value you get back.

Reading strings

The API never allocates memory that you have to free. Instead, functions that return a string use a two-call pattern: ask for the required size, allocate, then ask for the content.

size_t size = 0;
if (dotlottie_get_active_marker(player, NULL, &size) != DOTLOTTIE_SUCCESS) {
  return;
}

char *marker = malloc(size);            /* size includes the null terminator */
dotlottie_get_active_marker(player, marker, NULL);

/* use marker ... */
free(marker);

Passing NULL for the buffer queries the size; passing NULL for size_out skips the size report. Every function with a buffer and size_out pair works this way — manifests, theme IDs, animation IDs, slot IDs and values, and state machine state.

dotlottie_get_marker() is different: it hands back a pointer into memory the library owns, which you must not free.

Feature gating

The runtime is compiled with only the features you ask for, and functions belonging to a feature you left out still exist — they just return FeatureNotEnabled instead of doing anything. This keeps the header and ABI stable no matter how the library was configured.

Result ofRequires feature
dotlottie_load_dotlottie_data, dotlottie_get_manifest, dotlottie_load_animationdotlottie
dotlottie_set_theme, dotlottie_set_theme_data, dotlottie_get_theme_idtheming
dotlottie_set_audio_volume, dotlottie_get_audio_volumeaudio
Any dotlottie_state_machine_* functionstate-machines

State machines signal absence differently: dotlottie_state_machine_load() returns NULL when the feature is off, so you will never hold a non-null state machine handle in a build without it.

Getting FeatureNotEnabled from a call you expected to work means rebuilding with that feature. See Choose your features.

Naming in the generated header

The header is generated by cbindgen, which prefixes types but leaves function names as they are. Knowing the rule saves time reading it:

  • Functions keep their names — dotlottie_new_player, dotlottie_set_speed

  • Types gain a dotlottie prefix — dotlottiePlayer, dotlottieLayout, dotlottieDotLottieResult

  • Convenience aliases are provided for the two you use most — DotLottiePlayer and the DOTLOTTIE_SUCCESS constant

Enum constants are unprefixed and share the global namespace

Enum values are emitted bare: Success, Error, Play, Pause, Stop, Frame, Render, Loop, Complete, None, Fill, Cover, Idle, Playing, Surface, Texture. Common words like None, Error, Loop, and Stop collide easily with application or platform macros. If you hit a conflict, include dotlottie_player.h before your own headers, or wrap the include in a translation unit that exposes only your own wrapper functions.

Two further quirks in the generated header are worth knowing so you don't chase them:

  • DotLottieConfig is declared as a typedef to a struct that is never defined. It is a leftover with no use — configure the player through the individual dotlottie_set_* functions instead.

  • Some doc-comment examples inside the header reference constants that don't exist, such as DotLottiePlayerEventType_Load. The real spelling is the bare Load.

Error handling across the boundary

The release build compiles Rust with panic = "abort". If the core hits an unrecoverable internal error, the whole process terminates — a panic cannot be caught or converted into a result code by the C caller. In practice this is reserved for genuine bugs; ordinary failures such as a bad path or malformed animation come back as result codes.

Learn more

Last updated: August 13, 2026 at 9:17 AMEdit this page