Get Selection of TextFormField in Flutter - flutter

My goal is to get the selected text of the TextFormField, but each time the button is pressed, the TextFormField loses its focus, and the print in the console shows only a selection between -1 and -1.
It did work a few weeks ago, did the behavior change in the latest release? I am on the stable Flutter channel. (Flutter Channel stable, 2.5.0)
This is my test example:
import 'package:flutter/material.dart';
class Play extends StatefulWidget {
const Play({Key? key}) : super(key: key);
#override
_PlayState createState() => _PlayState();
}
class _PlayState extends State<Play> {
TextEditingController _controller = TextEditingController();
FocusNode _node = FocusNode();
void _onPressed() {
TextSelection selection = _controller.selection;
print("selection.start: ${selection.start}");
print("selection.end: ${selection.end}");
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
focusNode: _node,
controller: _controller,
),
ElevatedButton(
onPressed: _onPressed,
child: Text("do something"),
),
],
),
);
}
}

Since it works fine on mobile, I'd say it's a bug in Flutter, but fear not! Welcome to the world of workarounds and temporary solutionsTM; replace your entire _onPressed method by this:
int start = -1;
int end = -1;
#override
void initState() {
super.initState();
_controller.addListener(() async {
TextSelection selection = _controller.selection;
if (selection.start > -1) start = selection.start;
if (selection.end > -1) end = selection.end;
});
}
void _onPressed() {
print(start);
print(end);
}
Let me know if something is not very clear and I'll be happy to edit the answer with further explanations.
DartPad example
The Flutter community would love you to file a bug report (if it doesn't exist already)! :D

Related

All keys are unresponsive when using HardwareKeyboard in Flutter

I have the android device you see in the picture. This device has an orange button on the back. I want to trigger a certain function when this button is pressed. I just try to use the HardwareKeyboard in Flutter as below and get a solution, but I don't get a response.enter image description here
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
#override
void initState() {
HardwareKeyboard.instance.addHandler(key_handler);
super.initState();
}
bool key_handler(event) {
print(event.physicalKey.debugName);
// In this part, when a button is clicked, it should return certain responses to me.
if (event is KeyDownEvent) {
if (event.physicalKey.usbHidUsage == PhysicalKeyboardKey.audioVolumeDown.usbHidUsage) {
_incrementCounter();
} else if (event.physicalKey.usbHidUsage ==
PhysicalKeyboardKey.audioVolumeUp.usbHidUsage) {
_decrementCounter();
}
}
return true;
}
void _incrementCounter() {
setState(() {
_counter++;
});
}
void _decrementCounter() {
setState(() {
_counter--;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Hit a Volume UP/Down key:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
TextField(
decoration: InputDecoration(
hintText: "Once touch here and show a software key."),
),
],
),
),
);
}
}
The code above increments or decrements the count by 1 unit when the volume up and down buttons are pressed. It works.
The button I need is known as f4 on the back. But when I click on this button I don't get any response.
I think Flutter has this problem because the codes of this part are not complete. Because there is no response from any button except the volume up and down buttons, the back button.
There is no Plugin that I can find to solve this issue. There used to be a plugin called hardware_buttons. Currently unavailable. Because it was written 3 years ago and the update has not arrived. I think since Flutter added HardwareKeyboard to itself, it was no longer needed.
Please write all the guesses you know and guess in the comment section.

how to focus the next text field automatically after user input 1 character in flutter

