Irbisa · Cheatsheet

Forms, Input & Focus

TextField, TextEditingController, Form validation, FocusNode, input formatters, keyboard insets

Junior Developer · 21 questions

  1. 01

    What is a TextEditingController, and why must it be created in State rather than in build?

    A TextEditingController holds a field's current value (the text, the selection and the IME composing range) and lets you read, set and listen to it.

    class NameField extends StatefulWidget {  const NameField({super.key});  @override  State<NameField> createState() => _NameFieldState();}…
  2. 02

    What is the difference between TextField and TextFormField?

    TextFormField is a TextField wrapped in a FormField<String>: it registers with the enclosing Form and adds validator, onSaved, initialValue and autovalidateMode, showing the validator's message as the field's error.

  3. 03

    How do you validate a form in Flutter: what do Form, GlobalKey<FormState> and validator each do?

    Form groups TextFormFields, a GlobalKey<FormState> gives you access to its state, and each field's validator returns an error message or null.

    class _LoginFormState extends State<LoginForm> {  final _formKey = GlobalKey<FormState>();  String _email = '';   void _submit() {    final form = _formKey.currentState!;…
  4. 04

    What is the difference between onChanged, onSubmitted and a TextEditingController listener?

    onChanged fires when the user edits the text, onSubmitted when they press the keyboard's action key, and a controller listener fires on every change of the value, including a caret move and text you set in code.

    class _SearchState extends State<Search> {  final _query = TextEditingController();  String _last = '';   @override  void initState() {…
  5. 05

    What do labelText, hintText, helperText and errorText do in an InputDecoration, and why does the form jump when an error appears?

    All four are drawn by InputDecorator inside the field's own box.

  6. 06

    What do keyboardType, textInputAction and textCapitalization control on a TextField?

    They configure the on-screen keyboard, not the value: keyboardType picks the layout, textInputAction sets the action key, textCapitalization sets the shift state.

  7. 07

    What does Scaffold.resizeToAvoidBottomInset do, and why can the keyboard still cover your form's button?

    With resizeToAvoidBottomInset: true (the default) the Scaffold shrinks its body by the keyboard's height.

    Scaffold(  // resizeToAvoidBottomInset: true is the default; leave it on  body: SafeArea(    child: SingleChildScrollView(      padding: const EdgeInsets.all(16),      child: Column(…
  8. 08

    After validate() passes you call save(), and the model is still empty. Where does a Form actually keep its values?

    The Form stores no values.

    class _CheckoutState extends State<Checkout> {  final _formKey = GlobalKey<FormState>();  var _order = const Order();   void _submit() {    final form = _formKey.currentState!;…
  9. 09

    Find the bug: the edit-profile form shows an empty Name field even after the profile has loaded.

    class _ProfileFormState extends State<ProfileForm> {  Profile? _profile;   @override  void initState() {    super.initState();    widget.repo.load().then((p) { if (mounted) setState(() => _profile = p); });  }   @override  Widget build(BuildContext context) => TextFormField(        initialValue: _profile?.name,        decoration: const InputDecoration(labelText: 'Name'),      );}

    initialValue is read only once, when the field's FormFieldState is created.

  10. 10

    A sign-up form opens already covered in red errors. Which autovalidateMode causes that, and which would you use instead?

    That is AutovalidateMode.always: it runs every validator on every build, the first one included, so an untouched form is already full of errors.

  11. 11

    You show the server's "email already registered" error like this, and it stays after the user fixes the email. Why, and what is the fix?

    String? _serverError; // set when the API rejects the email TextFormField(  controller: _email,  autovalidateMode: AutovalidateMode.onUserInteraction,  decoration: InputDecoration(    labelText: 'Email',    errorText: _serverError,  ),  validator: (v) => (v ?? '').contains('@') ? null : 'Enter a valid email',)

    decoration.errorText is your own state, not the form's: TextFormField only overlays the validator's message when there is one, and nothing resets _serverError when the user types.

  12. 12

    What is a FocusNode, and how do you move focus to the next field and dismiss the keyboard?

    A FocusNode is a field's place in the focus tree, which lives next to the widget tree; the node with primary focus receives keyboard input.

    class _LoginState extends State<Login> {  final _password = FocusNode();   void _hideKeyboard(PointerDownEvent _) =>      FocusManager.instance.primaryFocus?.unfocus();…
  13. 13

    The keyboard shows a Next key, but pressing it leaves the cursor in the email field. What is wrong with this code?

    Column(  children: [    TextField(      controller: _email,      textInputAction: TextInputAction.next,      onEditingComplete: _saveDraft,    ),    TextField(controller: _password, obscureText: true),  ],)

    Providing onEditingComplete replaces the default action-key handling, and that default is what calls nextFocus() for TextInputAction.next.

  14. 14

    How do you build an input mask with a TextInputFormatter, and why does the caret jump to the end after every keystroke?

    A formatter's formatEditUpdate(oldValue, newValue) returns a whole TextEditingValue, so it decides where the caret goes too.

    class ThousandsFormatter extends TextInputFormatter {  @override  TextEditingValue formatEditUpdate(TextEditingValue old, TextEditingValue next) {    final digits = next.text.replaceAll(RegExp(r'\D'), '');    if (digits.isEmpty) return const TextEditingValue();…
  15. 15

    A validator must be synchronous, so how do you show a server-side check like "username is taken" while the user types?

    Keep the network call out of the validator.

    class _UsernameFieldState extends State<UsernameField> {  Timer? _debounce;  int _request = 0;  String? _taken; // what the synchronous validator hands back  void _onChanged(String value) {    _taken = null; // the old verdict was about the old text…
  16. 16

    Your date picker sits inside a Form, but validate() ignores it and save() never sees its value. How do you make a non-text widget a real form field?

    Subclass FormField<T>.

    class DateFormField extends FormField<DateTime> {  DateFormField({    super.key,    super.initialValue,    super.validator,    super.onSaved,…
  17. 17

    The OS password manager never offers to fill or save your login form, and the SMS code is typed by hand. What does autofill need in Flutter?

    Autofill is a contract with the OS: fields need autofillHints, they must sit inside one AutofillGroup, and you should finish the autofill context after a successful sign-in.

    AutofillGroup( // these fields are ONE credential  child: Column(    children: [      TextField(        controller: _email,        autofillHints: const [AutofillHints.username], // not .email…
  18. 18

    With a Bloc driving a search field, characters get lost when typing fast and the caret jumps to the end. Who should own the text?

    The TextEditingController should own the text and the caret.

    class _SearchFieldState extends State<SearchField> {  final _ctrl = TextEditingController();   @override  void dispose() {    _ctrl.dispose();…
  19. 19

    Text typed into a form disappears after rotating the phone. What was destroyed, and when do you actually need RestorableTextEditingController?

    Rotation is not the culprit: in Flutter it is just a resize, and every State survives it.

    class _SignupPageState extends State<SignupPage> with RestorationMixin {  // survives process death; a plain controller survives only rebuilds  final _email = RestorableTextEditingController();   @override  String? get restorationId => 'signup'; // null opts this subtree out…
  20. 20

    A 30-field form built with ListView.builder loses typed values and error messages once a field scrolls off screen. What causes it and what do you change?

    ListView.builder unmounts children that scroll out of its cache area, and everything they owned dies with them: the row's TextEditingController and its FormFieldState.

    class _LongFormState extends State<LongForm> {  final _formKey = GlobalKey<FormState>();  // the page owns the values; a row is just a view of one  late final Map<String, TextEditingController> _ctrls = {    for (final f in widget.fields) f.id: TextEditingController(text: f.initial),  };…
  21. 21

    This upper-casing formatter scrambles words typed with predictive keyboards and Japanese IMEs. What is TextEditingValue.composing, and what is wrong here?

    class ShoutFormatter extends TextInputFormatter {  @override  TextEditingValue formatEditUpdate(TextEditingValue old, TextEditingValue next) {    return TextEditingValue(      text: next.text.toUpperCase(),      selection: next.selection,      composing: next.composing,    );  }}

    While an IME is composing a word, the field holds provisional text marked by composing, a range the platform owns.