Built a timer in flutter but if one textfield input is null then the timer wont start

Viewed 201

So i have made a timer and it fully works for the most part. It has start, stop and pause buttons which works. Starting the timer works as long as i have input something in each of the three textfields (hour, minute and seconds) but as soon as one of them has no input (null) the timer wont start and spits out the error code

Exception caught by Gesture Invalid number (at character 1).

I have tried to put ?? 0 at lots of places in the code but i am quite new at this so that might be a totally dumb solution and it wont work.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_plugg/main.dart';

class TimerView extends StatefulWidget {
  @override
  _TimerViewState createState() => _TimerViewState();
}


class _TimerViewState extends State<TimerView> with TickerProviderStateMixin {
  TextEditingController _minuteController  = TextEditingController();
  TextEditingController _hourController = TextEditingController();
  TextEditingController _secondController = TextEditingController();

  AnimationController _controller;

  Animation<int> _animation;

  

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _pauseTimer() {
    _controller.stop();
  }

  void _stopTimer() {
    _controller.reset();
  }

  void _startTimer() {
    int timerInput = int.parse(_hourController.text) * 3600 +
        int.parse(_minuteController.text) * 60 +
        int.parse(_secondController.text);
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: timerInput),
    );

    _animation = StepTween(begin: timerInput, end: 0).animate(_controller)
      ..addListener(() {
        _setTimerValues();
      });
    _controller.forward();
  }

  void _setTimerValues() {
    setState(() {
      print(_animation.value);
      _hourController.text = (_animation.value ~/ 3600).toString();

      _minuteController.text = ((_animation.value % 3600) ~/ 60).toString();

      _secondController.text = (_animation.value % 60).toString();
    });
  }

  Widget _textInput({TextEditingController controller}) {
    return Container(
      width: 100,
      height: 60,
      child: TextField(
        showCursor: false,
        enableInteractiveSelection: false,
        controller: controller,
        style: TextStyle(
          fontSize: 60,
        ),
        inputFormatters: [FilteringTextInputFormatter.digitsOnly],
        keyboardType: TextInputType.number,
        textAlign: TextAlign.center,
        maxLength: 2,
        decoration: InputDecoration(
          contentPadding: EdgeInsets.zero,
          hintStyle: TextStyle(color: Colors.grey),
          counterText: "",
          hintMaxLines: 1,
          hintText: "00",
          border: InputBorder.none,
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      drawer: PersistentDrawer(currentPage: "Timer"),
      appBar: AppBar(
        title: Text("Timer"),
      ),
      body: Column(
        children: [
          SizedBox(height: 80),
          Row(
            crossAxisAlignment: CrossAxisAlignment.end,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              _textInput(
                controller: _hourController,
              ),
              Text(
                "h",
                style: TextStyle(fontSize: 24),
              ),
              
              _textInput(
                controller: _minuteController,
              ),
              Text(
                "m",
                style: TextStyle(fontSize: 24),
              ),
              _textInput(controller: _secondController),
              Text(
                "s",
                style: TextStyle(fontSize: 24),
              )
            ],
          ),
          SizedBox(
            height: 60,
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Padding(
                padding: const EdgeInsets.only(right: 15),
                child: ElevatedButton(
                  onPressed: () {
                    _controller?.isAnimating ?? false ? _pauseTimer() : _startTimer();
                    setState(() {});
                  },
                  child: _controller?.isAnimating ?? false
                      ? Text("Pause Timer")
                      : Text("Start Timer"),
                ),
              ),
              Padding(
                padding: const EdgeInsets.only(left: 15),
                child: ElevatedButton(
                  onPressed: _stopTimer,
                  child: Text("Stop Timer"),),
              )
            ],
          ),
        ],
      ),
    );
  }
}
2 Answers

?? is use for checking null in your case it's empty string so please check isNotEmpty.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_plugg/main.dart';


class TimerView extends StatefulWidget {
  @override
  _TimerViewState createState() => _TimerViewState();
}