I have 4 textFormField widgets. Once the user has completed the first text field I would like to focus on the next textField automatically. Is there a way to do this in Flutter? anyone please share , thank in advance :)
This can be done in Flutter in different ways, and I'll try to share the simplest one of them. Before getting into the answer, it's worth mentioning the following issue:
Detect when delete is typed into a TextField #14809
In Flutter, backspace does not send any event when the TextField is empty (i.e. TextField.onChanged won't be called). In your case, if the user is at third field and they press backspace to return to the second field, there's no way to capture that key press without some workaround that were discussed in the linked issue. In short, you'll need to add a zero-width space character (it doesn't get rendered but is present in the String) to detect backspace events.
I mentioned this issue because I'm sharing an example that utilize the zero-width space character (zwsp for short).
In the following example, I simply created two lists that contains:
FocusNode for each field
TextEditingController for each field.
Based on the index, you can bring the focus to a specific field by calling:
FocusNode.requestFocus().
Similarly, you can remove the focus by calling FocusNode.unfocus or you can remove any focus from anywhere by calling: FocusScope.of(context).unfocus(); (in the example below, it's used after the last character is inserted to hide the keyboard).
That being said, here's a full example that you can copy and paste to try it out:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(child: CodeField()),
);
}
}
/// zero-width space character
///
/// this character can be added to a string to detect backspace.
/// The value, from its name, has a zero-width so it's not rendered
/// in the screen but it'll be present in the String.
///
/// The main reason this value is used because in Flutter mobile,
/// backspace is not detected when there's nothing to delete.
const zwsp = '\u200b';
// the selection is at offset 1 so any character is inserted after it.
const zwspEditingValue = TextEditingValue(text: zwsp, selection: TextSelection(baseOffset: 1, extentOffset: 1));
class CodeField extends StatefulWidget {
const CodeField({Key key}) : super(key: key);
#override
_CodeFieldState createState() => _CodeFieldState();
}
class _CodeFieldState extends State<CodeField> {
List<String> code = ['', '', '', ''];
List<TextEditingController> controllers;
List<FocusNode> focusNodes;
#override
void initState() {
// TODO: implement initState
super.initState();
focusNodes = List.generate(4, (index) => FocusNode());
controllers = List.generate(4, (index) {
final ctrl = TextEditingController();
ctrl.value = zwspEditingValue;
return ctrl;
});
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
// give the focus to the first node.
focusNodes[0].requestFocus();
});
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
focusNodes.forEach((focusNode) {
focusNode.dispose();
});
controllers.forEach((controller) {
controller.dispose();
});
}
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
4,
(index) {
return Container(
width: 20,
height: 20,
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
child: TextField(
controller: controllers[index],
focusNode: focusNodes[index],
maxLength: 2,
keyboardType: TextInputType.number,
decoration: InputDecoration(
counterText: "",
),
onChanged: (value) {
if (value.length > 1) {
// this is a new character event
if (index + 1 == focusNodes.length) {
// do something after the last character was inserted
FocusScope.of(context).unfocus();
} else {
// move to the next field
focusNodes[index + 1].requestFocus();
}
} else {
// this is backspace event
// reset the controller
controllers[index].value = zwspEditingValue;
if (index == 0) {
// do something if backspace was pressed at the first field
} else {
// go back to previous field
controllers[index - 1].value = zwspEditingValue;
focusNodes[index - 1].requestFocus();
}
}
// make sure to remove the zwsp character
code[index] = value.replaceAll(zwsp, '');
print('current code = $code');
},
),
);
},
),
);
}
}
You may want to use a FocusNode on each of your TextFormField, this way, once your user has enter text in the TextFormField, you can use in the callback onChanged of the TextFormField call myNextTextFieldFocusNode.requestFocus()
FocusNode textFieldOne = FocusNode();
FocusNode textFieldTwo = FocusNode();
// ...
TextFormField(
onChanged: (_) {
textFieldTwo.requestFocus();
},
focusNode: textFieldOne,
controller: textController,
)
You can use onChanged and nodefocus properties. When onchanged called refer to next textfield.
init a focus node ;
late FocusNode myFocusNode;
#override
void initState() {
super.initState();
myFocusNode = FocusNode();
}
#override
void dispose() {
// Clean up the focus node when the Form is disposed.
myFocusNode.dispose();
super.dispose();
}
onChanged property;
TextField(
focusNode: myFocusNode1,
onChanged: (text) {
myFocusNode2.requestFocus();// I could not remember the correct usage please check
},
),

Show TextField value on same screen on button click

