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),
);
}),
],
Related
I have a listview to which am applying a filter , the filter works but the UI won't update the list is unchanged .
this is my implementation
class QrqcOnlineListView extends StatefulWidget {
#override
_QrqcOfflineListViewState createState() => _QrqcOfflineListViewState();
}
List<Qrqc> filteredQrqc = [];
List<Qrqc>? qrqcList = [];
class _QrqcOfflineListViewState extends State<QrqcOnlineListView> {
List<Qrqc>? myList;
String? label;
Future<List<Qrqc>?> getQrqcData() => SharedPreferenceMyQrqc().getListQrqc();
List<Qrqc> filteredQrqc = [];
final _controller = TextEditingController();
String? _searchText;
List<TypeSettings> listTypes = [];
#override
void initState() {
Provider.of<MyQrqcListViewModel>(context, listen: false).fetchMyQrqc();
super.initState();
fetchTypes();
}
fetchTypes() async {
listTypes = (await SettingsViewModel().fetchTypes());
}
#override
Widget build(BuildContext context) {
var myQrqcListViewModel = Provider.of<MyQrqcListViewModel>(context);
myList = myQrqcListViewModel.articlesList;
List<Qrqc>? qrqcList = myList;
filteredQrqc = myList!;
List<Qrqc> listFilter = List.from(qrqcList!);
QrqcDetails? result;
String? type;
void updateList(String value) {
setState(() {
listFilter = qrqcList
.where((element) =>
element.title!.toLowerCase().contains(value.toLowerCase()))
.toList();
});
}
String? setTypeLabel() {
for (int j = 0; j < listFilter.length; j++) {
for (int i = 0; i < listTypes.length; i++) {
if (listTypes[i].id == listFilter[j].typeID) {
listFilter[j].typeName = listTypes[i].label;
}
}
}
}
return Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
decoration: InputDecoration(
hintText: "Search",
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(4.0),
),
),
),
onChanged: (value) => updateList(value),
),
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: ListView.builder(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
itemCount: listFilter.length,
itemBuilder: (BuildContext context, int index) {
String? backgroundImage;
String? _setImage() {
setTypeLabel();
if (listFilter[index].typeName == "Delivery") {
backgroundImage = "assets/icons/icon_delivery.png";
} else if (listFilter[index].typeName == "Security") {
backgroundImage = "assets/icons/icon_security.png";
} else if (listFilter[index].typeName == "Quality") {
backgroundImage = "assets/icons/quality.png";
} else if (listFilter[index].typeName == "Cost") {
backgroundImage = "assets/icons/Cost.png";
} else if (listFilter[index].typeName == "People") {
backgroundImage = "assets/icons/people.png";
} else {
backgroundImage = "assets/icons/unknown.png";
}
// print("list types: $qrqcList[index].typeName");
// print("_mTitle: $backgroundImage");
return backgroundImage;
}
return Column(
children: [
QrqcBody(
child: QrqcCard(
child: QrqcCardBody(
color: Colors.orange,
text: listFilter[index].status,
leading: QrqcCardLeaing(imgPath: _setImage()),
trailing: QrqcCardtrailing(
text: listFilter[index].progress.toString(),
percent: listFilter[index].progress.toString(),
),
title: listFilter[index].id.toString(),
subtitle: listFilter[index].title,
chlidren: [
QrqcDetailsCardFirstRow(
product: listFilter[index].productName ?? 'inconnu',
role: listFilter[index].role ?? "inconnu",
),
const SizedBox(height: 10),
QrqcDetailsCardSecondRow(
perim: listFilter[index].perimeterName ?? "inconnu",
date: convertDateTimeDisplay(
listFilter[index].createdAt!),
),
const SizedBox(height: 10),
],
)),
),
],
);
}),
),
],
);
}
}
I don't know what's wrong especially that the filter is working and i got no errors only the keyboard events in the stack trace , if anyone can help i'd be grateful , i've been stuck for a while now
Your issue is that your list is being re-initialized / reset every time in the build method; while you're filter the list correctly inside the setState, when the build method re-executes upon calling setState, you're resetting it again when you do this:
// upon every rebuild, the listFilter gets reset to the original value
List<Qrqc> listFilter = List.from(qrqcList!);
What you could is:
Use the existing property called ** _searchText** and store the value being provided as input by the TextField widget, that when it's empty, then you fetch the whole list, otherwise skip that logic, that way your listFilter remains filtered on the next widget rebuild, kind of like:
In your *updateList method:
void updateList(String value) {
// store the value being provided as input to the filter
// in the property called _searchText
_searchText = value;
setState(() {
listFilter = qrqcList
.where((element) =>
element.title!.toLowerCase().contains(_searchText.toLowerCase()))
.toList();
});
}
Inside the build method:
// check if its empty, that means there's no filter being applied
if (_searchText.isEmpty) {
List<Qrqc> listFilter = List.from(qrqcList!);
}
I want to print the phone number written in the text form field like this;
xxxx-xxx-xxxx
but I want to do this without using any packages.
Can you help me?
You can try with the InputTextFormatter
class MyWidget extends StatelessWidget {
final _mobileFormatter = PhoneNumberTextInputFormatter();
#override
Widget build(BuildContext context) {
return TextFormField(
keyboardType: TextInputType.number,
maxLength: 13,
inputFormatters:[
FilteringTextInputFormatter.digitsOnly,
_mobileFormatter,
],
decoration: InputDecoration(
icon: Icon(Icons.phone_iphone),
hintText: "Mobile*",
),
);
}
}
class PhoneNumberTextInputFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
final int newTextLength = newValue.text.length;
int selectionIndex = newValue.selection.end;
int usedSubstringIndex = 0;
final StringBuffer newText = new StringBuffer();
if (newTextLength >= 5) {
newText.write(newValue.text.substring(0, usedSubstringIndex = 4) +'-' );
if (newValue.selection.end >= 5)
selectionIndex += 3;
}
if (newTextLength >= 7) {
newText.write(newValue.text.substring(4, usedSubstringIndex = 7) );
if (newValue.selection.end >= 7)
selectionIndex++;
}
if (newTextLength >= 8) {
newText.write('-'+newValue.text.substring(7, usedSubstringIndex = 8) );
if (newValue.selection.end >= 11)
selectionIndex++;
}
if (newTextLength >= usedSubstringIndex)
newText.write(newValue.text.substring(usedSubstringIndex));
return new TextEditingValue(
text: newText.toString(),
selection: new TextSelection.collapsed(offset: newText.length),
);
}
}
output:
You can use the obscureText: true parameter like this:
const Form(
child: TextField(
obscureText: true,
),
),
There is a video for this:
enter link description here
I have 1 Form with 2 TextFormField, firstName and lastName.
I need to validate this two data with condition and regex, then pass data to the second page with Navigator.
Problem is, i want lastName is optional or can be empty.
But i can't remove validator because i still need to use it with regex or others.
TextEditingController firstName = TextEditingController();
TextEditingController lastName = TextEditingController();
GlobalKey<FormState> _key = GlobalKey();
bool _validate = false;
String first, last;
Form(
key: _key,
autovalidate: _validate,
child: Column(children: [
InputName(
controller: firstName,
placeholder: 'Nama depan',
validator: validateFirstName,
onSaved: (String val) {
first = val;
},
),
InputName(
controller: lastName,
placeholder: 'Nama belakang',
validator: validateLastName,
onSaved: (String val) {
last = val;
},
),
]),
),
Button(
text: 'Lanjut mengisi Email',
onPressed: () {
if (_key.currentState.validate()) {
_key.currentState.save();
String a = firstName.text.capitalize();
String b = lastName.text.capitalize();
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
Regist2Page([a, b]),
transitionDuration: Duration(seconds: 0),
),
);
} else {
setState(() {
_validate = true;
});
}
},
),
String validateLastName(String value) {
if (value.isNotEmpty) {
if (value.length > 20) {
return 'Maksimal 20 karakter';
}
return null;
}
return null;
}
Try this validator
String validateLastName(String value) {
if (value == "") {
// if value were null, is true and then return null
return null;
}
if (value.length > 20) {
return 'Maksimal 20 karakter';
}
return null;
}
Try this!
String validateLastName(String value,{bool isOptional = false})) {
if(isOptional && (value==null || value.isEmpty)){
return null;
}
if (value.isNotEmpty) {
if (value.length > 20) {
return 'Maksimal 20 karakter';
}
return null;
}
return null;
}
Solved by Agus Setya R.
String b = lastName.text == '' ? '' : lastName.text.capitalize();
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;
}
}
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;
},