How to discard Backspace key press event in Textfield in Flutter (Web)? - flutter

I am creating a Flutter web app. The requirement is that under certain condition I have to block user from pressing backspace (or delete key) while typing some text in Textfield. I have tried RawKeyboardListener and TextEditingController. They help me listening the keyboard events, but I am unable to modify or discard keyboard events.
RawKeyboardListener(
focusNode: focusNode,
onKey: handleOnKeyEvent,
child: TextField(
textAlignVertical: TextAlignVertical.top,
textInputAction: TextInputAction.newline,
controller: textEditingController,
decoration: InputDecoration(
border: InputBorder.none, hintText: 'Start typing here'),
keyboardType: TextInputType.multiline,
minLines: null,
maxLines: null,
expands: true,
onChanged: (value) {
//print('text = $value');
handleUserInput(value);
},
),
),

How about using a TextController to check if text has been backspaced?
In this way the user can still type new characters but cannot delete them.
late String initialText;
late TextEditingController _textController;
void initState() {
initialText = "MyText";
_textController = TextEditingController(text: initialText);
super.initState();
}
and inside the build():
TextFormField(
controller: _textController,
onChanged: (input) {
if (_textController.text.length < initialText.length) {
_textController.text = initialText;
} else {
setState(() {
initialText = _textController.text;
});
}
},
)

For me, the TextField.onChange() and TextController callback were not worked.
After a bit of searching, I got this GitHub answer by Sonu-simon
https://github.com/flutter/flutter/issues/14809#issuecomment-907617733
Basically, It says to wrap TextField with the RawKeyboardListener widget. It has an onKey callback with RawKeyEvent passed in as a param.
I used it and it worked for me.
if (value.data.logicalKey.keyLabel == "Backspace") {
// stuff here...
}
In answer, Sonu-Simon explained to use the keyId property(to verify the backspace pressed), but it is different for each platform I think. So I have used the keyLabel property.

Related

onEditingComplete is not called after unfocus

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?
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'
),
),
],
),
),
),
);
}
}
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,
));
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)

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

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
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();
}
}
}
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,
)
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
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;
}
}

How to add the typed value in textfield as in update

I am creating a firebase CRUD flutter app. I want to show the typed value in textfield as in update. So that users can update the value by erasing the typed value.
//define
var _fNameController = TextEditingController();
//set value which one you set
#override
void initState() {
_fNameController.text = 'jayesh';
super.initState();
}
// textfield
TextFormField(
keyboardType: TextInputType.text,
controller: _fNameController,
textInputAction: TextInputAction.next,
textCapitalization: TextCapitalization.words,
decoration: InputDecoration(
hintText: 'First Name',
//border: OutlineInputBorder(),
),
validator: validator.validatefName,
),

How to check whether TextFormField is focused or not

I have a TextFormField with focusNode:
TextFormField(
key: Key('login-username-field-key'),
controller: loginTextController,
textInputAction: TextInputAction.next,
keyboardType: TextInputType.text,
onFieldSubmitted: (term){
_changeFocusField(context, _loginFocus, _passwordFocus);
},
focusNode: _loginFocus,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).loginFieldUsername
),
),
And then:
// Check that text field initially is not focused
final TextFormField textField = tester.widget(find.byKey(Key('login-username-field-key')));
expect(textField.focusNode.hasFocus, isFalse);
But from the docs I saw that TextFormField doesn't have 'focusNode' property (like TextField.focusNode.hasFocus).
So how to check that behavior?
PS I mean we can use FocusNode listeners, but I don't want to do that just for testing purposes. It should be really simply field.focusNode like for TextField.
You should add listener, where you can check state of your textFormField.
The method hasFocus return true or false.
#override
void initState() {
super.initState();
_loginFocus.addListener(_onFocusChange);
}
void _onFocusChange(){
debugPrint("Focus: "+_focus.hasFocus.toString());
}

Changing focus from one text field to the next in Flutter

I have two textFormField widgets. Once the user has completed the first text field I would like to focus on the next textField. Is there a way to do this in Flutter? Currently, the done button just closes the keyboard. I was guessing the focusNode class might be the answer to this but not really sure how that works does anyone have any good examples of focusNode class? Thanks in advance.
Yes, FocusNode and the onFieldSubmitted from a TextFormField are probably the way to go.
FocusScope.of(context).requestFocus(focusNode);
Here is an example that may help:
FocusNode textSecondFocusNode = new FocusNode();
TextFormField textFirst = new TextFormField(
onFieldSubmitted: (String value) {
FocusScope.of(context).requestFocus(textSecondFocusNode);
},
);
TextFormField textSecond = new TextFormField(
focusNode: textSecondFocusNode,
);
// render textFirst and textSecond where you want
You may also want to trigger FocusScope.of() from a button rather than onFieldSubmitted, but hopefully the above example gives you enough context to construct an appropriate solution for your use case.
Screenshot:
No need to use FocusNode
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
TextField(
decoration: InputDecoration(hintText: 'First Name'),
textInputAction: TextInputAction.next,
onEditingComplete: () => FocusScope.of(context).nextFocus(),
),
TextField(
decoration: InputDecoration(hintText: 'Last Name'),
textInputAction: TextInputAction.done,
onSubmitted: (_) => FocusScope.of(context).unfocus(),
),
],
),
);
}
There's a similar method like in Android.
Add
textInputAction
parameter to the TextFormField Widget, then add the property as;
TextInputAction.next
This is how I did it:
var _focusNodes = List.generate(6, (index) => FocusNode()));
And in the TextFormField:
TextFormField(
focusNode: _focusNodes[i],
maxLines: 1,
textInputAction: TextInputAction.next,
onChanged: (text) {
if (i < _controllers.length) {
if (text.isEmpty)
_focusNodes[i - 1].requestFocus();
else
_focusNodes[i + 1].requestFocus();
}
},
),