0

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?

Hamed
  • 5,867
  • 4
  • 32
  • 56
Kulpas
  • 167
  • 1
  • 9

1 Answers1

2

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();
  }
}
Hamed
  • 5,867
  • 4
  • 32
  • 56
  • 1
    Thanks! That was exactly was I was looking for! One thing though: It seems that when you submit, the `focusNode` listener happens before `onSave` so even though the widget was updated with the new name, the listener executed with the old `initialName` and sets the controller text to the old name. After doing a hot reload it updates properly. I fixed it by changing `widget.initalName` to `_itemName` in the listener function though you could also use `didUpdateWidget()` and set the controllet text there. – Kulpas Mar 25 '23 at 16:27