This is my screen with TextField and Button. When someone clicks on show button, I want it to show the name below the button as shown in below picture.
Code below:
class Demo extends StatefulWidget {
#override
_DemoState createState() => _DemoState();
}
class _DemoState extends State<Demo> {
final name = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Row(
children: [
Text(
'Name'
),
TextField(
controller: name,
)
],
),
RaisedButton(
onPressed: (){
},
child: Text('Show'),
)
],
),
),
);
}
}
This can be a basic example for your question. The UI is not exactly what you've shown above
class Question extends StatefulWidget {
Question({Key key}) : super(key: key);
#override
_QuestionState createState() => _QuestionState();
}
class _QuestionState extends State<Question> {
String text = '';
bool shouldDisplay = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
Center(
child: TextField(
onChanged: (value) {
setState(() {
text = value;
});
},
),
),
FlatButton(onPressed: () {
setState(() {
shouldDisplay = !shouldDisplay;
});
}, child: Text('Submit')),
shouldDisplay ? Text(text) : Spacer()
],
);
}
}
Hope this helps.
Initialize two variables. One for a TextEditingController and one for the text value.
TextEditingController controller = TextEditingController();
String display = '';
Give your TextField a controller.
TextField(controller:controller);
In your button, set onPressed to change display text to the controller text.
FlatButton(
child: Text("Show"),
onPressed()=> setState((){display = controller.text;});
),
Then where you want to show the text, set the text string to display.
Text(display);
I would advice you to learn the basics of flutter first before asking these kinds of questions. This can be simply achieved through using TextEditingController and setState(). Simply define a controller for your TextField and then call setState() when your button is pressed. Note that you have to be on a StatefulWidget since calling setState() rebuilds the UI.
Create a TextEditingController and string above the #override Widget build:
String displayName="";
final myController = TextEditingController();
Create a TextField and add assign the controller to it:
TextField(
controller: myController,
);
Call setState() on button pressed:
MaterialButton(
child: Text("Show"),
onPressed: (){
setState(() {
displayName=myController.text;
});
})
Display it using a Text widget:
Text(displayName);
Good Luck!
You can find out how to use TextEditingController here: https://flutter.dev/docs/cookbook/forms/retrieve-input
More about widgets here: https://www.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG

Flutter-web TextFormField issue

usually I can disable/grey-out a button until a TextFormField meets certain parameters in flutter by something like this:
TextFormField(
controller: _controller
value: (value)
)
SubmitButton(
onPressed: _controller.text.isNotEmpty ? _submit : null;
)
But when compiled as a website the Button seems no longer aware of the controller value...
I have tried targeting in several different ways, e.g. _controller.value.text.isEmpty and _controller.text.isEmpty...
I'm guessing I'm missing something or this method just isn't possible for web ... Is there any other way to get the same result?
To be honest, your code shouldn't work in flutter mobile either, but may be works because of screen keyboard causes widget rebuild when showing or hiding.
To fix this issue we have to use stateful widget with state variable like canSubmit and update it in textField's listener onChange with setState method. Then every time the text changes, our stateful widget will update the submit button..
class Page extends StatefulWidget {
#override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> {
bool canSubmit;
#override
void initState() {
canSubmit = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
onChanged: (value) {
setState(() {
canSubmit = value.isNotEmpty;
});
},
),
RaisedButton(
onPressed: canSubmit ? _submit : null,
child: Text('Submit'),
)
],
),
),
);
}
void _submit() {
print('Submitted');
}
}

How do you change the value inside of a textfield flutter?

