Flutter Textfield

Text fields allow users to enter text into a UI. They typically appear in forms and dialogs.

To use textfields in Flutter to capture user input. It covers the different types of textfields available, how to style them, and how to retrieve the user's input. 

Additionally, it includes sample code with comments and step-by-step explanations to help you get started.

Code:

TextField(
  decoration: InputDecoration(
    labelText: 'Enter your name', // Label text for the textfield
    hintText: 'John Doe', // Placeholder text
    border: OutlineInputBorder(), // Border for the textfield
  ),
  onChanged: (text) { // Callback function that is called whenever the user types in the textfield
    print('User typed: $text');
  },
);

Step-by-Step Code Explanation:

  • Create a new TextField widget.
  • Set the decoration property to an InputDecoration widget to customize the appearance of the textfield.
  • Specify the labelText property to add a label to the textfield.
  • Specify the hintText property to add a placeholder text inside the textfield.
  • Specify the border property to add a border to the textfield.
  • Add an onChanged callback function that will be called whenever the user types in the textfield.
  • In the callback function, you can access the user's input by using the text parameter.

..

Full demo:

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

class TextFieldDemo extends StatelessWidget {
  const TextFieldDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: false,
        title: const Text('Text field'),
      ),
      body: const TextFormFieldDemo(),
    );
  }
}

class TextFormFieldDemo extends StatefulWidget {
  const TextFormFieldDemo({super.key});

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

class PersonData {
  String? name = '';
  String? phoneNumber = '';
  String? email = '';
  String password = '';
}

/*. It has several parameters including restorationId, fieldKey, hintText, labelText,
helperText, onSaved, validator, onFieldSubmitted, focusNode, and textInputAction.
The _PasswordFieldState class implements the RestorationMixin to restore the state of
the _obscureText variable.*/
class PasswordField extends StatefulWidget {
  const PasswordField({
    super.key,
    this.restorationId,
    this.fieldKey,
    this.hintText,
    this.labelText,
    this.helperText,
    this.onSaved,
    this.validator,
    this.onFieldSubmitted,
    this.focusNode,
    this.textInputAction,
  });

  final String? restorationId;
  final Key? fieldKey;
  final String? hintText;
  final String? labelText;
  final String? helperText;
  final FormFieldSetter<String>? onSaved;
  final FormFieldValidator<String>? validator;
  final ValueChanged<String>? onFieldSubmitted;
  final FocusNode? focusNode;
  final TextInputAction? textInputAction;

  @override
  State<PasswordField> createState() => _PasswordFieldState();
}

class _PasswordFieldState extends State<PasswordField> with RestorationMixin {
  final RestorableBool _obscureText = RestorableBool(true);

  @override
  String? get restorationId => widget.restorationId;

  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(_obscureText, 'obscure_text');
  }

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      key: widget.fieldKey,
      restorationId: 'password_text_field',
      obscureText: _obscureText.value,
      maxLength: 8,
      onSaved: widget.onSaved,
      validator: widget.validator,
      onFieldSubmitted: widget.onFieldSubmitted,
      decoration: InputDecoration(
        filled: true,
        hintText: widget.hintText,
        labelText: widget.labelText,
        helperText: widget.helperText,
        suffixIcon: IconButton(
          onPressed: () {
            setState(() {
              _obscureText.value = !_obscureText.value;
            });
          },
          hoverColor: Colors.transparent,
          icon: Icon(
            _obscureText.value ? Icons.visibility : Icons.visibility_off,
            semanticLabel: _obscureText.value
                ? 'Show Password'
                : 'Hide Password',
          ),
        ),
      ),
    );
  }
}

