Related
I'm trying to format textformfield while typing. I've managed to get it working...well sort of. Currently the textformfield takes in number and works like this:
user types in number for example "1" - > user types in second number ex. "2" the formatting changes the value to "1.2" by adding . in between -> user types in a third number "3" the formatting changes the value to "12.3".
Now my issue is that this is a price field and I want to round up to two digits after the . SO it would be 1.00, 1.20 or 12.30.
I've been sitting on this for a bit now and I have no idea how to make it work.
I want to make sure that there cannot be more than 5 characters inside with largest value example (12.30)
The Code:
TextFormField(
validator: (fuelPriceValue) {
if (fuelPriceValue!.isEmpty) {
return null;
}
},
onChanged: (fuelPriceValue) {
fuelPrice = (fuelPriceValue as TextEditingController);
},
controller: fuelPrice,
keyboardType: TextInputType.number,
inputFormatters: [
LengthLimitingTextInputFormatter(4),
FilteringTextInputFormatter.allow(RegExp(r'\d*')),
TextInputFormatter.withFunction((oldValue, newValue) {
LengthLimitingTextInputFormatter(3);
if (newValue.text.length == 2) {
return TextEditingValue(
text: '${newValue.text.substring(0, 1)}.${newValue.text.substring(1)}',
selection: TextSelection.fromPosition(TextPosition(offset: newValue.text.length + 1))
);
}
LengthLimitingTextInputFormatter(4);
if (newValue.text.length == 3) {
return TextEditingValue(
text: '${newValue.text.substring(0, 2)}.${newValue.text.substring(2)}',
selection: TextSelection.fromPosition(TextPosition(offset: newValue.text.length + 1))
);
}
if (newValue.text.length == 4) {
return TextEditingValue(
text: '${newValue.text.substring(0, 2)}.${newValue.text.substring(2)}',
selection: TextSelection.fromPosition(TextPosition(offset: newValue.text.length))
);
}
return TextEditingValue(
text: newValue.text,
selection: TextSelection.collapsed(offset: newValue.text.length),
);
}),
],
// inputFormatters: [
// LengthLimitingTextInputFormatter(5),
// FilteringTextInputFormatter.allow(RegExp(r'\d*')),
// TextInputFormatter.withFunction((oldValue, newValue) {
//
// LengthLimitingTextInputFormatter(3);
// if (newValue.text.length == 2) {
// return TextEditingValue(
// text: '${newValue.text.substring(0, 1)}.${newValue.text.substring(1)}',
// selection: TextSelection.fromPosition(TextPosition(offset: newValue.text.length + 1))
// );
// }
// LengthLimitingTextInputFormatter(4);
// if (newValue.text.length == 3) {
// return TextEditingValue(
// text: '${newValue.text.substring(0, 2)}.${newValue.text.substring(2)}',
// selection: TextSelection.fromPosition(TextPosition(offset: newValue.text.length + 1))
// );
// }
// if (newValue.text.length == 4) {
// return TextEditingValue(
// text: '${newValue.text.substring(0, 2)}.${newValue.text.substring(2)}',
// selection: TextSelection.fromPosition(TextPosition(offset: newValue.text.length))
// );
// }
// return TextEditingValue(
// text: newValue.text,
// selection: TextSelection.collapsed(offset: newValue.text.length),
// );
// }),
// ],
decoration: InputDecoration(
errorBorder: OutlineInputBorder(),
border: OutlineInputBorder(),
contentPadding: EdgeInsets.only(left: 10))),
its just idea not final solution.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class TextExample extends StatefulWidget {
const TextExample({super.key});
#override
State<TextExample> createState() => _TextExampleState();
}
class _TextExampleState extends State<TextExample> {
TextEditingController c = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
child: Column(
children: [
TextFormField(
controller: c,
onChanged: (value) {
//TextSelection.fromPosition(TextPosition(offset: c.text.length));
},
keyboardType: TextInputType.number,
inputFormatters: [
LengthLimitingTextInputFormatter(4),
FilteringTextInputFormatter.allow(RegExp(r"[0-9.]")),
TextInputFormatter.withFunction((oldValue, newValue) {
String input = newValue.text;
if(input.length==2){
if(!input.contains(".")){
input = input[0] + "."+input[1];
}else{
}
}
if(input.length==3){
if(input.contains(".")){
input = input.replaceAll(".", "");
input = input[0] + "."+input[1];
}else{
}
}
if(input.length==4){
var count = '.'.allMatches(input).length;
if(count>1){
input = oldValue.text;
return upr(input);
}else if(count==1){
if(input.contains(".") && input.length==4){
// var position = input.indexOf('.');
input = input.replaceAll(".", "");
input = input[0] + input[1]+"."+input[2];
if(int.parse(input[0]) != 1){
input = oldValue.text;
return upr(input);
}
if(int.parse(input[0]) == 1 && int.parse(input[1])>2 ){
input = oldValue.text;
return upr(input);
}
if(int.parse(input[0]) == 1 && int.parse(input[1])<3 && int.parse(input[3])>3){
input = oldValue.text;
return upr(input);
}
if(int.parse(input[0]) != 1 && int.parse(input[1])< 3 ){
input = oldValue.text;
return upr(input);
}
}
}else{
input = oldValue.text;
return upr(input);
}
}
if(input.length>4){
input = oldValue.text;
return upr(input);
}
return upr(input);
}),
],
),
],
),
),
),
);
}
}
TextEditingValue upr(input){
TextEditingValue val1 = TextEditingValue(
text:input,
selection: TextSelection.fromPosition(TextPosition(offset: input.length))
);
return val1 ;
}
Ok so I've managed to answer the question myself. THank you for suggestions.
Here is the updated code:
In my widget class I've added
final RegExp regExp = new RegExp(r'^\d{2}\.\d{2}$');
And here is the inputFormatter from textformfield
inputFormatters: [
new LengthLimitingTextInputFormatter(6),
FilteringTextInputFormatter.allow(RegExp(r'\d*')),
TextInputFormatter.withFunction((oldValue, newValue) {
if (newValue.text.length == 1 && newValue.text.length > oldValue.text.length) {
return TextEditingValue(
text: '${newValue.text}.00',
selection: TextSelection.fromPosition(TextPosition(offset: newValue.text.length + 3))
);
}
if (newValue.text.length == 4) {
return TextEditingValue(
text: '${newValue.text.substring(0, 1)}.${newValue.text.substring(3)}0',
selection: TextSelection.fromPosition(TextPosition(offset: newValue.text.length + 1))
);
}
LengthLimitingTextInputFormatter(5);
if (newValue.text.length == 5) {
if (!regExp.hasMatch(oldValue.text)) {
return TextEditingValue(
text: '${newValue.text.substring(0, 1)}${newValue.text.substring(
3, 4)}.${newValue.text.substring(4)}0',
selection: TextSelection.fromPosition(
TextPosition(offset: newValue.text.length))
);
} else {
return TextEditingValue(
text: oldValue.text,
selection: TextSelection.fromPosition(
TextPosition(offset: newValue.text.length))
);
}
}
return TextEditingValue(
text: newValue.text,
selection: TextSelection.collapsed(offset: newValue.text.length),
);
}),
],
I recently wrote a test program that I needed that is essentially a CRUD program. I needed to handle this differently to other similar programs that I have written, because I normally use a stateful FAB widget, and don't have to setState() to enable and disable the FAB. In this test program I didn't want to use the custom FAB, and used the standard FAB. I found that whenever I had to enable or disable the FAB because of a change to a TextField, that this required a setState(), and after the build, the cursor for the TextField that was being edited had repositioned. I don't know why that happens, because I had not recreated the Widgets. The only solution that I could come up with to handle that issue was fairly messy and required saving the Widget position in the List of TextField and also save the Selection, and then after the build resetting the Selection to the saved Selection.
What I need to achieve is for the FAB to be only enabled when data has changed. Obviously this can vary with every key entry.
I presume I'm not handling this in the optimal way. How is this handled so that the cursor position remains as it was prior to the build?
----- Have Now Added Code below ----
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() => runApp(MyApp());
//=====================================================================================
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test Widgets',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(title: 'Test Widgets'),
);
}
}
//=====================================================================================
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_HomePageState createState() => _HomePageState();
}
//=====================================================================================
class _HomePageState extends State<HomePage> {
bool _tfDataHasChanged = false;
bool _tfInitialized = false;
bool _tfSaveSelection = false;
int _iNdxWidgetChanged = -1;
List<String> _lsOldData = ['Row 1', 'Row 2', 'Row 3', 'Row 4'];
List<String> _lsNewData = ['Row 1', 'Row 2', 'Row 3', 'Row 4'];
List<TextField> _lwTextFields;
TextSelection _wTextSelection;
//-------------------------------------------------------------------------------------
#override
void dispose() {
for (int iNdxWidget = 0;
_lwTextFields != null && iNdxWidget < _lwTextFields.length;
iNdxWidget++) {
_lwTextFields[iNdxWidget].focusNode.removeListener(() {
_fnFocusChanged();
});
_lwTextFields[iNdxWidget]?.controller?.dispose();
_lwTextFields[iNdxWidget]?.focusNode?.dispose();
}
super.dispose();
}
//-------------------------------------------------------------------------------------
#override
Widget build(BuildContext context) {
_tfInitialized = false;
SchedulerBinding.instance.addPostFrameCallback((_) => _fnOnBuildComplete());
if (_lwTextFields == null) {
_fnCreateAllWidgets();
}
List<Widget> lwDisplay = _fnCreateDisplay();
return Scaffold(
appBar: AppBar(
flexibleSpace: SafeArea(
child: _fnCreateAppBarWidgets(),
)),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: lwDisplay,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _tfDataHasChanged ? _fnUpdateData : null,
tooltip: 'Update',
backgroundColor: _tfDataHasChanged ? Colors.blue : Colors.grey,
child: Icon(Icons.done),
),
);
}
//-------------------------------------------------------------------------------------
_fnOnBuildComplete() {
_tfInitialized = true;
if (_tfSaveSelection && _iNdxWidgetChanged >= 0) {
_lwTextFields[_iNdxWidgetChanged].controller.selection = _wTextSelection;
}
}
//-------------------------------------------------------------------------------------
void _fnCreateAllWidgets() {
_lwTextFields = List(_lsNewData.length);
for (int iNdxWidget = 0; iNdxWidget < _lwTextFields.length; iNdxWidget++) {
_fnCreateTextField(iNdxWidget);
}
}
//-------------------------------------------------------------------------------------
void _fnCreateTextField(int iNdxWidget) {
TextEditingController wController = TextEditingController();
FocusNode wFocusNode = FocusNode();
wFocusNode.addListener(() => _fnFocusChanged());
_lwTextFields[iNdxWidget] = TextField(
autofocus: false, //(iNdxWidget == 0),
autocorrect: false,
enabled: true,
keyboardType: TextInputType.text,
maxLength: 25,
controller: wController,
focusNode: wFocusNode,
textInputAction: TextInputAction.next /* TYPE OF ACTION KEY */,
onSubmitted: ((v) => _fnSetNextFocus(iNdxWidget)),
onChanged: (text) => _fnTextListener(iNdxWidget, text),
decoration: _fnCreateInputDecoration(
'Text Field Number ${iNdxWidget + 1}', 'Enter Data'),
style: _fnCreateWidgetTextStyle(Colors.blue[700]),
);
}
//-------------------------------------------------------------------------------------
_fnTextListener(int iNdxWidget, String sText) {
if (_tfInitialized) {
_lsNewData[iNdxWidget] = sText;
_fnCheckIfDataHasChanged(
iNdxWidget) /* ENABLE OR DISABLE SUBMIT BUTTON */;
}
}
//-------------------------------------------------------------------------------------
_fnSetNextFocus(int iNdxWidget) {
if (_lwTextFields[iNdxWidget].focusNode.hasFocus) {
_lwTextFields[iNdxWidget].focusNode.unfocus();
if (iNdxWidget + 1 < _lwTextFields.length) {
_lwTextFields[iNdxWidget + 1]?.focusNode?.requestFocus();
}
}
}
//-------------------------------------------------------------------------------------
InputDecoration _fnCreateInputDecoration(String sHeading, String sHint) {
return InputDecoration(
labelText: sHeading,
hintText: sHint,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(20.0)),
);
}
//-------------------------------------------------------------------------------------
TextStyle _fnCreateWidgetTextStyle(Color color) {
return TextStyle(
fontSize: 14.0,
color: color,
);
}
//-------------------------------------------------------------------------------------
List<Widget> _fnCreateDisplay() {
List<Widget> lwDisplay = List((_lwTextFields.length * 2) + 2);
lwDisplay[0] = SizedBox(height: 10);
int iNdxDisplay = 1;
for (int iNdxWidget = 0; iNdxWidget < _lwTextFields.length; iNdxWidget++) {
_lwTextFields[iNdxWidget].controller.text = _lsNewData[iNdxWidget];
lwDisplay[iNdxDisplay++] = _lwTextFields[iNdxWidget];
lwDisplay[iNdxDisplay++] =
SizedBox(height: iNdxDisplay < lwDisplay.length - 2 ? 10 : 80);
}
lwDisplay[lwDisplay.length - 1] = Divider(color: Colors.black, height: 2);
return lwDisplay;
}
//-------------------------------------------------------------------------------------
_fnUpdateData() {
for (int iNdxWidget = 0; iNdxWidget < _lsNewData.length; iNdxWidget++) {
if (_lsNewData[iNdxWidget] != _lsOldData[iNdxWidget]) {
_lsOldData[iNdxWidget] = _lsNewData[iNdxWidget];
}
}
_fnCheckIfDataHasChanged(-1);
}
//-------------------------------------------------------------------------------------
_fnCheckIfDataHasChanged(int iNdxWidgetChanged) {
bool tfChanged = false /* INIT */;
for (int iNdxWidgetTest = 0;
!tfChanged && iNdxWidgetTest < _lsNewData.length;
iNdxWidgetTest++) {
tfChanged = _lsNewData[iNdxWidgetTest] != _lsOldData[iNdxWidgetTest];
}
if (iNdxWidgetChanged >= 0) {
_iNdxWidgetChanged = iNdxWidgetChanged;
_wTextSelection = _lwTextFields[iNdxWidgetChanged].controller.selection;
}
if (tfChanged != _tfDataHasChanged) {
setState(() => _tfDataHasChanged = tfChanged) /* WE NEED TO ENABLE FAB */;
}
}
//-------------------------------------------------------------------------------------
Row _fnCreateAppBarWidgets() {
IconData wIconData =
_tfSaveSelection ? Icons.check_box : Icons.check_box_outline_blank;
Color wColor = _tfSaveSelection ? Colors.blue[900] : Colors.grey[600];
IconButton wIconButton = IconButton(
icon: Icon(wIconData),
color: wColor,
onPressed: _fnCheckboxChanged,
iconSize: 40);
return Row(children: <Widget>[
SizedBox(width: 10),
Text('Save\nSelection', textAlign: TextAlign.center),
wIconButton,
SizedBox(width: 30),
Text('Test TextField')
]);
}
//-------------------------------------------------------------------------------------
_fnFocusChanged() {
for (int iNdxWidget = 0; iNdxWidget < _lwTextFields.length; iNdxWidget++) {
if (_lwTextFields[iNdxWidget].focusNode.hasFocus) {
_iNdxWidgetChanged = iNdxWidget;
_wTextSelection = _lwTextFields[iNdxWidget].controller.selection;
return;
}
}
}
//-------------------------------------------------------------------------------------
void _fnCheckboxChanged() {
_tfSaveSelection = !_tfSaveSelection;
if (!_tfSaveSelection) {
_iNdxWidgetChanged = -1;
}
setState(() {});
}
}
-------- Have added key to TextField, but issue persists ---------
key: ValueKey<int>(iNdxWidget),
My bug - as posted by #pskink
My excuse - I normally use a stateful FAB, so I don't normally encounter this.
Answer:
so change this line:
TextEditingController wController = TextEditingController(text: _lsNewData[iNdxWidget]);
and remove this one
_lwTextFields[iNdxWidget].controller.text = _lsNewData[iNdxWidget];
– pskink Feb 23 at 7:33
I hope these functions might help you
void updateText(String text) {
if (text != null) {
this.text = _applyMask(mask, text);
} else {
this.text = '';
}
_lastUpdatedText = this.text;
}
void updateMask(String mask, {bool moveCursorToEnd = true}) {
this.mask = mask;
updateText(text);
if (moveCursorToEnd) {
this.moveCursorToEnd();
}
}
void moveCursorToEnd() {
final String text = _lastUpdatedText;
selection =
TextSelection.fromPosition(TextPosition(offset: (text ?? '').length));
}
I am using a textfield with these properties:
TextField(
controller: textController,
keyboardType: TextInputType.multiline,
maxLines: 4,
maxLength: 150,
),
Which works fine but I was wondering how I could prevent users from typing in break lines that would cause the text field to have more lines that the maxLines (4)..
Is there a way of locking the lines at 4?
e.g.
input 1 \n \n \n should work
but 1 \n \n \n \n \n \n should not be allowed
I modified LengthLimitingTextInputFormatter to get my own MaxLinesTextInputFormatter.
Here is the code
class MaxLinesTextInputFormatter extends TextInputFormatter {
MaxLinesTextInputFormatter(this.maxLines)
: assert(maxLines == null || maxLines == -1 || maxLines > 0);
final int maxLines;
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, // unused.
TextEditingValue newValue,
) {
if (maxLines != null && maxLines > 0) {
final regEx = RegExp("^.*((\n?.*){0,${maxLines - 1}})");
String newString = regEx.stringMatch(newValue.text) ?? "";
final maxLength = newString.length;
if (newValue.text.runes.length > maxLength) {
final TextSelection newSelection = newValue.selection.copyWith(
baseOffset: math.min(newValue.selection.start, maxLength),
extentOffset: math.min(newValue.selection.end, maxLength),
);
final RuneIterator iterator = RuneIterator(newValue.text);
if (iterator.moveNext())
for (int count = 0; count < maxLength; ++count)
if (!iterator.moveNext()) break;
final String truncated = newValue.text.substring(0, iterator.rawIndex);
return TextEditingValue(
text: truncated,
selection: newSelection,
composing: TextRange.empty,
);
}
return newValue;
}
return newValue;
}
}
Usage:
TextField(
decoration: InputDecoration(),
maxLines: 4,
inputFormatters: [MaxLinesTextInputFormatter(4)],
)
You can use allMatches() function to count the number of lines the input contains and update an error variable if the function returns 4 or more.
if (('\n'.allMatches(text).length + 1) > 4) { // check for new lines and update bool variable }
An example:
import 'package:flutter/material.dart';
class Demo extends StatefulWidget {
#override
_DemoState createState() => _DemoState();
}
class _DemoState extends State<Demo> {
final textController = TextEditingController();
bool error = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("DEMO"),
),
body: Container(
child: Column(children: [
TextField(
controller: textController,
keyboardType: TextInputType.multiline,
maxLines: 4,
maxLength: 150,
onChanged: (text) {
setState(() {
if (('\n'.allMatches(text).length + 1) > 4) {
error = true;
} else {
error = false;
}
});
},
),
error ? Text("More than 4 lines entered") : Container()
])));
}
}
I updated the Crazy Lazy Cat answer to null safety
import 'dart:math';
import 'package:flutter/services.dart';
class MaxLinesTextInputFormatter extends TextInputFormatter {
MaxLinesTextInputFormatter(this._maxLines) : assert(_maxLines == -1 || _maxLines > 0);
final int _maxLines;
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, // unused.
TextEditingValue newValue,
) {
if (_maxLines > 0) {
final regEx = RegExp("^.*((\n?.*){0,${_maxLines - 1}})");
final newString = regEx.stringMatch(newValue.text) ?? "";
final maxLength = newString.length;
if (newValue.text.runes.length > maxLength) {
final newSelection = newValue.selection.copyWith(
baseOffset: min(newValue.selection.start, maxLength),
extentOffset: min(newValue.selection.end, maxLength),
);
final iterator = RuneIterator(newValue.text);
if (iterator.moveNext()) {
for (var count = 0; count < maxLength; ++count) {
if (!iterator.moveNext()) break;
}
}
final truncated = newValue.text.substring(0, iterator.rawIndex);
return TextEditingValue(
text: truncated,
selection: newSelection,
composing: TextRange.empty,
);
}
return newValue;
}
return newValue;
}
}
this is code. for 4 textfield and one for adding those values also take switch case for getting the values from the label of textfield but didn't get any output. my switch case also work but didn't get any output in totalSum variable.
this is code. for 4 textfield and one for adding those values also take switch case for getting the values from the label of textfield but didn't get any output. my switch case also work but didn't get any output in totalSum variable.
#override
State<StatefulWidget> createState() => _AmountEdit();
}
class _AmountEdit extends State<AddAmount> {
final amount = new TextEditingController();
final amount1 = new TextEditingController();
final amount2 = new TextEditingController();
final amount3 = new TextEditingController();
final amount4 = new TextEditingController();
final total = new TextEditingController();
final FocusNode _focusNode = FocusNode();
final FocusNode _focusNode1 = FocusNode();
final FocusNode _focusNode2 = FocusNode();
final FocusNode _focusNode3 = FocusNode();
final FocusNode _focusNode4 = FocusNode();
final FocusNode _focusNode5 = FocusNode();
void initState() {
super.initState();
_focusNode.addListener(() {
print("Has focus: ${_focusNode.hasFocus}");
if (_focusNode.hasFocus == false) {
print(test);
}
if (test.contains(",00")) {
print("perfect amount");
} else {
String finalAmount = test + append;
print("the final amount ${finalAmount}");
amount.text = finalAmount;
}
});
}
#override
void dispose() {
_focusNode.dispose();
super.dispose();
}
String append = ",00";
String test;
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
appBar: AppBar(
title: Text('Retrieve Text Input'),
),
body: Column(
children: <Widget>[
TextBox1(
controllerText: amount,
focusNode: _focusNode,
label: "amount",
),
TextBox1(
controllerText: amount1,
focusNode: _focusNode1,
label: "amount1",
),
TextBox1(
controllerText: amount2,
focusNode: _focusNode2,
label: "amount2",
),
TextBox1(
controllerText: amount3,
focusNode: _focusNode3,
label: "amount3",
),
TextBox1(
controllerText: amount4,
focusNode: _focusNode4,
label: "amount4",
),
TextBox1(
controllerText: total,
focusNode: _focusNode5,
),
],
),
);
}
}
class TextBox1 extends StatefulWidget {
var controllerText;
var focusNode;
String label;
TextBox1({this.controllerText, this.focusNode, this.label});
#override
State<StatefulWidget> createState() => _TextBox();
}
class _TextBox extends State<TextBox1> {
String test;
int test1;
String append = ",00";
String finalAmount;
#override
void initState() {
super.initState();
widget.focusNode.addListener(() {
if (widget.focusNode.hasFocus == false) {
print(test);
if (test == "") {
finalAmount = "";
} else {
if (test.contains(",")) {
} else {
finalAmount = test + append;
widget.controllerText.text = finalAmount;
}
}
}
});
}
#override
void dispose() {
widget.focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
// TODO: implement build
double screenWidth = MediaQuery.of(context).size.width;
return TextField(
onChanged: (value) {
print("widget label ${widget.label}");
print("onchangecallback");
test = value;
print("printng value ${value}");
test1 = int.parse(value);
sum(value, widget.label);
// print("printing test1 ${test1}");
},
// inputFormatters: [
// WhitelistingTextInputFormatter.digitsOnly,
// CurrencyInputFormatter(),
// ],
controller: widget.controllerText,
focusNode: widget.focusNode,
keyboardType: TextInputType.number,
decoration: InputDecoration(
disabledBorder:
OutlineInputBorder(borderSide: BorderSide(color: Colors.white)),
border: OutlineInputBorder(),
));
}
dynamic a;
dynamic b;
dynamic c;
dynamic d;
dynamic e;
dynamic totalSum;
sum(value, type) {
// print("print value ${widget.controllerText}");
// print("print amount text field ${value}");
switch (type) {
case "amount":
{
setState(() {});
a = value;
print("value of A ${a}");
}
break;
case "amount1":
{setState(() {});
b = value;
print("value of B ${b}");
}
break;
case "amount2":
{setState(() {});
c = value;
print("value of C ${c}");
}
break;
case "amount3":
{setState(() {});
d = value;
print("value of D ${d}");
}
break;
case "amount4":
{setState(() {});
e = value;
print("value of E ${e}");
}
break;
}
print("value of A outside the switch ${a}");
print("value of B outside the switch ${b}");
totalSum = a + b + c + d + e;
print("this is total ${totalSum}");
}
}```
Why not using TextEditingController.fromValue controller?
new TextField(
controller: new TextEditingController.fromValue(
new TextEditingValue(
text: field1,
selection:
onChanged: (content) {
setState(() {
sum = field1+field2+field3+field4;
});
},
onTap: (() {}),
onSubmitted: ((s) {
setState(() {
});
}),
),
In my code I validate phone number. If phone number is incorrect - I show error message. But, when user starts to edit number I want to hide this error message.
I've found the solution with currentState.reset(), but it seems not the good one. I have to handle issues with saving text and cursor position. And I still have one small artifact. Normally when I press and hold backspace - it deletes symbols one by one. If I do it when error message is shown - then error message disappears and only one symbol is deleted.
Does anybody know the right solution for this case?
final TextEditingController controller = TextEditingController();
final RegExp _phoneRegex = RegExp(r"^\+{1}\d{10, 15}\$");
bool isError = false;
TextSelection currentPosition;
return Column(
children: <Widget>[
Form(
key: _textKey,
child: TextFormField(
controller: controller,
validator: (str) {
isError = true;
if (str.isEmpty) {
return err_empty_field;
} else if (!_phoneRegex.hasMatch(str)) {
return err_invalid_phone;
}
isError = false;
},
),
onChanged: () {
if (controller.selection.start < 0 &&
controller.text.length > 0) {
TextSelection position =
controller.text.length > currentPosition.start
? currentPosition
: TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
controller.selection = position;
}
if (isError) {
isError = false;
currentPosition = controller.selection;
if (currentPosition.start > controller.text.length) {
currentPosition = TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
}
String currentText = controller.text;
_textKey.currentState.reset();
controller.text = currentText;
controller.selection = currentPosition;
}
},
),
RaisedButton(
onPressed: () {
_textKey.currentState.validate();
},
child: Text(login),
)
],
);
EDIT (Nov 2020)
autovalidate was deprecated after v1.19.0.
Instead use autovalidateMode:
Form(
autovalidateMode: AutovalidateMode.onUserInteraction`.
...
)
Original post
here is a suitable solution to this problem.
You don't actually need to use onChanged or any tips causing side-effects, I solved it by creating a class property which is initialized to false:
bool _autovalidate = false;
The Form Widget has a named property autovalidate. You should pass it the previous boolean:
Form(
key: _textKey,
autovalidate: _autovalidate,
...
)
And in your Submit button onPressed() method, you should update the _autovalidate boolean to be true if the form is invalid, this will make the form to auto validate the TextFormField on every onChanged call:
RaisedButton(
onPressed: () {
if (_textKey.currentState.validate()) {
print('valid');
} else {
print('invalid');
setState(() => _autoValidate = true);
}
},
child: Text(login),
)
I hope it helped Somebody.
January 2021
...
AutovalidateMode _autoValidate = AutovalidateMode.disabled;
Form(
key: _textKey,
autovalidateMode: _autovalidate,
...
)
RaisedButton(
onPressed: () {
if (_textKey.currentState.validate()) {
print('valid');
} else {
print('invalid');
setState(() => _autoValidate = AutovalidateMode.always);
}
},
child: Text("login"),
)
The problem here is errorText is automatically managed by the validator field of the TextFormField. At the same time, the simple solution is to handle the errorText manually.
Step 1: Create
String field, _errorText initialised to null. The field will hold the error message that needs to be shown.
Boolean field, _error initialised to false. The filed is true if there is an error otherwise false.
Step 2:
Assign _errorText to TextFormField
Step 3 (Important):
Make sure that TextFormField validator returns a null value.
Handle the validation here and assign the proper error message to _errorText.
Update _error state correspondingly.
Step 4 (Important):
Reset _errorText and _error. This will remove the error from field soon as you start editing.
Step 5:
Trigger field validation in the onFieldSubmitted and manage your code flow...
import 'package:flutter/material.dart';
class WorkGround extends StatefulWidget {
#override
_WorkGroundState createState() => _WorkGroundState();
}
class _WorkGroundState extends State<WorkGround> {
final _formKey = GlobalKey<FormState>();
final _usernameFocusNode = FocusNode();
final _phoneNumberFocusNode = FocusNode();
/*
* Step 1.
* */
String _userNameErrorText;
bool _userNameError = false;
String _phoneNumberErrorText;
bool _phoneNumberError = false;
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormField(
focusNode: _usernameFocusNode,
decoration: InputDecoration(
labelText: 'Username',
/*
* Step 2
* */
errorText: _userNameErrorText, // Handling error manually
),
textInputAction: TextInputAction.next,
/*
* Step 3
* */
validator: (value) {
setState(() {
if(value.isEmpty) {
_userNameError = true;
_userNameErrorText = 'Enter Username';
}
});
return null; // Return null to handle error manually.
},
/*
* Step 4
* */
onChanged: (value) {
setState(() {
_userNameError = false;
_userNameErrorText = null; // Resets the error
});
},
/*
* Step 5
* */
onFieldSubmitted: (value) {
_formKey.currentState.validate(); // Trigger validation
if(!_userNameError) {
FocusScope.of(context).requestFocus(_phoneNumberFocusNode);
}
},
),
TextFormField(
focusNode: _phoneNumberFocusNode,
decoration: InputDecoration(
labelText: 'Phone Number',
/*
* Step 2
* */
errorText: _phoneNumberErrorText, // Handling error manually
),
textInputAction: TextInputAction.done,
/*
* Step 3
* */
validator: (value) {
setState(() {
if(value.isEmpty) {
_phoneNumberError = true;
_phoneNumberErrorText = 'Enter Phone number';
} else if( value.length < 10) {
_phoneNumberError = true;
_phoneNumberErrorText = 'Invalid Phone number';
}
});
return null; // Return null to handle error manually.
},
/*
* Step 4
* */
onChanged: (value) {
setState(() {
_phoneNumberError = false;
_phoneNumberErrorText = null; // Resets the error
});
},
/*
* Step 5
* */
onFieldSubmitted: (value) {
_formKey.currentState.validate(); // Trigger validation
if(!_phoneNumberError) {
// submit form or whatever your code flow is...
}
},
),
],
),
),
);
}
}
I have achieved your both below functionality:
1) Hide error message when editing
2) validate input field when login button pressed
Note: i have commented phone number regex and put validation for
string length < 10 digit for testing.
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState(){
super.initState();
}
final TextEditingController controller = TextEditingController();
// final RegExp _phoneRegex = RegExp(r"^\+{1}\d{10, 15}\$");
bool isError = false;
bool isWriting = false;
bool isLoginPressed = false;
int counter = 0;
String myErrorString = "";
TextSelection currentPosition;
final _textKey = GlobalKey<FormState>();
#override
Widget build(BuildContext ctx) {
return Scaffold(
appBar: AppBar(
title: Text('MapSample'),
),
body: Container(
child: Column(
children: <Widget>[
Form(
key: _textKey,
child: TextFormField(
controller: controller,
validator: (str) {
myErrorString = "";
if(isLoginPressed){
isError = true;
if (str.isEmpty) {
myErrorString = 'err_empty_field';
return myErrorString;
}
else if (str.length < 10) {
myErrorString = 'err_invalid_phone';
validateMe();
return myErrorString;
}
/*else if (!_phoneRegex.hasMatch(str)) {
myErrorString = 'err_invalid_phone';
validateMe();
return myErrorString;
}*/
isError = false;
myErrorString = "";
}else{
myErrorString = "";
}
},
),
onChanged: () {
counter++;
if(counter == 9){
counter = 0;
isLoginPressed = false;
}
if(isLoginPressed){
}else{
isWriting = true;
isLoginPressed = false;
myErrorString = "";
_textKey.currentState.validate();
}
},
),
RaisedButton(
onPressed: () {
counter = 1;
isWriting = false;
isLoginPressed = true;
_textKey.currentState.validate();
},
child: Text('login'),
)
],
),
),
);
}
void validateMe() {
if(isLoginPressed){
currentPosition = TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
String currentText = controller.text;
_textKey.currentState.reset();
controller.text = currentText;
controller.selection = currentPosition;
isWriting = false;
isLoginPressed = true;
}
}
}
I've found working and easier way
final _textKey = GlobalKey<FormState>();
final TextEditingController _controller = TextEditingController();
Widget _getPhoneInputForm() {
final RegExp _phoneRegex = RegExp(r"^\+{1}\d{10,17}");
bool isError = false;
bool isButtonPressed = false;
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 36.0),
child: Form(
key: _textKey,
child: TextFormField(
keyboardType: TextInputType.phone,
decoration: InputDecoration(
hintText: hint_enter_phone,
contentPadding: EdgeInsets.all(24.0),
fillColor: Colors.blueGrey.withOpacity(0.3),
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0)),
borderSide: BorderSide(color: Colors.blueGrey))),
controller: _controller,
validator: (str) {
if (!isButtonPressed) {
return null;
}
isError = true;
if (str.isEmpty) {
return err_empty_field;
} else if (!_phoneRegex.hasMatch(str)) {
return err_invalid_phone;
}
isError = false;
},
onFieldSubmitted: (str) {
if (_textKey.currentState.validate()) _phoneLogin();
},
),
onChanged: () {
isButtonPressed = false;
if (isError) {
_textKey.currentState.validate();
}
},
),
),
RaisedButton(
color: Colors.teal,
textColor: Colors.white,
onPressed: () {
isButtonPressed = true;
if (_textKey.currentState.validate()) _phoneLogin();
},
child: Text(login),
)
],
);
}
This is an exemple , i think its not necessary to do onchange() , the function validate name do the work ...
String validateName(String value) {
String patttern = r'(^[a-zA-Z ]*$)';
RegExp regExp = new RegExp(patttern);
if (value.length == 0) {
return "Name is Required";
} else if (!regExp.hasMatch(value)) {
return "Name must be a-z and A-Z";
}
return null;
}
TextFormField(
controller: _lastname, validator: validateName ,
//initialValue: widget.contact.last_name,
decoration:
InputDecoration(labelText: 'Last name'),
),
void Save() {
if (_keyForm.currentState.validate()) {
// No any error in validation
_keyForm.currentState.save();
................
}
i have found that using a combination of FocusNode and AtuoValidateMode.onUserInteraction does the trick.
class _TextAutoValidateModeExampleState extends State<TextAutoValidateModeExample> {
FocusNode node = FocusNode();
#override
Widget build(BuildContext context) {
return Container(
child: TextFormField(
focusNode: node,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) {
if(node.hasFocus) return null;
if (value!.isEmpty) return "value cannot be empty";
if (!value.isEmail) return "not a valid email";
},
),
);
}
}
// Call this method inside onChanged() and when its focusnode hasFocus
void formReset(GlobalKey<FormState> formKey, TextEditingController controller) {
String stringValue = controller.text;
TextPosition textPosition = controller.selection.base;
formKey.currentState.reset();
controller.text = stringValue;
controller.selection = TextSelection.fromPosition(textPosition);
}
this format worked for me, Hope it helps someone....
validator: (value){
bool emailValid = RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+#[a-zA-Z0-9]+\.[a-zA-Z]+").hasMatch(value);
isError = true;
if(value.isEmpty){
return "Provide an email";
}else if(!emailValid){
return "Enter a valid email";
}
isError = false;
return null;
},