AnimatedBuilder not working when trying to avoid setState call inside add listener

1,137

The issue is that _scale, which you are using as the value for the animation, is not being updated due to a lack of setState. The only place _scale is being updated is in the stateful widget's build method. The only time build is called again after the first time is when setState is called, leading to _scale not being updated like you wanted.

AnimatedBuilder is essentially the same as you would do by calling setState, but it isolates the rebuild to just the builder of itself rather than the enclosing stateful widget as a whole, which is why it's considered better for performance.

To solve this, simply move _scale = 1 - _controller.value; into the builder of AnimatedBuilder.

@override
Widget build(BuildContext context) {
  //_scale = 1 - _controller.value; NOT HERE
  return GestureDetector(
    onTap: _onTap,
    onTapDown: _onTapDown,
    onTapUp: _onTapUp,
    onTapCancel: _onTapCancel,
    child: AnimatedBuilder(
        animation: _controller,
        child: _animatedButtonUI,
        builder: (context, child) {
          _scale = 1 - _controller.value; //HERE
          return Transform.scale(
            scale: _scale,
            child: child,
          );
        },
    )
  );
}
Share:
1,137
MAA
Author by

MAA

Updated on December 22, 2022

Comments

  • MAA
    MAA over 1 year

    I had copy pasted code for an animated button that calls setState as it adds a listener to the animation controller.

    This article and SO answer:

    (Article) Flutter Laggy Animations: How Not To setState

    (SO answer) Flutter animation not working

    stated that calling setState is not recommended in this case because it will rebuild the entire UI each time the button is pressed. Makes sense!

    Now when I do use the AnimatedBuilder stops working. What am I doing wrong?

    The Code:

    class _AnimatedButtonState extends State<AnimatedButton>
        with SingleTickerProviderStateMixin {
      final style = GoogleFonts.volkhov(
        color: Colors.black,
        fontSize: 15,
      );
      double _scale;
      AnimationController _controller;
    
      @override
      void initState() {
        super.initState();
        _controller = AnimationController(
            vsync: this,
            duration: Duration(milliseconds: 100),
            lowerBound: 0.0,
            upperBound: 0.1);
        // _controller.addListener(() {
        //   setState(() {});
        // });
      }
    
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        _scale = 1 - _controller.value;
    
        return GestureDetector(
          onTap: _onTap,
          onTapDown: _onTapDown,
          onTapUp: _onTapUp,
          onTapCancel: _onTapCancel,
          child: AnimatedBuilder(           // HERE HERE
              animation: _controller,
              child: _animatedButtonUI,
              builder: (context, child) {
                return Transform.scale(
                  scale: _scale,
                  child: child,
                );
              },
          )
        );
      }
    
      _onTapUp(TapUpDetails details) => _controller.reverse();
      _onTapDown(TapDownDetails details) => _controller.forward();
      _onTapCancel() => _controller.reverse();
    
      Widget get _animatedButtonUI => Container(
        height: 100,
        width: 250,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(100),
          gradient: LinearGradient(
            begin: Alignment.bottomLeft,
            end: Alignment.bottomRight,
            colors: [Colors.green, Colors.green[400], Colors.green[500]],
          ),
        ),
        child: Center(
          child: Text(
            'Save',
            style: TextStyle(
              fontSize: 25,
              fontWeight: FontWeight.bold,
              color: Colors.black,
            ),
          ),
        ),
      );
    

    Side-note: The reason I found this tip, was because I was trying to make my button more responsive to click/touch. Quick clicks and touches on the phone, did not trigger the animation when I used the setState. Instead it took longer than a casual touch by the user. If you can help me with that as well, I'd appreciate it a lot.

    • Christopher Moore
      Christopher Moore almost 4 years
      In this case, setState does not seem like it would be very expensive as the whole widget is just the button which is the only thing to be animated. Using AnimatedBuilder provides little to no benefit.
    • MAA
      MAA almost 4 years
      I thought so as well. It's just the first I came across AnimatedBuilder so I wanted to use it.
    • Christopher Moore
      Christopher Moore almost 4 years
      It works well in other cases where the stateful widget contains more, as in the article you posted. See my answer to solve your problem.