class TextFormFieldDemoState extends State<TextFormFieldDemo>
    with RestorationMixin {
  PersonData person = PersonData();

  late FocusNode _phoneNumber, _email, _lifeStory, _password, _retypePassword;

  @override
  void initState() {
    super.initState();
    _phoneNumber = FocusNode();
    _email = FocusNode();
    _lifeStory = FocusNode();
    _password = FocusNode();
    _retypePassword = FocusNode();
  }

  @override
  void dispose() {
    _phoneNumber.dispose();
    _email.dispose();
    _lifeStory.dispose();
    _password.dispose();
    _retypePassword.dispose();
    super.dispose();
  }

  void showInSnackBar(String value) {
    ScaffoldMessenger.of(context).hideCurrentSnackBar();
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      content: Text(value),
    ));
  }

  @override
  String get restorationId => 'text_field_demo';

  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(_autoValidateModeIndex, 'autovalidate_mode');
  }

  final RestorableInt _autoValidateModeIndex =
  RestorableInt(AutovalidateMode.disabled.index);

  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final GlobalKey<FormFieldState<String>> _passwordFieldKey =
  GlobalKey<FormFieldState<String>>();
  final _UsNumberTextInputFormatter _phoneNumberFormatter =
  _UsNumberTextInputFormatter();

  void _handleSubmitted() {
    final form = _formKey.currentState!;
    if (!form.validate()) {
      _autoValidateModeIndex.value =
          AutovalidateMode.always.index; // Start validating on every change.
      showInSnackBar(
        'Please fix the errors in red before submitting.',
      );
    } else {
      form.save();
      showInSnackBar('Form submitted');
    }
  }

  String? _validateName(String? value) {
    if (value == null || value.isEmpty) {
      return 'User name cannot be empty';
    }
    final nameExp = RegExp(r'^[A-Za-z ]+$');
    if (!nameExp.hasMatch(value)) {
      return 'Enter Only Alphabetical Chars';
    }
    return null;
  }

  String? _validatePhoneNumber(String? value) {
    final phoneExp = RegExp(r'^\(\d\d\d\) \d\d\d\-\d\d\d\d$');
    if (!phoneExp.hasMatch(value!)) {
      return 'Enter US Phone Number';
    }
    return null;
  }

  String? _validatePassword(String? value) {
    final passwordField = _passwordFieldKey.currentState!;
    if (passwordField.value == null || passwordField.value!.isEmpty) {
      return 'Enter Password';
    }
    if (passwordField.value != value) {
      return 'Passwords Do Not Match';
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    const sizedBoxSpace = SizedBox(height: 24);

    return Form(
      key: _formKey,
      autovalidateMode: AutovalidateMode.values[_autoValidateModeIndex.value],
      child: Scrollbar(
        child: SingleChildScrollView(
          restorationId: 'text_field_demo_scroll_view',
          padding: const EdgeInsets.symmetric(horizontal: 16),
          child: Column(
            children: [
              sizedBoxSpace,
              TextFormField(
                // A unique identifier for this field to restore its state in case the app is closed and reopened.
                restorationId: 'name_field',
                // Specifies the type of action to display on the keyboard, in this case "Next".
                textInputAction: TextInputAction.next,
                // Specifies the capitalization behavior for the text entered in this field.
                textCapitalization: TextCapitalization.words,
                // Defines the decoration of the input field, such as hint text, border, icon, etc.
                decoration: const InputDecoration(
                  filled: true,
                  icon: Icon(Icons.person),
                  hintText: 'What Do People Call You',
                  labelText: 'Your name',
                ),
                // Callback function that is called when the form is submitted and the field has been validated.
                onSaved: (value) {
                  // Update the person object with the entered name.
                  person.name = value;
                  // Move the focus to the next input field, in this case the phone number field.
                  _phoneNumber.requestFocus();
                },
                // Function to validate the input value of the field.
                validator: _validateName,
              ),
              sizedBoxSpace,
              TextFormField(
                // A unique identifier for this field to restore its state in case the app is closed and reopened.
                restorationId: 'phone_number_field',
                // Specifies the type of action to display on the keyboard, in this case "Next".
                textInputAction: TextInputAction.next,
                // Associates the focus node with this field.
                focusNode: _phoneNumber,
                // Defines the decoration of the input field, such as hint text, border, icon, etc.
                decoration: const InputDecoration(
                  filled: true,
                  icon: Icon(Icons.phone),
                  hintText: 'Where Can We Reach You',
                  labelText: 'Enter Phone Number',
                  // Adds a prefix text to the field, in this case the country code for the United States.
                  prefixText: '+1 ',
                ),
                // Specifies the type of keyboard to use, in this case a phone number keyboard.
                keyboardType: TextInputType.phone,
                // Callback function that is called when the form is submitted and the field has been validated.
                onSaved: (value) {
                  // Update the person object with the entered phone number.
                  person.phoneNumber = value;
                  // Move the focus to the next input field, in this case the email field.
                  _email.requestFocus();
                },
                // The maximum number of characters allowed for this field.
                maxLength: 14,
                // Specifies how to enforce the maximum length, in this case none to allow for formatting characters.
                maxLengthEnforcement: MaxLengthEnforcement.none,
                // Function to validate the input value of the field.
                validator: _validatePhoneNumber,
                // List of input formatters to apply to the field, in this case a digit filter and a phone number formatter.
                inputFormatters: <TextInputFormatter>[
                  FilteringTextInputFormatter.digitsOnly,
                  // Format the phone number as (###) ###-####.
                  _phoneNumberFormatter,
                ],
              ),

              sizedBoxSpace,
              TextFormField(
                // A unique identifier for this field to restore its state in case the app is closed and reopened.
                restorationId: 'email_field',
                // Specifies the type of action to display on the keyboard, in this case "Next".
                textInputAction: TextInputAction.next,
                // Associates the focus node with this field.
                focusNode: _email,
                // Defines the decoration of the input field, such as hint text, border, icon, etc.
                decoration: const InputDecoration(
                  filled: true,
                  icon: Icon(Icons.email),
                  hintText: 'Your Email Address',
                  labelText: 'Enter Email',
                ),
                // Specifies the type of keyboard to use, in this case an email address keyboard.
                keyboardType: TextInputType.emailAddress,
                // Callback function that is called when the form is submitted and the field has been validated.
                onSaved: (value) {
                  // Update the person object with the entered email.
                  person.email = value;
                  // Move the focus to the next input field, in this case the life story field.
                  _lifeStory.requestFocus();
                },
              ),

              sizedBoxSpace,
              // Disabled text field
              TextFormField(
                // Whether this field is enabled or disabled.
                enabled: false,
                // A unique identifier for this field to restore its state in case the app is closed and reopened.
                restorationId: 'disabled_email_field',
                // Specifies the type of action to display on the keyboard, in this case "Next".
                textInputAction: TextInputAction.next,
                // Defines the decoration of the input field, such as hint text, border, icon, etc.
                decoration: const InputDecoration(
                  filled: true,
                  icon: Icon(Icons.email),
                  hintText: 'Your Email Address',
                  labelText: 'Enter Email',
                ),
                // Specifies the type of keyboard to use, in this case an email address keyboard.
                keyboardType: TextInputType.emailAddress,
              ),

              sizedBoxSpace,
              TextFormField(
                // A unique identifier for this field to restore its state in case the app is closed and reopened.
                restorationId: 'life_story_field',
                // Associates the focus node with this field.
                focusNode: _lifeStory,
                // Defines the decoration of the input field, such as hint text, border, icon, etc.
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: 'Tell Us About Yourself',
                  helperText: 'Keep It Short',
                  labelText: 'Life Story',
                ),
                // Specifies the maximum number of lines that the text field can have.
                maxLines: 3,
              ),

              sizedBoxSpace,
              TextFormField(
                // A unique identifier for this field to restore its state in case the app is closed and reopened.
                restorationId: 'salary_field',
                // Specifies the type of action to display on the keyboard, in this case "Next".
                textInputAction: TextInputAction.next,
                // Specifies the type of keyboard to use, in this case a numeric keyboard.
                keyboardType: TextInputType.number,
                // Defines the decoration of the input field, such as hint text, border, icon, etc.
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: 'Salary',
                  // Adds a suffix text to the input field.
                  suffixText: 'Enter USD',
                ),
                // Specifies the maximum number of lines that the text field can have.
                maxLines: 1,
              ),

              sizedBoxSpace,
              PasswordField(
                restorationId: 'password_field',
                textInputAction: TextInputAction.next,
                focusNode: _password,
                fieldKey: _passwordFieldKey,
                helperText: 'No More Than',
                labelText: 'Password',
                onFieldSubmitted: (value) {
                  setState(() {
                    person.password = value;
                    _retypePassword.requestFocus();
                  });
                },
              ),
              sizedBoxSpace,
              TextFormField(
                // A unique identifier used to restore the state of the widget.
                restorationId: 'retype_password_field',

                // The focus node that controls this text field.
                focusNode: _retypePassword,

                // The decoration to configure the appearance of the input field.
                decoration: const InputDecoration(
                  filled: true,
                  labelText: 'Retype Password',
                ),

                // The maximum number of characters allowed in the input field.
                maxLength: 8,

                // Whether the input field should hide the text being entered.
                obscureText: true,

                // A validator function to check the validity of the entered text.
                validator: _validatePassword,

                // A callback function that's called when the user submits the input.
                onFieldSubmitted: (value) {
                  _handleSubmitted();
                },
              ),
              sizedBoxSpace,
              Center(
                child: ElevatedButton(
                  onPressed: _handleSubmitted,
                  child: const Text('Submit'),
                ),
              ),
              sizedBoxSpace,
              Text(
                'Required Field',
                style: Theme.of(context).textTheme.bodySmall,
              ),
              sizedBoxSpace,
            ],
          ),
        ),
      ),
    );
  }
}

