lottie_fixup
Fixes Lottie/Bodymovin exports that crash or freeze the
lottie Flutter package: audio layers,
empty precomps, and expressions that lottie doesn't execute (loopOut()/
loopIn(), wiggle(), random(), time-based motion, cross-layer links).
Features
- Stops the audio-layer crash — After Effects audio layers (
"ty": 6) ship without the transform blocklottieexpects, which throwsNull check operator used on a null value. This package strips them. - Bakes
loopOut()/loopIn()expressions into real keyframes, so looping animations don't freeze after their first cycle (lottiedoesn't execute expressions). All four After Effects loop modes are supported in both directions —'cycle','pingpong','offset','continue'— the latter two on any numeric property (position, scale, rotation, opacity...) — plus the duration-basedloopOutDuration()/loopInDuration()variants in'cycle'/'pingpong'mode. - Bakes other expressions, on a never-animated property or one that's
already keyframed (the expression's result is authoritative, same as After
Effects — the original curve is only available through
value/valueAtTime()): continuoustime-based motion (e.g.time * 180for constant rotation),if/elsebranching with comparisons/booleans, localvarbindings, cross-layer links (thisComp.layer('Name').transform.position, copied exactly when that's the whole expression on a never-animated property, sampled when combined with other math or via.valueAtTime(t)), theMath.*namespace,linear()/ease()/easeIn()/easeOut()/clamp(),add()/sub()/mul()/div()/value,posterizeTime(), andrandom()/wiggle()(a deterministic, seeded approximation — After Effects' own noise/PRNG can't be reproduced bit-for-bit, but this is reproducible across builds and beats a frozen property). Awiggle()-only expression on a shape path wiggles each vertex independently. - Prunes empty precomps and now-unreferenced assets left behind by the fixes above.
- Use it at load time (drop-in decoder, no build step) or ahead of time (CLI, zero runtime cost).
Getting started
Add the dependency:
dependencies:
lottie_fixup: ^0.3.0
Usage
At load time — no build step
Drop fixupLottieDecoder into any lottie loading API that takes a
decoder:
import 'package:lottie/lottie.dart';
import 'package:lottie_fixup/lottie_fixup.dart';
Lottie.asset('assets/character.json', decoder: fixupLottieDecoder)
Safe to apply unconditionally, even to files already fixed ahead of
time — fix is a no-op when there's nothing left to do. This adds a JSON
decode/walk/re-encode once per composition load, not per frame. For larger
files, pass backgroundLoading: true to move that work off the UI isolate:
Lottie.asset(
'assets/character.json',
decoder: fixupLottieDecoder,
backgroundLoading: true,
)
Ahead of time — CLI
For an animation that ships in every build and never changes, fix it once and skip the runtime cost entirely:
dart pub global activate lottie_fixup
lottie_fixup diagnose assets/animations/*.json # report only, no changes
lottie_fixup fix assets/animations/*.json # fix in place
Library
import 'dart:convert';
import 'dart:io';
import 'package:lottie_fixup/lottie_fixup.dart';
final file = File('animation.json');
final doc = jsonDecode(file.readAsStringSync()) as Map<String, dynamic>;
final result = fix(doc); // mutates doc in place
if (result.changed) {
file.writeAsStringSync(jsonEncode(doc));
}
Configuration: opting out of approximations
A few parts of expression baking are an approximation or a judgment call
rather than an exact match to what After Effects would render — see
Features above. BakeOptions lets you turn any of them off
individually; every option defaults to true (bake everything), and turning
one off only ever makes baking more conservative — the affected
expressions are reported as unsupported instead of altered:
| Option | Default | Turn off to... |
|---|---|---|
bakeRandomAndWiggle |
true |
Leave random()/wiggle() unbaked (also disables bakeShapePathWiggle). |
bakeOnKeyframedProperties |
true |
Only bake never-keyframed ("a": 0) properties, matching versions before 0.3.0. |
bakeShapePathWiggle |
true |
Leave wiggle() on a shape path unbaked. |
bakeApproximateEasing |
true |
Leave ease()/easeIn()/easeOut() unbaked (linear() is unaffected — it's an exact formula, not an approximation). |
It plugs into every entry point:
const options = BakeOptions(bakeOnKeyframedProperties: false);
// Library
fix(doc, options: options);
diagnose(rawJson, doc, options: options); // pass the same options you'll fix() with
// At load time
Lottie.asset(
'assets/character.json',
decoder: fixupLottieDecoderWithOptions(options),
)
# CLI
lottie_fixup fix --no-keyframed-properties assets/animations/*.json
What this does not fix
- Expressions this package's small evaluator doesn't understand (e.g.
effect(...), a reference into a nested comp,for/whileloops or user-defined functions) are reported (diagnose, orFixResult.propertyBake.skippedExpressions) but left untouched. 'offset'/'continue'on a non-numeric value (a shape path, for example, rather than position/scale/rotation/opacity) is reported rather than baked, since those modes work by doing arithmetic directly on the value.- A non-
wiggle()expression on a shape path (arithmetic directly on a path value) is reported rather than baked — After Effects doesn't support that either. - A duration variant (
loopOutDuration/loopInDuration) whose duration is shorter than the keyframed segment itself is reported rather than baked — that would need interpolating a cut point in the middle of the real animation, which isn't implemented. - A property that calls both
loopInandloopOut(a manual "loop both ways" expression) is reported rather than guessed at. random()/wiggle()are baked as a plausible approximation, not a bit-exact match to After Effects — see Features above.- A layer missing
ksfor a reason other than being an audio layer is flagged (SanitizeResult.layersMissingTransform) rather than silently removed, since that could be a real authoring mistake worth checking by hand.
Additional information
If this package saved you a debugging session, consider buying me a coffee.
Libraries
- core
- The pure-Dart half of
lottie_fixup: fixes for common issues in Lottie files exported from After Effects via Bodymovin, with no dependency on Flutter. Import this (instead ofpackage:lottie_fixup/lottie_fixup.dart) from code that must run under plaindart run— a CLI tool or a script with no Flutter engine attached — since the main library also exports fixupLottieDecoder, which pulls inpackage:lottieand thereforedart:ui. - lottie_fixup
- Fixes for common issues in Lottie files exported from After Effects via
Bodymovin, before they reach the
lottieFlutter package.
