Flutter tutorials and courses by Andrea Bizzotto

Flutter: Why do TweenAnimationBuilder and AnimatedBuilder have a child argument?

If you've been working with animations in Flutter, you may have noticed that some widgets take both child and builder arguments.

Here's one example that uses TweenAnimationBuilder to animate a Container's color from red to green:

TweenAnimationBuilder<Color>( tween: Tween<Color>(begin: Colors.red, end: Colors.green), duration: const Duration(seconds: 1), // child is *optional* so we can pass null or omit it child: null, // builder is *required* // note that the third argument is an (optional) child builder: (BuildContext context, Color color, Widget? child) { return Container(color: color); }, )

If we want we can omit the child argument entirely, and things will still work as long as we return a widget inside the builder.

So, why and when should we pass a child?

Answer: Performance optimizations

Here's what the official AnimatedBuilder documentation says:

If your builder function contains a subtree that does not depend on the animation, it's more efficient to build that subtree once instead of rebuilding it on every animation tick.

If you pass the pre-built subtree as the child parameter, the AnimatedBuilder will pass it back to your builder function so that you can incorporate it into your build.

Using this pre-built child is entirely optional, but can improve performance significantly in some cases and is therefore a good practice.

In other words, by passing a child widget using it inside the builder, we guarantee that the child is only built once rather than on every animation tick.

Here's another example that returns a rotating Container:

AnimatedBuilder( animation: someAnimation, // pass a child widget child: Container(color: Colors.red), // get the child back as a (nullable) Widget object builder: (BuildContext context, Widget? child) { // this rebuilds on every animation tick return Transform( alignment: Alignment.center, transform: Matrix4.rotationZ(2 * pi * someAnimation.value), // this is only built once child: child, ); }, )

In this case, we use an AnimatedBuilder because the transform argument depends on the animation's value.

But the child of the Transform widget only needs to be built once, so we can pass it to the AnimatedBuilder directly and reuse it in the builder callback.

So when can we use the child argument?

The answer is: when you have a widget that does not depend on the animation value.

Let's check this again:

child: Container(color: Colors.red)

In this case the Container never changes, so it doesn't need to be rebuilt inside the builder.


Let's revisit the original TweenAnimationBuilder example:

TweenAnimationBuilder<Color>( tween: Tween<Color>(begin: Colors.red, end: Colors.green), duration: const Duration(seconds: 1), builder: (BuildContext context, Color color, Widget? child) { return Container(color: color); }, )

In this case, we cannot create the Container upfront because its color changes as part of the animation. So we have to create it on-the-fly inside the builder.

In addition to TweenAnimationBuilder and AnimatedBuilder, other Flutter widgets follow the same convention. For example ValueListenableBuilder automatically rebuilds when its valueListenable argument changes. If you're working with some kind of FooBuilder widget, check if it has a child argument.

Wrap Up

We now know when to use the child argument, and when not to.

Once again, this is a performance optimization.

Very often, it makes sense to measure performance first and only optimize the code if needed.

But the Flutter animation APIs are quite easy to use, so we may as well optimize upfront and use the child argument when we can.

Happy coding!

Want more?

Fast-track your Flutter learning with over 40 hours of in-depth content.