How to "undispose" animation controller for reuse?

520

If you look at the SpinkitWave class you sent in the comments: https://github.com/jogboms/flutter_spinkit/blob/master/lib/src/wave.dart

You will see that the below is its initState method:

  @override
  void initState() {
    super.initState();
    _controller = (widget.controller ?? AnimationController(vsync: this, duration: widget.duration))..repeat();
  }

What this is doing is saying if the user has passed in an AnimationController in as a parameter, then use that AnimationController, if not create a new one and use the duration that the user has passed in. Therefore if all you need the AnimationController for is to control the SpinKitWave widget, then I would suggest you are better off just passing in the duration to it and letting it then manage the AnimationController internally. i.e create the widget using the below.

// As you no longer need to handle the state of the AnimationController you can 
// convert the ResultBox into a StatelessWidget
class ResultBox extends StatelessWidget {
  final String time;
  const ResultBox({
   this.time,
    Key key,
  }) : super(key: key);
  Widget build(BuildContext context) {
    var _resultRow;
    if (widget.time != null) {
      _resultRow = Row(mainAxisAlignment: MainAxisAlignment.center, children: [
        Text(
          widget.time,
          style: TextStyle(color: Colors.white, fontSize: 50),
        ),
        Text(
          "ms",
          style: TextStyle(color: Colors.white, fontSize: 20),
        ),
      ]);
    } else {
      // The controller argument has been removed here and the duration
      // argument added
      _resultRow = SpinKitWave(color: Colors.white, duration: const Duration(seconds: 1);
    }
    return FittedBox(
      fit: BoxFit.fitWidth,
      child: _resultRow,
    );
  }
}
Share:
520
Jonah Kornberg
Author by

Jonah Kornberg

Updated on December 26, 2022

Comments

  • Jonah Kornberg
    Jonah Kornberg less than a minute

    I'm new to Flutter and having a bit of trouble with lifecycle methods and animation controllers.

    When I press the bottom button it begins playing animation, when I press the circle in the center it stops the animation, and if I press the bottom button again I get the error:

    AnimationController.stop() called after AnimationController.dispose() AnimationController methods should not be used after calling dispose. 'package:flutter/src/animation/animation_controller.dart': Failed assertion: line 767 pos 7: '_ticker != null'

    I want the animation to begin playing again instead. Thanks for any advice!

    import 'package:flutter/cupertino.dart';
    import 'package:provider/provider.dart';
    import 'package:flutter/material.dart';
    import 'model/app_state_model.dart';
    import 'dart:developer' as developer;
    import 'package:flutter_spinkit/flutter_spinkit.dart';
    class ProductListTab extends StatefulWidget {
      @override
      _ProductListTabState createState() => _ProductListTabState();
    }
    class _ProductListTabState extends State<ProductListTab>
        with SingleTickerProviderStateMixin {
      var _isActive = true;
      var _time = " ";
      var _resultBox;
      @override
      Widget build(BuildContext context) {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
                flex: 3,
                child: Container(
                  color: Colors.lightBlue,
                  child: Padding(
                      padding: const EdgeInsets.all(30.0),
                      child: Align(
                        alignment: Alignment(0, .6),
                        child: Container(
                            width: 150,
                            height: 150,
                            child: OutlinedButton(
                                //elevation: 0.0,
                                style: OutlinedButton.styleFrom(
                                    shape: CircleBorder(),
                                    backgroundColor: Colors.blue),
                                onPressed: () {
                                  setState(() {
                                    _time = "999";
                                  });
                                },
                                child: AnimatedSwitcher(
                                  duration: Duration(seconds: 1),
                                  child: ResultBox(time: _time),
                                ))),
                      )),
                )),
            Expanded(
              flex: 2,
              child: Padding(
                padding: const EdgeInsets.all(30),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    CupertinoButton(
                      child: Text(_isActive ? 'Cancel' : 'Ready'),
                      onPressed: () {
                        setState(() {
                          _isActive = !_isActive;
                          _time = null;
                        });
                      },
                      color: Colors.amber,
                    )
                  ],
                ),
              ),
            )
          ],
        );
      }
    }
    class ResultBox extends StatefulWidget {
      final String time;
      const ResultBox({
        String this.time,
        Key key,
      }) : super(key: key);
      @override
      _ResultBoxState createState() => _ResultBoxState();
    }
    class _ResultBoxState extends State<ResultBox>
        with SingleTickerProviderStateMixin {
      AnimationController _controller;
      void initState() {
        this._controller =
            AnimationController(vsync: this, duration: Duration(seconds: 1));
        super.initState();
        _controller.reset();
      }
      Widget build(BuildContext context) {
        var _resultRow;
        if (widget.time != null) {
          _resultRow = Row(mainAxisAlignment: MainAxisAlignment.center, children: [
            Text(
              widget.time,
              style: TextStyle(color: Colors.white, fontSize: 50),
            ),
            Text(
              "ms",
              style: TextStyle(color: Colors.white, fontSize: 20),
            ),
          ]);
        } else {
          _resultRow = SpinKitWave(color: Colors.white, controller: _controller);
        }
        return FittedBox(
          fit: BoxFit.fitWidth,
          child: _resultRow,
        );
      }
      Widget dispose() {
        this._controller.dispose();
      }
    }
    
    • JayDev
      JayDev almost 2 years
      Can you add the code for SpinKitWave please? - Nowhere in your code above does it call AnimationController.stop(). I'm assuming its that component that is doing it. As a first note though your dispose method isn't the lifecycle dispose method as it has a return type of Widget. The lifecycle event has a return type of void
    • Jonah Kornberg
      Jonah Kornberg almost 2 years
      @JayDev It's not because of SpinKitWave, its because the whole _ResultBox is re-rendered when _time changes that the animation disappears. I suppose this probably is not good practice Edit: You might be right actually because there is no Stop called
    • Jonah Kornberg
      Jonah Kornberg almost 2 years
      The code for SpinKitWave can be found here: github.com/jogboms/flutter_spinkit/blob/master/lib/src/…
    • JayDev
      JayDev almost 2 years
      AnimationControllers can be abit tricky. But in this use case I don't think you really need to use it, as if you let it the SpinKitWave widget will create and manage its own. I'll show you what I mean in an answer
  • Jonah Kornberg
    Jonah Kornberg almost 2 years
    Thank you so much for reading through it and helping :) I have it working now.