Flutter app development tutorials by Andrea Bizzotto

How to reduce AnimationController boilerplate code: Flutter Hooks vs extending the State class

AnimationController is the big daddy of animations in Flutter. You can use it to forward, reverse, or repeat animations, and much more.

But setting up an AnimationController requires a lot of ceremony:

// 1. declare a StatefulWidget class RotatingContainer extends StatefulWidget { @override _RotatingContainerState createState() => _RotatingContainerState(); } class _RotatingContainerState extends State<RotatingContainer> // 2. add SingleTickerProviderStateMixin with SingleTickerProviderStateMixin { // 3. declare the animation controller late final AnimationController _animationController; @override void initState() { super.initState(); // 4. initialize the animation controller _animationController = AnimationController( vsync: this, duration: Duration(milliseconds: 750), ); } @override void dispose() { // 5. dispose when done _animationController.dispose(); super.dispose(); } // build method, etc. }

This is all before we even write any animation code. If we want to use this to show a rotating box, we can call _animationController.repeat(); inside initState() and add this code in the build() method:

@override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animationController, child: Container(color: Colors.red), builder: (context, child) { return Transform( alignment: Alignment.center, transform: Matrix4.rotationZ(2 * pi * _animationController.value), child: child, ); }, ); }

Note: the red Container is passed as a child to the AnimatedBuilder and reused inside the builder. See "Why do TweenAnimationBuilder and AnimatedBuilder have a child argument?" for an explanation of this technique.

If you have a lot of explicit animations, it is difficult to reuse the code in initState() and dispose(), and copy-pasting all the boilerplate across widgets is error-prone.

So let's explore two ways of reducing the boilerplate code.

1. Use Flutter Hooks

The flutter_hooks package makes working with AnimationController dead simple:

// Note: we are extending `HookWidget` class RotatingContainer extends HookWidget { @override Widget build(BuildContext context) { // All the steps above are replaced by a single line: final controller = useAnimationController(duration: Duration(seconds: 2)) ..repeat(); // start the animation return AnimatedBuilder( animation: controller, child: Container(color: Colors.red), builder: (context, child) { return Transform( alignment: Alignment.center, transform: Matrix4.rotationZ(2 * pi * controller.value), child: child, ); }, ); } }

All the magic for obtaining the TickerProvider, initializing and disposing the AnimationController happens inside the useAnimationController() method.

Make sure to read the documentation for flutter_hooks to understand how to use this correctly.

Flutter Hooks are a great solution for this use case. If you are already using them in your project, this is a no-brainer. 👍

But if you don't want to have flutter_hooks as an extra dependency (for example because you're writing a package), there are other ways to reduce the boilerplate code.

2. Extending the State class

You can create a AnimationControllerState subclass of State<T> to contain all the AnimationController logic:

import 'package:flutter/material.dart'; abstract class AnimationControllerState<T extends StatefulWidget> extends State<T> with SingleTickerProviderStateMixin { AnimationControllerState(this.animationDuration); final Duration animationDuration; late final AnimationController animationController; @override void initState() { super.initState(); animationController = AnimationController(vsync: this, duration: animationDuration); } @override void dispose() { animationController.dispose(); super.dispose(); } }

Here's the original example, updated to use this:

class RotatingContainer extends StatefulWidget { @override _RotatingContainerState createState() => _RotatingContainerState(Duration(seconds: 2)); } class _RotatingContainerState extends AnimationControllerState<RotatingContainer> { // add a constructor so we can pass the duration to the parent class _RotatingContainerState(Duration duration) : super(duration); // TODO: initState & build methods }

In this case we still need a StatefulWidget. But all the initialization and dispose logic is in AnimationControllerState, which is now the parent of _RotatingContainerState.

We can complete the example like this:

@override void initState() { super.initState(); // animationController is defined in the parent class, so we can use it here animationController.repeat(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: animationController, child: Container(color: Colors.red), builder: (context, child) { return Transform( alignment: Alignment.center, transform: Matrix4.rotationZ(2 * pi * animationController.value), child: child, ); }, ); }

This is a bit more code than the flutter_hooks approach, but we don't need the additional package dependency.

If you don't mind having to create a StatefulWidget for each explicit animation in your app, this is a good solution. Many use cases that need explicit animations require some state variables anyway, so I find this acceptable in practice.

useAnimationController() is just one of many hooks that you can use. You can use flutter_hooks to manage state and much more. Make sure to learn about the various kinds of hooks to make the most of this package.

Almost forgot, here's the running app from the example above:

Rotating square using AnimationController

Wrap Up

We have explored two ways to reduce AnimationController boilerplate:

  1. use useAnimationController() from the flutter_hooks package
  2. implement AnimationControllerState and reuse it as needed

Now it's your turn

Are you ready to delete a lot of unnecessary code from your Flutter apps?

  • do a project-wide search for AnimationController
  • replace all the boilerplate code with your favourite method (useAnimationController() or AnimationControllerState)
  • test everything, make a PR and see how much code you saved

You're welcome! 😀

Want more?

Support my work and fast-track your Flutter learning with my in-depth courses.