I have a TextEditingController where if a user clicks a button it fills in with information. I can't seem to figure out how to change the text inside of a Textfield or TextFormField. Is there a solution?
Simply change the text property
TextField(
controller: txt,
),
RaisedButton(onPressed: () {
txt.text = "My Stringt";
}),
while txt is just a TextEditingController
var txt = TextEditingController();
The problem with just setting
_controller.text = "New value";
is that the cursor will be repositioned to the beginning (in material's TextField). Using
_controller.text = "Hello";
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _controller.text.length),
);
setState(() {});
is not efficient since it rebuilds the widget more than it's necessary (when setting the text property and when calling setState).
--
I believe the best way is to combine everything into one simple command:
final _newValue = "New value";
_controller.value = TextEditingValue(
text: _newValue,
selection: TextSelection.fromPosition(
TextPosition(offset: _newValue.length),
),
);
It works properly for both Material and Cupertino Textfields.
Screenshot:
Create a TextEditingController:
final TextEditingController _controller = TextEditingController();
Assign it to your TextField or TextFormField:
TextField(controller: _controller)
To update the text using a button at the cursor position (imagine there is already text in the textfield) :
ElevatedButton(
onPressed: () {
const newText = 'Hello World';
final updatedText = _controller.text + newText;
_controller.value = _controller.value.copyWith(
text: updatedText,
selection: TextSelection.collapsed(offset: updatedText.length),
);
},
)
You can use the text editing controller to manipulate the value inside a textfield.
var textController = new TextEditingController();
Now, create a new textfield and set textController as the controller for the textfield as shown below.
new TextField(controller: textController)
Now, create a RaisedButton anywhere in your code and set the desired text in the onPressed method of the RaisedButton.
new RaisedButton(
onPressed: () {
textController.text = "New text";
}
),
_mytexteditingcontroller.value = new TextEditingController.fromValue(new TextEditingValue(text: "My String")).value;
This seems to work if anyone has a better way please feel free to let me know.
First Thing
TextEditingController MyController= new TextEditingController();
Then add it to init State or in any SetState
MyController.value = TextEditingValue(text: "ANY TEXT");
The issue does not appear if you use the StatefulWidget with _controller as a member. Sounds weird but moving from stateless to stateful worked fine (that's because the widget is redrawn on each input to text editing controller which does not preserve state) E.g.:
Stateful: (working)
class MyWidget extends StatefulWidget {
const MyWidget(
{Key? key})
: super(key: key);
#override
_MyWidgetState createState() =>
_MyWidgetState();
}
class _MyWidgetState
extends State<MyWidget> {
late TextEditingController _controller;
#override
void initState() {
super.initState();
_controller = TextEditingController(text: "My Text");
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _controller,
),
],
);
}
}
Stateless: (issue)
class MyWidget extends StatelessWidget {
const MyWidget(
{Key? key})
: super(key: key);
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: TextEditingController(text: "My Text"),
),
],
);
}
}
If you simply want to replace the entire text inside the text editing controller, then the other answers here work. However, if you want to programmatically insert, replace a selection, or delete, then you need to have a little more code.
Making your own custom keyboard is one use case for this. All of the inserts and deletions below are done programmatically:
Inserting text
The _controller here is a TextEditingController for the TextField.
void _insertText(String myText) {
final text = _controller.text;
final textSelection = _controller.selection;
final newText = text.replaceRange(
textSelection.start,
textSelection.end,
myText,
);
final myTextLength = myText.length;
_controller.text = newText;
_controller.selection = textSelection.copyWith(
baseOffset: textSelection.start + myTextLength,
extentOffset: textSelection.start + myTextLength,
);
}
Thanks to this Stack Overflow answer for help with this.
Deleting text
There are a few different situations to think about:
There is a selection (delete the selection)
The cursor is at the beginning (don’t do anything)
Anything else (delete the previous character)
Here is the implementation:
void _backspace() {
final text = _controller.text;
final textSelection = _controller.selection;
final selectionLength = textSelection.end - textSelection.start;
// There is a selection.
if (selectionLength > 0) {
final newText = text.replaceRange(
textSelection.start,
textSelection.end,
'',
);
_controller.text = newText;
_controller.selection = textSelection.copyWith(
baseOffset: textSelection.start,
extentOffset: textSelection.start,
);
return;
}
// The cursor is at the beginning.
if (textSelection.start == 0) {
return;
}
// Delete the previous character
final newStart = textSelection.start - 1;
final newEnd = textSelection.start;
final newText = text.replaceRange(
newStart,
newEnd,
'',
);
_controller.text = newText;
_controller.selection = textSelection.copyWith(
baseOffset: newStart,
extentOffset: newStart,
);
}
Full code
You can find the full code and more explanation in my article Custom In-App Keyboard in Flutter.
Here is a full example where the parent widget controls the children widget. The parent widget updates the children widgets (Text and TextField) with a counter.
To update the Text widget, all you do is pass in the String parameter.
To update the TextField widget, you need to pass in a controller, and set the text in the controller.
main.dart:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
home: Home(),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Update Text and TextField demo'),
),
body: ParentWidget());
}
}
class ParentWidget extends StatefulWidget {
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int _counter = 0;
String _text = 'no taps yet';
var _controller = TextEditingController(text: 'initial value');
void _handleTap() {
setState(() {
_counter = _counter + 1;
_text = 'number of taps: ' + _counter.toString();
_controller.text = 'number of taps: ' + _counter.toString();
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(children: <Widget>[
RaisedButton(
onPressed: _handleTap,
child: const Text('Tap me', style: TextStyle(fontSize: 20)),
),
Text('$_text'),
TextField(controller: _controller,),
]),
);
}
}
Declare TextEditingController.
supply controller to the TextField.
user controller's text property to change the value of the textField.
follow this official solution to the problem
simply change the text or value property of controller.
if you do not edit selection property cursor goes to first of the new text.
onPress: () {
_controller.value=TextEditingValue(text: "sample text",selection: TextSelection.fromPosition(TextPosition(offset: sellPriceController.text.length)));
}
or in case you change the .text property:
onPress: () {
_controller.text="sample text";
_controller.selection = TextSelection.fromPosition(TextPosition(offset:_controller.text.length));
}
in cases that do not matter to you just don't change the selection property
add text to your controller like this image below