8

I have a TextField like this. The additional code is necessary to show that in different situations, I do various focus manipulation.

  final node = FocusScope.of(context);
  Function cleanInput = () => {controller.text = controller.text.trim()};
  Function onEditingComplete;
  Function onSubmitted
  TextInputAction textInputAction;
  if (!isLast) {
    onEditingComplete = () => {
          cleanInput(),
          node.nextFocus(),
        };
    onSubmitted = (_) => {cleanInput()};
    textInputAction = TextInputAction.next;
  } else {
    onEditingComplete = () => {
          cleanInput(),
        };
    onSubmitted = (_) => {
          cleanInput(),
          node.unfocus(),
        };
    textInputAction = TextInputAction.done;
  }
  Widget textInput = TextField(
      textInputAction: textInputAction,
      controller: controller,
      onEditingComplete: onEditingComplete,
      onSubmitted: onSubmitted,
      keyboardType: textInputType,
      ));

As you can see, I have functions I want to run onEditingComplete. However, this only gets called when I press the Next or Done buttons on my keyboard (or the Enter key in an emulator). If I change focus by tapping on a different field, this function does not get called.

I have tried using a Focus or FocusNode to help with this, but when I do so, the onEditingComplete function itself no longer works.

How can I get the desired effect here while everything plays nicely together?

Daniel Porteous
  • 5,536
  • 3
  • 25
  • 44

3 Answers3

10

Focus widget

Wrapping fields in a Focus widget might do the trick.

The Focus widget will capture focus loss events for children. With its onFocusChange argument you can call arbitrary functions.

Meanwhile, the onEditingComplete argument of TextField is unaffected and will still be called on the software keyboard "Next/Done" keypress.

This should handle field focus loss for both "Next/Done" keypress and user tapping on another field.

import 'package:flutter/material.dart';

class TextFieldFocusPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          padding: EdgeInsets.symmetric(horizontal: 20),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              // ↓ Add this wrapper
              Focus(
                child: TextField(
                  autofocus: true,
                  decoration: InputDecoration(
                    labelText: 'Name'
                  ),
                  textInputAction: TextInputAction.next,
                  // ↓ Handle focus change on Next / Done soft keyboard keys
                  onEditingComplete: () {
                    print('Name editing complete');
                    FocusScope.of(context).nextFocus();
                  },
                ),
                canRequestFocus: false,
                // ↓ Focus widget handler e.g. user taps elsewhere
                onFocusChange: (hasFocus) {
                  hasFocus ? print('Name GAINED focus') : print('Name LOST focus');
                },
              ),
              TextField(
                decoration: InputDecoration(
                  labelText: 'Password'
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
Baker
  • 24,730
  • 11
  • 100
  • 106
  • 1
    Bingo, this did the trick! For future readers, to get the correct effect, I made sure not to call any node focus changing functions in onFocusChange, like this: `Function onFocusChange = (hasFocus) => {hasFocus ? null : cleanInput()};`. You only need those calls in onEditingComplete, like you can see in my original post. As you can see, I only call the function to clean the input here, and it works like a charm! Thanks! – Daniel Porteous Jan 18 '21 at 19:25
  • 1
    Oh also, I had to use canRequestFocus: false, otherwise the focus node interferes with changing focus in onEditingComplete. This is very important! Could you update your answer Baker to include this? – Daniel Porteous Jan 18 '21 at 19:30
  • 1
    @DanielPorteous added the `canRequestfocus:false` for you – Baker Jan 18 '21 at 21:50
1

Please add a focus node to your textfield and add a listener to your focus node to trigger when it unfocuses

   final node = FocusScope.of(context);
   node.addListener(_handleFocusChange);


  void _handleFocusChange() {
    if (node.hasFocus != _focused) {
      setState(() {
        _focused = node.hasFocus;
      });
    }
  }

    Widget textInput = TextField(
      //you missed this line of code
      focusNode: node,
      textInputAction: textInputAction,
      controller: controller,
      onEditingComplete: onEditingComplete,
      onSubmitted: onSubmitted,
      keyboardType: textInputType,
      ));

And also you can validete automatically by adding autoValidate to your code like below:

Widget textInput = TextField(
 //add this line of code to auto validate
  autoValidate: true,
  textInputAction: textInputAction,
  controller: controller,
  onEditingComplete: onEditingComplete,
  onSubmitted: onSubmitted,
  keyboardType: textInputType,
  ));
Gourango Sutradhar
  • 1,461
  • 10
  • 16
  • The first code block does not work, I get this error: `Tried to make a child into a parent of itself`. As for the second part: There is no `autovalidate` argument to `TextField`. I notice that if I wrap the `TextField` in a `Form`, I can use `autovalidate` (or better, the non deprecated `autovalidateMode`), but that doesn't actually solve my problem. – Daniel Porteous Jan 12 '21 at 17:36
  • ```Tried to make a child into a parent of itself``` I think this is a hierarchy problem, this block of code should work. Can you please provide the full code of this Widget/Page? – Gourango Sutradhar Jan 13 '21 at 04:58
  • The given code is all there is. The next step from here is just to return the widget. I then use this function to create n instances of this widget in a ListView. – Daniel Porteous Jan 13 '21 at 17:35
0
FocusNode _node;
bool _focused = false;

@override
void initState() {
      super.initState();
       _node.addListener(_handleFocusChange);
    }

  void _handleFocusChange() {
        if (_node.hasFocus != _focused) {
          setState(() {
           _focused = _node.hasFocus;
        });
      }
      }
  @override
   void dispose() {
     _node.removeListener(_handleFocusChange);
    _node.dispose();
  super.dispose();
 }

    TextFormField(
focusNode: _node)
  • If I set a focus node on my field, my other functions such as onEditingComplete don't work, it does not switch focus to the next element. – Daniel Porteous Jan 13 '21 at 17:33