2

I'm trying to create a countdown timer in Flutter that prints the remaining time every 5 seconds until the timer runs out, and then runs the same countdown timer for the next value in a list of values.

The following recursive function gets close, but it waits the "iteration" before going to the next value in the List even when there is less time remaining for that Timer.

import 'dart:async';

Future main() async {
  final _sequence = [21, 10, 8];
  final _iteration = 5;

  void _countdown(seq) async {
    if (seq.length > 0) {
      var duration = seq[0];
      print('Starting at $duration');
      Timer.periodic(Duration(seconds: _iteration), (timer) {
        var remaining = duration - timer.tick * _iteration;
        if (remaining >= 0) {
          print('$duration, $remaining');
        } else {
          timer.cancel();
          _countdown(seq.sublist(1));
        }
      });
    }
  }

  _countdown(_sequence);
}

Each time it runs the duration is defined by the value in the _sequence List.

Even using the CountdownTimer (instead of Timer.periodic) has the same problem with waiting too long when the value remaining is less than the iteration:

import 'package:quiver/async.dart';

main() {
  final _sequence = [21, 10, 8];
  final _iteration = 5;

  void _countdown(seq) {
    if (seq.length > 0) {
      var duration = seq[0];
      print('Starting at $duration');
      CountdownTimer countdownTimer = CountdownTimer(
        Duration(seconds: duration),
        Duration(seconds: _iteration),
      );

      var sub = countdownTimer.listen(null);
      sub.onData((timer) {
        if (timer.remaining > Duration(seconds: 0)) {
          print('$duration, ${timer.remaining.inSeconds}');
        } else {
          sub.cancel();
          _countdown(seq.sublist(1));
        }
      });
    }
  }

  _countdown(_sequence);
}

The results should look like this with a 5 second pause between lines except where noted:

Starting at 21
21, 16
21, 11
21, 6
21, 1          <-- This one should only pause 1 second before continuing
Starting at 10
10, 5
10, 0          <-- This one should immediately continue
Starting at 8
8, 3           <-- This one should only pause 3 seconds before continuing
Simpler
  • 1,317
  • 2
  • 16
  • 31
  • Can you explain when will the timer run out? what is your duration – Tree May 23 '18 at 03:53
  • Than I will make an answer, once I understand better what are you trying to achieve – Tree May 23 '18 at 03:54
  • Possible duplicate of [Flutter hold splash screen for 3 Seconds](https://stackoverflow.com/questions/50129761/flutter-hold-splash-screen-for-3-seconds) – Rémi Rousselet May 23 '18 at 08:29

4 Answers4

6

Dart/Flutter supports countdown with CountDownTimer. Below are 2 examples, one without UI and one with a simple Text at center of screen (The timeout = 10 seconds and the step = 1 seconds, you can change to whatever you want)

Example 1:

import 'package:quiver/async.dart';

void main() {
  const timeOutInSeconds = 10;
  const stepInSeconds = 2;
  int currentNumber = 0;

  CountdownTimer countDownTimer = new CountdownTimer(
      new Duration(seconds: timeOutInSeconds),
      new Duration(seconds: stepInSeconds));

  var sub = countDownTimer.listen(null);
  sub.onData((duration) {
    currentNumber += stepInSeconds;
    int countdownNumber = timeOutInSeconds - currentNumber;
    // Make it start from the timeout value
    countdownNumber += stepInSeconds;
    print('Your message here: $countdownNumber');
  });

  sub.onDone(() {
    print("I'm done");

    sub.cancel();
  });
}

Example 2:

import 'package:flutter/material.dart';
import 'package:quiver/async.dart';

void main() {
  runApp(new MaterialApp(home: new CountdownTimerPage()));
}

class CountdownTimerPage extends StatefulWidget {
  @override
  CountdownTimerPageState createState() => new CountdownTimerPageState();
}

class CountdownTimerPageState extends State<CountdownTimerPage> {
  final timeOutInSeconds = 10;
  final stepInSeconds = 2;
  int currentNumber = 0;

  CountdownTimerPageState() {
    setupCountdownTimer();
  }

  setupCountdownTimer() {
    CountdownTimer countDownTimer = new CountdownTimer(
        new Duration(seconds: timeOutInSeconds),
        new Duration(seconds: stepInSeconds));

    var sub = countDownTimer.listen(null);
    sub.onData((duration) {
      currentNumber += stepInSeconds;

      this.onTimerTick(currentNumber);
      print('Your message here: $currentNumber');
    });

    sub.onDone(() {
      print("I'm done");

      sub.cancel();
    });
  }

  void onTimerTick(int currentNumber) {
    setState(() {
      currentNumber = currentNumber;
    });
  }

  @override
  Widget build(BuildContext context) {
    int number = timeOutInSeconds - currentNumber;
    // Make it start from the timeout value
    number += stepInSeconds;
    return new Scaffold(
      body: new Center(
          child: new Text(
        "Your message here: $number",
        style: new TextStyle(color: Colors.red, fontSize: 25.0),
      )),
    );
  }
}
Phuc Tran
  • 7,555
  • 1
  • 39
  • 27
  • I like this approach. How is the "remaining" time accessed? Instead of printing the numberOfMessage, the objective is to print the remaining time. – Simpler May 23 '18 at 13:45
  • I created an example here: https://github.com/nextfunc/flutter-playground. Please check it and let me know – Phuc Tran May 23 '18 at 13:53
  • @Simpler I just updated my answer also. If it helps, you can vote and accept. :) – Phuc Tran May 23 '18 at 14:14
  • Thanks for your help, but this methods doesn't work when the increment (stepInSeconds) is something other than 1 second. Set the increment to 2. The desired output would be: 10, 8, 6, 4, 2. – Simpler May 23 '18 at 19:15
  • Haha, sorry. I updated the code one more time. @Simpler – Phuc Tran May 23 '18 at 21:26
2

Try this!

Demo

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'NonStopIO',
      theme: new ThemeData(
        primarySwatch: Colors.red,
      ),
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var _sequence = [10, 10, 8];

  @override
  void initState() {
    super.initState();
    startTimer(0);
  }

  startTimer(int index) async {
    if (index < _sequence.length) {
      print("Sequence $index - countdown :  ${_sequence[index]} ");
      new Timer.periodic(new Duration(seconds: 5), (timer) {
        if ((_sequence[index] / 5) >= timer.tick) {
          print("${_sequence[index] - timer.tick * 5}");
        } else {
          timer.cancel();
          startTimer(index + 1);
        }
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('NonStopIO'),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'Counter',
            ),
            new Text(
              '',
            ),
          ],
        ),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
Ajay Kumar
  • 15,250
  • 14
  • 54
  • 53
2
// Start the periodic timer which prints something every 5 seconds
Timer timer = new Timer.periodic(new Duration(seconds: 5), (timer) {
  print('Something');
});

// Stop the periodic timer using another timer which runs only once after specified duration
new Timer(new Duration(minutes: 1), () {
  timer.cancel();
});
Vinoth Kumar
  • 12,637
  • 5
  • 32
  • 38
2

I found another answer which satisfies your requirements i think. Try this,

import 'package:flutter/material.dart';
import 'package:quiver/async.dart';

void main() => runApp(new MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> {

  @override
  void initState() {

    new CountdownTimer(new Duration(minutes: 1), new Duration(seconds: 5)).listen((data) {
      print('Something');
      print('Remaining time: ${data.remaining.inSeconds}');
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
    );
  }
}
Vinoth Kumar
  • 12,637
  • 5
  • 32
  • 38