regexfluttertimertextfieldtextformfield

Flutter input time in hh:mm:ss format in TextFormField or TextField without using pickers


Can anyone help to figure out how to set up a TextField or TextFormField widget for time input in hh:mm:ss format?

The formatting should be displayed in the field live, so the leading zeroes will be replaced while the user is typing.

For example, if we need to enter 1 hour 59 minutes and 27 seconds, it would need to work as follows:

It is working in the similar manner on the timer in the Android's built in Clock app.

I tried to use mask_text_input_formatter package, but it does not work as I need it to. Also, I do not wish to use time pickers.

import 'package:flutter/services.dart';
import 'package:mask_text_input_formatter/mask_text_input_formatter.dart';

class TimeInputField extends StatefulWidget {
  TimeInputField({Key key}) : super(key: key);

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

class _TimeInputFieldState extends State<TimeInputField> {
  TextEditingController _txtTimeController = TextEditingController();

  final MaskTextInputFormatter timeMaskFormatter =
      MaskTextInputFormatter(mask: '##:##:##', filter: {"#": RegExp(r'[0-9]')});

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: _txtTimeController,
      keyboardType: TextInputType.numberWithOptions(decimal: false),
      decoration: InputDecoration(
        hintText: '00:00:00',
      ),
      inputFormatters: <TextInputFormatter>[
        timeMaskFormatter
        // Not sure if it can be done with RegExp or a custom class here instead
      ],
    );
  }
}

Any help is greatly appreciated!


Solution

  • I used TextInputFormatter in the text field instead of onChanged. This allows to access old value and format the new input depending on if the new character was added or deleted. It also allows to use RegExp to accept only numbers. This would be the solution:

    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    import 'dart:math' as math;
    
    class TimeInputField extends StatefulWidget {
      TimeInputField({Key key}) : super(key: key);
    
      @override
      _TimeInputFieldState createState() => _TimeInputFieldState();
    }
    
    class _TimeInputFieldState extends State<TimeInputField> {
      TextEditingController _txtTimeController = TextEditingController();
    
      @override
      Widget build(BuildContext context) {
        return TextFormField(
          controller: _txtTimeController,
          keyboardType: TextInputType.numberWithOptions(decimal: false),
          decoration: InputDecoration(
            hintText: '00:00:00',
          ),
          inputFormatters: <TextInputFormatter>[
            TimeTextInputFormatter() // This input formatter will do the job        
          ],
        );
      }
    }
    
    class TimeTextInputFormatter extends TextInputFormatter {
      RegExp _exp;
      TimeTextInputFormatter() {
        _exp = RegExp(r'^[0-9:]+$');
      }
    
      @override
      TextEditingValue formatEditUpdate(
        TextEditingValue oldValue,
        TextEditingValue newValue,
      ) {
        if (_exp.hasMatch(newValue.text)) {
          TextSelection newSelection = newValue.selection;
    
          String value = newValue.text;
          String newText;
    
          String leftChunk = '';
          String rightChunk = '';
    
          if (value.length >= 8) {
            if (value.substring(0, 7) == '00:00:0') {
              leftChunk = '00:00:';
              rightChunk = value.substring(leftChunk.length + 1, value.length);
            } else if (value.substring(0, 6) == '00:00:') {
              leftChunk = '00:0';
              rightChunk = value.substring(6, 7) + ":" + value.substring(7);
            } else if (value.substring(0, 4) == '00:0') {
              leftChunk = '00:';
              rightChunk = value.substring(4, 5) +
                  value.substring(6, 7) +
                  ":" +
                  value.substring(7);
            } else if (value.substring(0, 3) == '00:') {
              leftChunk = '0';
              rightChunk = value.substring(3, 4) +
                  ":" +
                  value.substring(4, 5) +
                  value.substring(6, 7) +
                  ":" +
                  value.substring(7, 8) +
                  value.substring(8);
            } else {
              leftChunk = '';
              rightChunk = value.substring(1, 2) +
                  value.substring(3, 4) +
                  ":" +
                  value.substring(4, 5) +
                  value.substring(6, 7) +
                  ":" +
                  value.substring(7);
            }
          } else if (value.length == 7) {
            if (value.substring(0, 7) == '00:00:0') {
              leftChunk = '';
              rightChunk = '';
            } else if (value.substring(0, 6) == '00:00:') {
              leftChunk = '00:00:0';
              rightChunk = value.substring(6, 7);
            } else if (value.substring(0, 1) == '0') {
              leftChunk = '00:';
              rightChunk = value.substring(1, 2) +
                  value.substring(3, 4) +
                  ":" +
                  value.substring(4, 5) +
                  value.substring(6, 7);
            } else {
              leftChunk = '';
              rightChunk = value.substring(1, 2) +
                  value.substring(3, 4) +
                  ":" +
                  value.substring(4, 5) +
                  value.substring(6, 7) +
                  ":" +
                  value.substring(7);
            }
          } else {
            leftChunk = '00:00:0';
            rightChunk = value;
          }
    
          if (oldValue.text.isNotEmpty && oldValue.text.substring(0, 1) != '0') {
            if (value.length > 7) {
              return oldValue;
            } else {
              leftChunk = '0';
              rightChunk = value.substring(0, 1) +
                  ":" +
                  value.substring(1, 2) +
                  value.substring(3, 4) +
                  ":" +
                  value.substring(4, 5) +
                  value.substring(6, 7);
            }
          }
    
          newText = leftChunk + rightChunk;
    
          newSelection = newValue.selection.copyWith(
            baseOffset: math.min(newText.length, newText.length),
            extentOffset: math.min(newText.length, newText.length),
          );
    
          return TextEditingValue(
            text: newText,
            selection: newSelection,
            composing: TextRange.empty,
          );
        }
        return oldValue;
      }
    }