1

I have a stopwatch and would like to have a single button that both pauses and starts it. I am struggling with the logic around this. When printing to the console, the boolean is stuck on false, and won't let me re-click the button.

stopwatch.dart:

class NewStopWatch extends StatefulWidget {

  @override
  _NewStopWatchState createState() => new _NewStopWatchState();
}

class _NewStopWatchState extends State<NewStopWatch> {

  Stopwatch watch = new Stopwatch();
  Timer timer;
  bool startStop = true;

  String elapsedTime = '';

  updateTime(Timer timer) {
    if (watch.isRunning) {
      setState(() {
        startStop = false;
        print("startstop Inside=$startStop");
        elapsedTime = transformMilliSeconds(watch.elapsedMilliseconds);
      });
    }
  }
@override
  Widget build(BuildContext context) {

    return new Container(
            padding: EdgeInsets.all(20.0),
            child: new Column(
              children: <Widget>[
                new Text(elapsedTime, style: new TextStyle(fontSize: 25.0)),
                SizedBox(height: 20.0),
                new Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    new FloatingActionButton(
                      heroTag: "btn1",
                        backgroundColor: Colors.red,
                        onPressed: startOrStop(),
                        child: new Icon(Icons.pause)),
                    SizedBox(width: 20.0),
                    new FloatingActionButton(
                      heroTag: "btn2",
                        backgroundColor: Colors.green,
                        onPressed: resetWatch,
                        child: new Icon(Icons.check)),
                  ],
                )
              ],
            ));
  }

  startOrStop() {
    print("startstop=$startStop");
    if(startStop == true) {
      startWatch();
    } else {
      stopWatch();
    }
  }

  startWatch() {
    startStop = true;
    watch.start();
    timer = new Timer.periodic(new Duration(milliseconds: 100), updateTime);
  }

  stopWatch() {
    startStop = false;
    watch.stop();
    setTime();
    startStop = true;
  }

  setTime() {
    var timeSoFar = watch.elapsedMilliseconds;
    setState(() {
      elapsedTime = transformMilliSeconds(timeSoFar);
    });
  }

  transformMilliSeconds(int milliseconds) {
    int hundreds = (milliseconds / 10).truncate();
    int seconds = (hundreds / 100).truncate();
    int minutes = (seconds / 60).truncate();
    int hours = (minutes / 60).truncate();

    String hoursStr = (hours % 60).toString().padLeft(2, '0');
    String minutesStr = (minutes % 60).toString().padLeft(2, '0');
    String secondsStr = (seconds % 60).toString().padLeft(2, '0');

    return "$hoursStr:$minutesStr:$secondsStr";
  }
}

When the first button is clicked the first time, the stopwatch should start running. When it is clicked the second time, it should pause it.

Caiz22
  • 697
  • 3
  • 12
  • 21
  • Not an answer to your solution yet, but some simple questions: why do you name your boolean `startStop`? It is unnecessarily complicated to know what the value of `true` will result in. Should it start or stop the timer? And, why do you set `startStop` multiple times in `stopWatch()`? – flarkmarup Aug 10 '19 at 08:56
  • I set it to true initially, intending for the first click of the button to call the ```startWatch()``` method. The different values in ```stopWatch()```, was for experimentation purposes – Caiz22 Aug 10 '19 at 09:55

2 Answers2

7

I solved your code by adding setState() in your start and stop watch methods, flipped the logic in said methods, and added () => before startOrStop in the onPressed callback (This was the dealbreaker). Furthermore, I removed startStop = false; from updateTimer(). I simplified your startOrStop() if statement as you do not need to write == true when checking the boolean value, you can simply write if(startStop) when evaluating booleans.

Working example:

import 'dart:async';

import 'package:flutter/material.dart';

class NewStopWatch extends StatefulWidget {

  @override
  _NewStopWatchState createState() => _NewStopWatchState();
}

class _NewStopWatchState extends State<NewStopWatch> {

  Stopwatch watch = Stopwatch();
  Timer timer;
  bool startStop = true;

  String elapsedTime = '';

  updateTime(Timer timer) {
    if (watch.isRunning) {
      setState(() {
        print("startstop Inside=$startStop");
        elapsedTime = transformMilliSeconds(watch.elapsedMilliseconds);
      });
    }
  }
  @override
  Widget build(BuildContext context) {

    return Container(
      padding: EdgeInsets.all(20.0),
      child: Column(
        children: <Widget>[
          Text(elapsedTime, style: TextStyle(fontSize: 25.0)),
          SizedBox(height: 20.0),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              FloatingActionButton(
                  heroTag: "btn1",
                  backgroundColor: Colors.red,
                  onPressed: () => startOrStop(),
                  child: Icon(Icons.pause)),
              SizedBox(width: 20.0),
              FloatingActionButton(
                  heroTag: "btn2",
                  backgroundColor: Colors.green,
                  onPressed: null, //resetWatch,
                  child: Icon(Icons.check)),
            ],
          )
        ],
      ),
    );
  }

  startOrStop() {
    if(startStop) {
      startWatch();
    } else {
      stopWatch();
    }
  }

  startWatch() {
    setState(() {
      startStop = false;
      watch.start();
      timer = Timer.periodic(Duration(milliseconds: 100), updateTime);
    });
  }

  stopWatch() {
    setState(() {
      startStop = true;
      watch.stop();
      setTime();
    });
  }

  setTime() {
    var timeSoFar = watch.elapsedMilliseconds;
    setState(() {
      elapsedTime = transformMilliSeconds(timeSoFar);
    });
  }

  transformMilliSeconds(int milliseconds) {
    int hundreds = (milliseconds / 10).truncate();
    int seconds = (hundreds / 100).truncate();
    int minutes = (seconds / 60).truncate();
    int hours = (minutes / 60).truncate();

    String hoursStr = (hours % 60).toString().padLeft(2, '0');
    String minutesStr = (minutes % 60).toString().padLeft(2, '0');
    String secondsStr = (seconds % 60).toString().padLeft(2, '0');

    return "$hoursStr:$minutesStr:$secondsStr";
  }
}

flarkmarup
  • 5,129
  • 3
  • 24
  • 25
  • Thank you! This is exactly what I was after! What is the difference between ```onPressed: () => startOrStop()``` and ```onPressed: startStop``` - don't they both call the method? – Caiz22 Aug 10 '19 at 10:27
  • You are supposed to write `onPressed: () { your code here}`, but the [fat arrow](https://stackoverflow.com/a/51869508/3266152) is shorthand syntax for that method. [Link](https://proandroiddev.com/a-deep-dive-into-floatingactionbutton-in-flutter-bf95bee11627) to an article highlighting the issue. – flarkmarup Aug 10 '19 at 10:37
  • Another [explanation](https://stackoverflow.com/a/52407983/3266152) of the syntax – flarkmarup Aug 10 '19 at 10:45
1

Thanks for the working example. That helped me solve a similar problem.

In case it helps anyone, I added a few bits to flarkmarup's code so that the Icon's more relate to the flow.

At the top I added a variable: IconData btnPlayStatus = Icons.play_arrow;

In FloatingActionButton (btn1) I replaced the Icon with the variable like: child: Icon(btnPlayStatus)),

Then added SetState to startOrStop like:

  startOrStop() {
    if(startStop) {
      setState(() {
        btnPlayStatus = Icons.pause;
      });
      startWatch();
    } else {
      setState(() {
        btnPlayStatus = Icons.play_arrow;
      });
      stopWatch();
    }
  }
BrianDev
  • 57
  • 7