How to start a counter timer from zero in flutter?

2,573

If you want the counter to persist after closing the app, there is no way around saving the value somewhere (like shared preferences).

Using dateTime.toIso8601String() and DateTime.parse() will make the saving and loading less ugly. To calculate the passed time you can use DateTime.now().difference(lastButtonPressed)

There should be a function to format Duration (https://api.flutter.dev/flutter/intl/DateFormat/formatDurationFrom.html) but it's not implemented yet. I found one here: Formatting a Duration like HH:mm:ss

Here is a little example:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutterfly/SharedPrefs.dart';

class TestWidget extends StatefulWidget {
  @override
  _TestWidgetState createState() => _TestWidgetState();
}

class _TestWidgetState extends State<TestWidget> {
  DateTime _lastButtonPress;
  String _pressDuration;
  Timer _ticker;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("Time since button pressed"),
          Text(_pressDuration),
          RaisedButton(
            child: Text("Press me"),
            onPressed: () {
              _lastButtonPress = DateTime.now();
              _updateTimer();
              sharedPreferences.setString("lastButtonPress",_lastButtonPress.toIso8601String());
            },
          )
        ],
      ),
    );
  }


  @override
  void initState() {
    super.initState();
    final lastPressString = sharedPreferences.getString("lastButtonPress");
    _lastButtonPress = lastPressString!=null ? DateTime.parse(lastPressString) : DateTime.now();
    _updateTimer();
    _ticker = Timer.periodic(Duration(seconds:1),(_)=>_updateTimer());
  }


  @override
  void dispose() {
    _ticker.cancel();
    super.dispose();
  }



  void _updateTimer() {
    final duration = DateTime.now().difference(_lastButtonPress);
    final newDuration = _formatDuration(duration);
    setState(() {
      _pressDuration = newDuration;
    });
  }

  String _formatDuration(Duration duration) {
    String twoDigits(int n) {
      if (n >= 10) return "$n";
      return "0$n";
    }

    String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
    String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
    return "${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds";
  }
}

For simplicity i initialized shared preferences in the main method in global scope. Example

Share:
2,573
Nitneuq
Author by

Nitneuq

Updated on December 11, 2022

Comments

  • Nitneuq
    Nitneuq over 1 year

    I tried to display a timer ( format dd HH mm ss ) to count the time between each actions (button action for exemple ). And need to work even the app is close and rebuild. Currently I load a string date I saved with sharedpreference when I pressed a button who represent the time when I pressed the button. I format all time decimal to compare and display time difference. I think it's not beautifull, not what I search, and I don't succeded to display clock in the format (dd HH mm ss). If someone have a more simple exemple :)

    load_records_pulsion() async{
    
        /*var current_time = DateFormat('yyyy-MM-dd HH').format(DateTime.now());*/
        SharedPreferences prefs = await SharedPreferences.getInstance();
        setState(() {
          RegExp regExp = new RegExp(            //Here is the regex time pulsion
            r"([12]\d{3})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])",
          );
          last_pulsion = (prefs.getString('last_pulsion'))??0;
    
    
          var match = regExp.firstMatch("$last_pulsion");
          annees = match.group(1);   // hh:mm
          mois = match.group(2);   // hh:mm
          jours = match.group(3);   // hh:mm
    
          int annees_int = int.tryParse("$annees") ;
          int mois_int = int.tryParse("$mois") ;
          int jours_int = int.tryParse("$jours") ;
          print("$annees_int");
          print("$mois_int");
          print("$jours_int");
    
    
          final last_pulsion2 = DateTime(annees_int, mois_int, jours_int);
          final date_now = DateTime.now();
          difference_pulsion = date_now.difference(last_pulsion2).inDays;
    
          if(difference_pulsion==0){
            difference_pulsion ="";
            prefix_pulsion ="Aujourd'hui";
          }else{
            prefix_pulsion ="jours";
          }
    
        });
      }
    

    Also I tried this code, it's OK the timer is increase when I call the function, but I don't want datenow, I just need to start with zero time

    int _start = 0;
      void startTimer() {
        _start=0;
        var now = new DateTime.now();
        const oneSec = const Duration(seconds: 1);
        _timer = new Timer.periodic(
            oneSec,
                (Timer timer) => setState(() {
            {
              chrono = now.add(new Duration(seconds: _start));
                _start = _start + 1;
              }
            }));
      }
    

    Edit: I found this solution but have some lifecycle error, and if I close the app, I loose the timer.

     Stopwatch stopwatch = new Stopwatch();
    
    
      void rightButtonPressed() {
        setState(() {
          if (stopwatch.isRunning) {
            stopwatch.reset();
          } else {
            stopwatch.reset();
            stopwatch.start();
          }
        });
      }
     @override
      Widget build(BuildContext context)
      {
    ...
         new Container(height: 80.0,
                child: new Center(
                  child: new TimerText(stopwatch: stopwatch),
                )),
    
    ...
    class TimerText extends StatefulWidget {
      TimerText({this.stopwatch});
      final Stopwatch stopwatch;
    
      TimerTextState createState() => new TimerTextState(stopwatch: stopwatch);
    }
    
    class TimerTextState extends State<TimerText> {
    
      Timer timer;
      final Stopwatch stopwatch;
    
      TimerTextState({this.stopwatch}) {
        timer = new Timer.periodic(new Duration(milliseconds: 30), callback);
      }
    
      void callback(Timer timer) {
        if (stopwatch.isRunning) {
          setState(() {
    
          });
        }
      }
    
      @override
      Widget build(BuildContext context) {
        final TextStyle timerTextStyle = const TextStyle(fontSize: 50.0, fontFamily: "Open Sans");
        String formattedTime = TimerTextFormatter.format(stopwatch.elapsedMilliseconds);
        return new Text(formattedTime, style: timerTextStyle);
      }
    }
    
    class TimerTextFormatter {
      static String format(int milliseconds) {
    
        int seconds = (milliseconds / 1000).truncate();
        int minutes = (seconds / 60).truncate();
        int hours = (minutes / 60).truncate();
        int days = (hours / 24).truncate();
        String minutesStr = (minutes % 60).toString().padLeft(2, '0');
        String secondsStr = (seconds % 60).toString().padLeft(2, '0');
        String hoursStr = (hours % 60).toString().padLeft(2, '0');
        String daysStr = (days % 24).toString().padLeft(2, '0');
        return "$daysStr:$hoursStr:$minutesStr:$secondsStr";
      }
    }