I have one time based application in which I am showing timer(countdown) on one tab and some articles on next tab.

Now, user can read articles freely until the timer is ticking. But when user changes tab, timer resets.

I am not rebuilding timer when state changes. It's placed as a static variable and is initialized only once.

Any help would be appreciated.

Summary of what I have done so far:

1) Created a static variable static var timer = CountdownTimer()

2) Used this to create a listtile. ListTile(...,trailing:Classname.timer)

3) Started timer. Timer works fine.

4) Changed page. Could see that timer is still running(by print statement placed in ProgressPainter).

5) No change in timer.

class CountdownTimer extends StatefulWidget{
  var timer = CountdownTimerState();

  @override
  State<StatefulWidget> createState() {
    timer = CountdownTimerState();
//  timer._controller.duration =
    return timer;
  }
}

class CountdownTimerState extends State<CountdownTimer> with TickerProviderStateMixin{
  AnimationController _controller;

  String get timeRemaining{
    Duration duration = _controller.duration * _controller.value;
    return '${duration.inMinutes} : ${(duration.inSeconds % 60).toString().padLeft(2,'0')}';
  }


  startTimer(){
    print('called');
    _controller.reverse(from: 1);
  }

  stopTimer(){
    _controller.reverse(from: 0);
  }
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this,duration: const Duration(seconds: 180));
  }
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 60,
      height: 60,
      child: Align(alignment: FractionalOffset.center,child: AspectRatio(aspectRatio: 1.0,child: Stack(
        children: <Widget>[
          Positioned.fill(child: AnimatedBuilder(animation: _controller, builder: (BuildContext context,Widget child){
            return CustomPaint(
              painter: ProgressPainter(animation: _controller, backgroundColor: Colors.purple, color: Colors.white54),
            );
          })),
          Align(
            alignment: FractionalOffset.center,
            child: AnimatedBuilder(animation: _controller, builder: (BuildContext context,Widget child){
              return Text(timeRemaining,style: TextStyle(fontSize: 20,fontWeight: FontWeight.w400,color: Colors.white),);
            })
          )
        ],
      ),),),
    );
  }
}


class ProgressPainter extends CustomPainter {
  ProgressPainter({
    @required this.animation,
    @required this.backgroundColor,
    @required this.color,
  }) : super(repaint: animation);

  /// Animation representing what we are painting
  final Animation<double> animation;

  /// The color in the background of the circle
  final Color backgroundColor;

  /// The foreground color used to indicate progress
  final Color color;

  @override
  void paint(Canvas canvas, Size size) {
    print('Timer is running');
    Paint paint = new Paint()
      ..color = backgroundColor
      ..strokeWidth = 1.0
      ..strokeCap = StrokeCap.butt
      ..style = PaintingStyle.fill;
    canvas.drawCircle(size.center(Offset.zero), size.width / 2.0, paint);
    paint.color = color;
    double progressRadians = (1.0 - animation.value) * 2 * math.pi;
    canvas.drawArc(
        Offset.zero & size, math.pi * 1.5, -progressRadians, false, paint);
  }

  @override
  bool shouldRepaint(ProgressPainter other) {
    return animation.value != other.animation.value ||
        color != other.color ||
        backgroundColor != other.backgroundColor;
  }
}

1 Answers

0
Kalpesh Kundanani On Best Solutions

Create a timer_bloc.dart file and have following Bloc in it:

final timerBloc = TimerBloc();

class TimerBloc {
  Timer _timer;

  int _start = 10;

  StreamController<int> timerStreamController = StreamController<int>.broadcast();

  Stream get timerStream {
    if(!timerStreamController.hasListener){
      startTimer();
    }
    return timerStreamController.stream;
  }


  void startTimer() {
    const oneSec = const Duration(seconds: 1);
    _timer = new Timer.periodic(oneSec, (Timer timer) {
      if (_start < 1) {
        timer.cancel();
      } else {
        _start = _start - 1;
      }
      timerStreamController.sink.add(_start);
    });
  }

  stopTimer() {
    _timer?.cancel();
  }

  dispose() {
    timerStreamController.close();
  }
}

Now, wherever in your app timer tick updates are required just use StreamBuilder Widget like this.

return StreamBuilder(
  stream: timerBloc.timerStream,
  builder: (context, snapshot) {
  return Text(snapshot.data.toString());
},);