lottie-flutter: a pure Dart Lottie player, and how far down the stack it goes
Render After Effects animations natively on Flutter. This package is a pure Dart implementation of a Lottie player.
At a glance
- What is it?
- lottie-flutter is an unofficial pure Dart conversion of the Lottie-android player, so After Effects animations exported with Bodymovin render on Android, iOS, desktop and the web with no native dependency. The interesting part is how many layers of control it exposes, from a one line asset widget down to painting a frame onto your own canvas.
- Who is it for?
- Adopt lottie-flutter if you want After Effects animations in a Flutter application without a native dependency, and you value the fact that the whole player is Dart and therefore behaves identically on every platform Flutter supports. Do not adopt it if you need a feature that only exists in the upstream Lottie-android player, since this is an unofficial conversion and the two do not move in lockstep.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Dart, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A port, not a wrapper
Lottie for Flutter renders After Effects animations, exported as JSON with Bodymovin, natively on mobile. The Flutter implementation is what this repository is, and the README is precise about its nature: it is an unofficial conversion of the Lottie-android library in pure Dart.
That word, conversion, sets the expectation. There is no platform channel and no native library, so the package is Dart from the parser to the painter, and it works on Android, iOS, macOS, Linux, Windows and the web. The trade-off is that it is a port of a specific upstream project, and anything the Android library adds later will appear here later or not at all.
The package is published on pub.dev as `lottie`, and the repository is MIT licensed with a `FUNDING.yml`, a `CHANGELOG.md` and a `pubspec.lock`. The releases come in pairs on the same day, v3.6.0 and v3.6.1 both on 2026-09-18, with v3.5.1 from 2026-07-08, and the last push was on 2026-09-18.
The example project is the best documentation of the supported platforms, because the repository carries an `example/` directory with `android/`, `ios/`, `linux/`, `macos/` and `web/` subdirectories, and a `tool/` directory for the generation scripts the README mentions in its template file, `README.template.md`.
One widget, three sources
The simple case is a widget in a list, and the constructor tells you where the file comes from.
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView(
children: [
// Load a Lottie file from your assets
Lottie.asset('assets/LottieLogo1.json'),
// Load a Lottie file from a remote url
Lottie.network(
'https://raw.githubusercontent.com/xvrh/lottie-flutter/master/example/assets/Mobilo/A.json',
),
// Load an animation and its images from a zip file
Lottie.asset('assets/lottiefiles/angel.zip'),
],
),
),
);
}
}Three sources in one example. An asset, a URL, and a zip archive that carries the animation together with its images, which is the case where the After Effects export references external bitmap assets and the JSON alone is not enough.
The widget loads the file, parses it and runs the animation indefinitely. The convenience constructors are `Lottie.asset`, `Lottie.network` and `Lottie.memory`, and each of them loads, parses and caches the JSON automatically, which is the behaviour most applications want and the reason the cache is not something you have to think about.
Bringing your own AnimationController
The `Lottie` widget will run by itself, but the moment you need to control playback you pass your own `AnimationController`, and the example shows the whole pattern including the disposal.
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}The controller is created in `initState` with the state as the ticker provider, and disposed in `dispose`. Nothing about that is Lottie specific; it is the standard Flutter pattern, which is a good sign about how the package fits into an application.
The Lottie specific part is the `onLoaded` callback, which receives the composition once the file has been parsed. The composition carries the duration of the animation, and the example uses it to configure the controller and start the animation:
Lottie.asset(
'assets/LottieLogo1.json',
controller: _controller,
onLoaded: (composition) {
// Configure the AnimationController with the duration of the
// Lottie file and start the animation.
_controller
..duration = composition.duration
..forward();
},
);Once the controller is yours, the README says you get a rich API to play the animation in various ways: start and stop it when you want, play forward or backward, and loop between specific points. The duration comes from the file rather than from you, which removes the usual source of a desynchronised animation.
Sizing works like the Image widget
Sizing is handled the way Flutter users expect, and the README states the rule directly: the `Lottie` widget takes the same arguments and has the same behaviour as the `Image` widget in terms of controlling its size.
Lottie.asset(
'assets/LottieLogo1.json',
width: 200,
height: 200,
fit: BoxFit.fill,
)The `width` and `height` are optional and fall back on the size imposed by the parent or on the intrinsic size of the animation. That last fallback is the one that catches people out when a Lottie widget appears at an unexpected size: a JSON composition has a natural width and height, and without a constraint the widget uses them.
The `fit` argument is the same `BoxFit` enum as everywhere else in Flutter, so `BoxFit.fill` distorts the animation to the given box the same way it distorts an image, and the other values do what they do for images.
There is nothing Lottie specific in this part, and that is the point. A package that introduces its own layout rules is a package that fights the framework, and this one does not.
Loading and drawing, separated
The convenience constructors hide two things that you will eventually need to reach: how the file is loaded, and how the composition is drawn.
For loading, there are the `AssetLottie`, `NetworkLottie` and `MemoryLottie` classes, each of which exposes a `load` method returning a `Future<LottieComposition>`. The example builds that future in `initState` and renders it with a `FutureBuilder`:
class _MyWidgetState extends State<MyWidget> {
late final Future<LottieComposition> _composition;
@override
void initState() {
super.initState();
_composition = AssetLottie('assets/LottieLogo1.json').load();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<LottieComposition>(
future: _composition,
builder: (context, snapshot) {
var composition = snapshot.data;
if (composition != null) {
return Lottie(composition: composition);
} else {
return const Center(child: CircularProgressIndicator());
}
},
);
}
}This is the shape you want when the file is not known at build time, when you want to show your own loading state, or when the same composition is used in more than one place and you do not want it parsed twice.
For drawing, the lowest level is `LottieDrawable`, which can be painted onto any `Canvas` at any frame. The example draws forty frames of a composition in a grid, which is the clearest possible demonstration of what the object is for:
class _Painter extends CustomPainter {
final LottieDrawable drawable;
_Painter(LottieComposition composition)
: drawable = LottieDrawable(composition);
@override
void paint(Canvas canvas, Size size) {
var frameCount = 40;
var columns = 10;
for (var i = 0; i < frameCount; i++) {
var destRect = Offset(i % columns * 50.0, i ~/ 10 * 80.0) & (size / 5);
drawable
..setProgress(i / frameCount)
..draw(canvas, destRect);
}
}`setProgress` takes a fraction of the composition and `draw` takes a destination rectangle. The three layers, widget, composition and drawable, are separate on purpose, and each one is useful at a different level of control.
Changing text, colour and position while it runs
The last layer of control is the delegate system, which is what you use when the animation has to reflect application state rather than just play.
The README describes modifying some properties of the animation at runtime, changing the text, the colour, the opacity and the position of some layers. For each `ValueDelegate` you can either provide a static value or a callback that computes a value for each frame.
class _Animation extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Lottie.asset(
'assets/Tests/Shapes.json',
delegates: LottieDelegates(
text: (initialText) => '**$initialText**',
values: [
ValueDelegate.color(const [
'Shape Layer 1',
'Rectangle',
'Fill 1',
], value: Colors.red),The address of the property is a path through the composition, a list of layer and property names, which is the part that takes experimentation. `Shape Layer 1`, then `Rectangle`, then `Fill 1` identifies a specific fill inside a specific shape layer, and the same path style is used for the other value types.
The two forms of delegate answer different needs. A static value is a one-off change, for example colouring a shape to match a state of the application. A callback is evaluated per frame, which is how you drive something like a progress indicator whose fill follows an animation controller, or text that shows a live number.
Because the delegate is a function, the animation can be bound to application state without rebuilding the widget tree on every change, which is the reason the API is shaped this way rather than as a plain property.
Where lottie-flutter is the wrong choice
The first wrong case is a project that needs a feature only the upstream player has. The README says this is an unofficial conversion of Lottie-android, and a port follows its source rather than leading it. The changelog in the repository is the place to check whether a specific feature has arrived, and the answer is not always yes on the day you ask.
The second is a project that already has a native Lottie dependency on one platform and wants the same player everywhere. The point of this package is that it is pure Dart, with no native code and identical behaviour on every target. If your application already ships the native Android library for one screen, you now have two players in the same app, with different performance characteristics and different file handling.
The third is heavy animation work. A pure Dart implementation that parses JSON and paints it frame by frame is a different proposition from a native library with a hardware-accelerated renderer behind it, and the README does not publish any performance numbers. On a low end Android device with a complex composition and several on screen at once, that difference is worth measuring before you commit to a design that depends on it.
The fourth is a team that needs the After Effects pipeline itself. This package starts at the exported JSON. Producing that JSON, and the images it references, is Bodymovin in After Effects, and the zip form of the asset is the escape hatch for animations with external bitmaps. Anyone asking the package to edit the source file is asking the wrong tool.
The last is a project with a strict dependency policy. The package is MIT licensed and published on pub.dev, which is the simplest case, but the example repository carries a `pubspec.lock`, so the example pins its versions, and the real dependency question is about the Flutter SDK version your app targets rather than about this package.
Editorial conclusion
Adopt lottie-flutter if you want After Effects animations in a Flutter application without a native dependency, and you value the fact that the whole player is Dart and therefore behaves identically on every platform Flutter supports. Do not adopt it if you need a feature that only exists in the upstream Lottie-android player, since this is an unofficial conversion and the two do not move in lockstep. Before you commit, check the changelog for the features you need, and decide whether you want the convenience constructors or the low level path, because the widget, the composition loader and the drawable are three separate layers and picking the wrong one costs you more code than it saves.
Frequently asked questions
How do I show a Lottie animation in Flutter?
Add the package and use one of the convenience constructors, for example `Lottie.asset('assets/LottieLogo1.json')`, `Lottie.network(url)` or `Lottie.memory(bytes)`. The widget loads, parses and caches the file, then runs the animation indefinitely.
How do I control playback of a Lottie animation in Flutter?
Provide your own AnimationController to the widget and set its duration from the composition in the onLoaded callback. With the controller in hand you can start and stop the animation, play forward or backward, and loop between specific points.
Does lottie-flutter require native code?
No. The README describes it as an unofficial conversion of the Lottie-android library in pure Dart, and says it works on Android, iOS, macOS, Linux, Windows and the web without a native dependency.
Can I change the text or colour of a Lottie animation while it plays?
Yes. The widget takes a LottieDelegates object, and each ValueDelegate can be given a static value or a callback that is evaluated for each frame, with properties addressed by a path of layer and property names.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/xvrh-lottie-flutter)Was this useful?