Flutter TextField - How to support submission on <ENTER> _and_ newline on <SHIFT-ENTER>
Asked Answered
Q

5

14

I'm working on a Flutter Web application which includes chat.

I'd like to include an ordinary input function where users can enter text and send it into the chat stream. A standard feature of chat apps these days is to send on <ENTER> and to perform a line break on <SHIFT-ENTER>, or some variation of this.

Currently I've only been able to achieve one of these functions at a time. If you set the TextField's keyboardType to TextInputType.multiline then <ENTER> and <SHIFT-ENTER> always perform a line-break, there doesn't appear to be a way to override this behavior.

If instead your TextField is TextInputType.text you can capture <ENTER> and send, but trying to capture <SHIFT-ENTER> to add a line-break has not worked. I've tried manually grabbing the key press via an onKey handler and inserting \n to the controller.text, but it appears that TextInputType.text is not meant for multiline at all, so it doesn't play well.

Just wondering if any other devs have run into this or come up with any suitable solutions. Ideally a solution would also work across android/ios. For me, I've decided to go with TextInputType.text and forgo the multiline functionality for now.

Thanks

Quondam answered 9/12, 2020 at 20:19 Comment(1)
M
17

This can be achieved by adding a FocusNode to the TextField. Place the focus node in your widget's state.

late final _focusNode = FocusNode(
  onKey: (FocusNode node, RawKeyEvent evt) {
    if (!evt.isShiftPressed && evt.logicalKey.keyLabel == 'Enter') {
      if (evt is RawKeyDownEvent) {
        _sendMessage();
      }
      return KeyEventResult.handled;
    }
    else {
      return KeyEventResult.ignored;
    }
  },
);

In your build function add the focus when creating the TextField.

TextField(
  autofocus: true,
  controller: _textController,
  focusNode: _focusNode,
)
Morpheme answered 28/9, 2021 at 9:20 Comment(3)
very working answer without any additional widgetKuhlman
greatest answerScroggins
I think it's opposite to ops request, he wants shift+enter to change line, and enter to send message, it can enter, but how baout change new line?Ito
Q
9

For what it's worth, I was able to concoct a reasonable solution that I'll post below in case anyone runs into this themselves.

I wrapped the Textfield in a keyboard listener which calls my onSend function when it see's an <Enter>. I tried this before, but I guess earlier I was missing the cast to RawKeyEventDataWeb which allowed me to capture isShiftPressed to allow for new lines on <SHFT-ENTER> without forcing a send. Unfortunately I had to add some hacky code to remove the \n that's added when pressing enter, but that's a small price to pay for functional + modern messaging.

RawKeyboardListener(
   focusNode: focusNode,
   onKey: handleKeyPress,
   child: TextField(
        controller: messageController,
        minLines: 1,
        maxLines: null,
        textInputAction: TextInputAction.done,
        style: normalTextStyle,
        keyboardType: TextInputType.multiline,
        decoration: InputDecoration(
             isDense: true,
             hintText: 'Type a message',
             hintStyle: TextStyle(
                  fontSize: 16,
                  color: Color(0xFF474749),
             ),
             border: InputBorder.none,
        ),
    ),
)

void handleKeyPress(event) {
if (event is RawKeyUpEvent && event.data is RawKeyEventDataWeb) {
  var data = event.data as RawKeyEventDataWeb;
  if (data.code == "Enter" && !event.isShiftPressed) {
    final val = messageController.value;
    final messageWithoutNewLine =
        messageController.text.substring(0, val.selection.start - 1) +
            messageController.text.substring(val.selection.start);
    messageController.value = TextEditingValue(
      text: messageWithoutNewLine,
      selection: TextSelection.fromPosition(
        TextPosition(offset: messageWithoutNewLine.length),
      ),
    );
    _onSend();
  }
}
}
Quondam answered 12/2, 2021 at 5:18 Comment(0)
A
5