/// Format incoming numeric text to fit the format of (###) ###-#### ##
class _UsNumberTextInputFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(
      TextEditingValue oldValue,
      TextEditingValue newValue,
      ) {
    final newTextLength = newValue.text.length;
    final newText = StringBuffer();
    var selectionIndex = newValue.selection.end;
    var usedSubstringIndex = 0;
    if (newTextLength >= 1) {
      newText.write('(');
      if (newValue.selection.end >= 1) selectionIndex++;
    }
    if (newTextLength >= 4) {
      newText.write('${newValue.text.substring(0, usedSubstringIndex = 3)}) ');
      if (newValue.selection.end >= 3) selectionIndex += 2;
    }
    if (newTextLength >= 7) {
      newText.write('${newValue.text.substring(3, usedSubstringIndex = 6)}-');
      if (newValue.selection.end >= 6) selectionIndex++;
    }
    if (newTextLength >= 11) {
      newText.write('${newValue.text.substring(6, usedSubstringIndex = 10)} ');
      if (newValue.selection.end >= 10) selectionIndex++;
    }
    // Dump the rest.
    if (newTextLength >= usedSubstringIndex) {
      newText.write(newValue.text.substring(usedSubstringIndex));
    }
    return TextEditingValue(
      text: newText.toString(),
      selection: TextSelection.collapsed(offset: selectionIndex),
    );
  }
}


To use TextFormField and PasswordField widgets to create a form that collects user data such as name, phone number, email, and password. The form performs validation on each field and displays an error message if the entered data is not valid.


The TextFormFieldDemo widget is a stateless widget that contains the Scaffold and TextFormFieldDemo widgets. The TextFormFieldDemo widget is a stateful widget that contains the form fields and handles the form submission.


The PersonData class is a simple data class that holds the user data.


The PasswordField widget is a stateful widget that provides a password input field with a toggle button to show/hide the password. It uses the RestorationMixin to preserve the state of the password visibility when the widget is rebuilt.


The TextFormFieldDemoState class is a stateful widget that contains the form fields and validation logic. It also uses the RestorationMixin to preserve the state of the form when the widget is rebuilt.


The _phoneNumberFormatter is a custom input formatter that formats the phone number input as (###) ###-####.


The _validateName, _validatePhoneNumber, and _validatePassword methods are validation functions that check if the entered data is valid. If the data is not valid, they return an error message to be displayed to the user.


The _handleSubmitted method is called when the user submits the form. It checks if the form data is valid and displays an error message if it is not. If the form data is valid, it saves the data and displays a message to the user...

Comments