flutterflutter-focus-node

Flutter TextField how to detect that the text field was unfocused but not submitted?


I want to use a TextField to allow the user to rename an item in the app. I would like for the new item name to be saved if the user presses the 'done' button on the keyboard but I would also like for the app to treat the user unfocusing the TextField without pressing the done button to mean the user canceled the action and the item name should be reverted to the previous name. Is there a way to call a function on unfocus only when the text was not submitted?


Solution

  • You can achieve this using the FocusNode class in Flutter.

    class RenameItemWidget extends StatefulWidget {
      const RenameItemWidget({required this.initialName, this.onSave});
    
      final String initialName;
      final void Function(String value)? onSave;
    
      @override
      State<RenameItemWidget> createState() => _RenameItemWidgetState();
    }
    
    class _RenameItemWidgetState extends State<RenameItemWidget> {
      late TextEditingController _textEditingController;
      late FocusNode _focusNode;
      late String _itemName;
    
      @override
      void initState() {
        super.initState();
    
        _textEditingController = TextEditingController(text: widget.initialName);
    
        _focusNode = FocusNode();
        _itemName = widget.initialName;
    
        _focusNode.addListener(_onFocusChange);
      }
    
      @override
      void didUpdateWidget(RenameItemWidget oldWidget) {
        super.didUpdateWidget(oldWidget);
    
        if (oldWidget.initialName != widget.initialName) {
          _textEditingController.text = widget.initialName;
          _itemName = widget.initialName;
        }
      }
    
      @override
      Widget build(BuildContext context) => TextField(
            controller: _textEditingController,
            focusNode: _focusNode,
            decoration: const InputDecoration(labelText: 'Item name'),
            onSubmitted: (_) => _saveName(),
          );
    
      void _onFocusChange() {
        if (!_focusNode.hasFocus && _textEditingController.text != _itemName) {
          setState(() => _textEditingController.text = _itemName);
        }
      }
    
      void _saveName() {
        setState(() => _itemName = _textEditingController.text);
    
        widget.onSave?.call(_itemName);
      }
    
      @override
      void dispose() {
        _textEditingController.dispose();
        _focusNode.dispose();
    
        super.dispose();
      }
    }