This is what I am using in my TextField to support newline on enter.

class TextInputsWidget extends StatelessWidget {
  final TextEditingController chatTextFieldController = TextEditingController();
  late final _focusNode = FocusNode(
    onKey: _handleKeyPress,
  );

  KeyEventResult _handleKeyPress(FocusNode focusNode, RawKeyEvent event) {
    // handles submit on enter
    if (event.isKeyPressed(LogicalKeyboardKey.enter) && !event.isShiftPressed) {
      _sendMessage();
      // handled means that the event will not propagate
      return KeyEventResult.handled;
    }
    // ignore every other keyboard event including SHIFT+ENTER
    return KeyEventResult.ignored;
  }

  void _sendMessage() {
    if (chatTextFieldController.text.trim().isNotEmpty) {
      // Do something with your input text
      print(chatTextFieldController.text.trim());

      // bring focus back to the input field
      Future.delayed(Duration.zero, () {
        _focusNode.requestFocus();
        chatTextFieldController.clear();
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: TextField(
        keyboardType: TextInputType.multiline,
        maxLines: null,
        textInputAction: TextInputAction.newline,
        autofocus: true,
        focusNode: _focusNode,
        controller: chatTextFieldController,
        decoration: const InputDecoration(
          border: InputBorder.none,
          contentPadding: EdgeInsets.fromLTRB(8, 0, 0, 0),
          hintText: "Enter your message here",
          hintStyle: TextStyle(color: Colors.black54),
        ),
      ),
    );
  }
}

There are mainly 3 key changes

  • keyboardType: TextInputType.multiline,
  • textInputAction: TextInputAction.newline,
  • FocusNode which can listen to keyboard events
Abernathy answered 3/9, 2022 at 0:52 Comment(0)
C
1

The best way to have the Enter key be disabled for the input and instead send it when no ctrl key is pressed is through the focusNode directly on the input, this way you won't have to remove extra new lines.

class _InputTextState extends State<InputText> {
  late final _focusNode = FocusNode(onKey: handleKeyPress);
 
  @override
  Widget build(BuildContext context) {
    return TextField(
      focusNode: _focusNode,
    );
  }

  KeyEventResult handleKeyPress(FocusNode focusNode, RawKeyEvent event) {
    // handles submit on enter
    if (kIsWeb &&
        event.isKeyPressed(LogicalKeyboardKey.enter) &&
        !event.isControlPressed &&
        !event.isShiftPressed) {
      widget.onSubmit();
    // handled means that the event will not propagate
      return KeyEventResult.handled;
    }
    return KeyEventResult.ignored;
  }
}
Chandelier answered 19/10, 2021 at 16:4 Comment(0)
C
1

Here's another solution if you want to use onKeyEvent instead of onKey. Since onKey will be deprecated at one point according to the documentation:

This is a legacy API based on [RawKeyEvent] and will be deprecated in the future. Prefer [onKeyEvent] instead.

// In State class
FocusNode? textFieldNode;

@override
void initState() {
  textFieldNode = FocusNode(
    onKeyEvent: (node, event) {
      final enterPressedWithoutShift = event is KeyDownEvent &&
          event.physicalKey == PhysicalKeyboardKey.enter &&
          !HardwareKeyboard.instance.physicalKeysPressed.any(
                (key) => <PhysicalKeyboardKey>{
              PhysicalKeyboardKey.shiftLeft,
              PhysicalKeyboardKey.shiftRight,
            }.contains(key),
          );

      if (enterPressedWithoutShift) {
        // Submit stuff
        return KeyEventResult.handled;
      } else if (event is KeyRepeatEvent) {
        // Disable holding enter
        return KeyEventResult.handled;
      } else {
        return KeyEventResult.ignored;
      }
    },
  );
  super.initState();
}

In Widget:

TextField(
  focusNode: textFieldNode,
)
Cruiser answered 9/10, 2023 at 7:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.