class _TimerViewState extends State<TimerView> with TickerProviderStateMixin {
  TextEditingController _minuteController = TextEditingController();
  TextEditingController _hourController = TextEditingController();
  TextEditingController _secondController = TextEditingController();

  AnimationController _controller;

  Animation<int> _animation;

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _pauseTimer() {
    _controller.stop();
  }

  void _stopTimer() {
    _controller.reset();
  }

  void _startTimer() {
    int timerInput = int.parse(_hourController.text.isNotEmpty ? _hourController.text : '0') * 3600 +
        int.parse(_minuteController.text.isNotEmpty ? _minuteController.text : '0') * 60 +
        int.parse(_secondController.text.isNotEmpty ? _secondController.text : '0');
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: timerInput),
    );

    _animation = StepTween(begin: timerInput, end: 0).animate(_controller)
      ..addListener(() {
        _setTimerValues();
      });
    _controller.forward();
  }

  void _setTimerValues() {
    setState(() {
      print(_animation.value);
      _hourController.text = (_animation.value ~/ 3600).toString();

      _minuteController.text = ((_animation.value % 3600) ~/ 60).toString();

      _secondController.text = (_animation.value % 60).toString();
    });
  }

  Widget _textInput({TextEditingController controller}) {
    return Container(
      width: 100,
      height: 60,
      child: TextField(
        showCursor: false,
        enableInteractiveSelection: false,
        controller: controller,
        style: TextStyle(
          fontSize: 60,
        ),
        inputFormatters: [FilteringTextInputFormatter.digitsOnly],
        keyboardType: TextInputType.number,
        textAlign: TextAlign.center,
        maxLength: 2,
        decoration: InputDecoration(
          contentPadding: EdgeInsets.zero,
          hintStyle: TextStyle(color: Colors.grey),
          counterText: "",
          hintMaxLines: 1,
          hintText: "00",
          border: InputBorder.none,
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      drawer: PersistentDrawer(currentPage: "Timer"),
      appBar: AppBar(
        title: Text("Timer"),
      ),
      body: Column(
        children: [
          SizedBox(height: 80),
          Row(
            crossAxisAlignment: CrossAxisAlignment.end,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              _textInput(controller: _hourController),
              Text("h", style: TextStyle(fontSize: 24)),
              _textInput(controller: _minuteController),
              Text("m", style: TextStyle(fontSize: 24)),
              _textInput(controller: _secondController),
              Text("s", style: TextStyle(fontSize: 24))
            ],
          ),
          SizedBox(
            height: 60,
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Padding(
                padding: const EdgeInsets.only(right: 15),
                child: ElevatedButton(
                  onPressed: () {
                    _controller?.isAnimating ?? false ? _pauseTimer() : _startTimer();
                    setState(() {});
                  },
                  child: _controller?.isAnimating ?? false ? Text("Pause Timer") : Text("Start Timer"),
                ),
              ),
              Padding(
                padding: const EdgeInsets.only(left: 15),
                child: ElevatedButton(
                  onPressed: _stopTimer,
                  child: Text("Stop Timer"),
                ),
              )
            ],
          ),
        ],
      ),
    );
  }
}

you have to check of the the value is empty or null.

for that create a function _format which takes a value a string value. it checks if the value is valid and return a valid output.

String _format(value) {
    if (value != null && value.isNotEmpty) {
        return value;
    }
    return "0";
}

and call the function inside your _startTimer function.

void _startTimer() {
    int timerInput = int.parse(_format(_hourController.text)) * 3600 +
        int.parse(_format(_minuteController.text)) * 60 +
        int.parse(_format(_secondController.text));
    _controller = AnimationController(
        vsync: this,
        duration: Duration(seconds: timerInput),
    );

    _animation = StepTween(begin: timerInput, end: 0).animate(_controller)
        ..addListener(() {
            _setTimerValues();
        });
    _controller.forward();
}
Related