Backspace doesn't work correctly with TextInputFormatter - flutter

I'm trying to a create custom InputTextFormatter.
The formatter separates thousands with spaces and limits amount of characters after a dot to 2 chars.
I want to move the cursor to the end of the TextField value, but it looks like the real cursor moves further according to how many times I've tried to enter additional characters after reaching the limit (2 chars).
It looks like selection not applies to resulting TextEditingValue.
Steps to reproduce:
Enter a '12.34' to TextField.
Keeping previous value try to add
'111' for example.
Press backspace. Nothing happens.
Expected behavior: pressing backspace once must delete the last character in the TextField.
class MoneyFormatter extends TextInputFormatter {
MoneyFormatter({this.maxLength = 30, this.decimals = 0});
final int maxLength;
final int decimals;
#override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
print('---------- newValue.selection.extentOffset : ${newValue.selection.extentOffset}');
String output = newValue.text.formatAsCurrency(decimals: decimals);
var result = TextEditingValue(
text: output,
//this line doesn't have any effect
selection: TextSelection.collapsed(offset: output.length),
);
print('---------- result.selection.extentOffset : ${result.selection.extentOffset}');
return result;
}
}
With every additional character result.selection.extentOffset stays the same but newValue.selection.extentOffset increases by 1 despite the fact returning selection: TextSelection.collapsed(offset: output.length)
extension FormatAsCurrency on String {
String formatAsCurrency({int decimals = 0, String ifNullReturn}) {
if (this == null) return ifNullReturn;
if (this.isEmpty) return '';
String output = this;
if (output[0] == '.') output = '0' + output;
var chunks = this.withoutTrailingDots.split('.');
output = chunks[0].separatedThousands;
if (decimals == 0 || chunks.length == 1) return output;
output += '.' + chunks[1].limitLengthTo(decimals).withoutTrailingZeros;
return output;
}
}
I'm aware there are other TextInputFormatters on pub.dev like flutter_multi_formatter, I've tried all of them, the problem stays the same.

Use \b in your regex :
TextField(
controller: tvMobile,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(
RegExp(r'[0-9,\b]')), // <-- Use \b in your regex here so backspace works.
],
maxLength: 10,
style: TextStyle(
color: Colors.white,
fontFamily: "Roboto",
fontWeight: FontWeight.w300,
),
decoration: InputDecoration(
labelStyle: TextStyle(
color: Colors.white,
fontFamily: "Roboto",
fontWeight: FontWeight.w300,
),
fillColor: Colors.white,
enabledBorder: UnderlineInputBorder(
borderSide:
BorderSide(color: Colors.yellow),
),
focusedBorder: UnderlineInputBorder(
borderSide:
BorderSide(color: Colors.yellow),
),
hintText: 'Mobile Number',
hintStyle: TextStyle(
color: Colors.grey,
fontFamily: "Roboto",
fontWeight: FontWeight.w300,
),
),
),

I found the backspacing problem when I try to use formatter with keyboard types number. The cursor tends to skip character(s) for each backspacing. It works fine if I use keyboard type text.

Related

How to prevent typing after some length in flutter text Field

If item Quantity is 3 then user must have to enter 3 serial # (comma Separated). I want to prevent user to type further if the comma separated string length reaches to 3. Also Backspace should also work if he wants to erase some value. Please guide me on this:
Here is My Code:
TextFormField(
cursorColor: titleTextColor,
decoration: const InputDecoration(
labelText: "Serial #",
labelStyle: TextStyle(color: appBarColor),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: appBarColor),
),
//[focusedBorder], displayed when [TextField, InputDecorator.isFocused] is true
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: appBarColor),
),
),
validator: (val) {
if (val!.isEmpty) {
return 'Required';
}
},
controller: serialsController,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.done,
onChanged: (val) {
if (val.isNotEmpty) {
int len = val.split(",").length;
if (double.parse(len.toString()) > double.parse(itemListOrderDetail[index].LineQuantity.toString())) {
showToast("Stop", FToast().init(context));
}
}
},
)
In this case a good solution is using a mask.
Check this package easy mask
https://pub.dev/packages/easy_mask
So , when you change the qtd number u change the mask too.
Edit.
You can use onChanged prop to control too...
onChanged:(val){
if(val.split(',').length > 3){
yourController.text = val.substring(0,val.length-1);
}
} ,
Change the number 3 by your QTD var or controller

Updating a text widget using onchanged

I am trying to update a text widget as the user types, its working but i don't see the input as I type
TextFormField(
autofocus: true,
controller: amountController,
keyboardType: TextInputType.number,
cursorColor: Colors.amber,
decoration: InputDecoration(
hintText: "Enter Amount",
prefixIcon: Material(
elevation: 0,
borderRadius: BorderRadius.all(Radius.circular(30)),
child: Icon(
Icons.phone_outlined,
size: 15,
color: Color(0xff020529),
),
),
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 25, vertical: 13)),
onChanged: (value) {
add(value);
},
),
this is the widget
Text( x.toString(),
style: TextStyle(fontWeight: FontWeight.bold),
),
This is my method
add(String value) {
var ans = 36 * double.parse(value);
print('ddd');
setState(() {
x = ans;
});
What's happening is double.parse is failing because the given value can't be converted to a number.
One option to handle this is by using a try catch (see following code sample), and then setting the displayed value to 0 (or could also do something like set an error message saying something like "invalid input" if the user enters something like "123d"
add(String value) {
double ans;
try {
ans = 36 * double.parse(value);
} on FormatException catch (e) {
// string couldn't be formatted to double
ans = 0;
}
setState(() {
x = ans;
});
}
I used a try catch, but you could also check if value is empty, and/or add an onError callback to double.parse.

Variable value gets null outside setState() in Flutter

TextField(
style: TextStyle(
fontFamily: "Averta",
fontWeight: FontWeight.bold,
fontSize: 16,
color: Colors.black),
enableSuggestions: true,
onChanged: (value) {
setState(() {
final title = value;
print(title);
});
},
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.all(18),
hintText: "Enter Task Title",
hintStyle: TextStyle(
fontFamily: "Averta",
fontWeight: FontWeight.normal,
fontSize: 12,
color: Colors.grey.shade300),
),
),
Here,I have a title variable which I update with onChanged() function.
Here the print() function shows exact change in value which I do in TextField.
But I don't want to pass it here, I want to take some more data and then pass it when I click the CREATE button here-
InkWell(
onTap: () {
print(title);
setState(() {
DatabaseHelper _dbhelper =
DatabaseHelper();
Task _newTask = Task(
title: title,
year: year,
month: month,
day: daye,
hour: hour,
minute: minute,
weekday: weekday);
_dbhelper
.insertTask(_newTask);
});
}
Here the print statement shows "null",but I want to use the value which was updated above in TextField.
How can I achieve this in Flutter?
This line creates a new variable called title and sets it:
final title = value;
You want to access the already existing variable:
title = value;
That should work.

Resetting TextfieldController for all textfields, using provider

I'm having a problem trying to figuring out the proper way on how to do this. Basically in my app, I want to reset all the fields for "cleanup" by the user. I can reset everything, but the TextFields. The only way that I found to solve the problem is by using the if that you can see inside the Consumer. I don't think though it's the proper way on how to handle this type of thing.
I also thought to push inside my provider class all the controller and then reset them, but I think it's still too heavy. I'm trying to find the cleanest and lightest solution, even to learn what's the best practice in these situations.
Thanks in advance!
return Provider.of<Provider_Class>(context, listen: false).fields[_label] != null ? SizedBox(
height: 57.5,
child: Consumer<Provider_Class>(builder: (context, provider, child) {
if (provider.resetted == true) {
_controller.text = "";
}
return Material(
elevation: this.elev,
shadowColor: Colors.black,
borderRadius: new BorderRadius.circular(15),
animationDuration: new Duration(milliseconds: 500),
child: new TextField(
focusNode: _focusNode,
keyboardAppearance: Brightness.light,
style: Theme.of(context).textTheme.headline5,
controller: _controller,
keyboardType: TextInputType.number,
textAlign: TextAlign.end,
inputFormatters: <TextInputFormatter>[
LengthLimitingTextInputFormatter(8),
_whichLabel(widget.label),
],
decoration: new InputDecoration(
enabledBorder: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(15),
borderSide: new BorderSide(width: 1.2, color: CliniLiliac300),
),
focusedBorder: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(15),
borderSide: new BorderSide(width: 2.5, color: CliniLiliac300),
),
filled: true,
fillColor: Colors.white,
hintText: "0.0",
hintStyle: new TextStyle(fontSize: 15, color: Colors.black, fontFamily: "Montserrat"),
),
onChanged: (val) {
var cursorPos = _controller.selection;
val = val.replaceAll(",", ".");
if (val == "") {
provider.fields[_label] = 0.0;
} else if (double.parse(val) > provider.measure[_label] &&
provider.measure[_label] != 0) {
provider.fields[_label] % 1 == 0
? _controller.text = provider.fields[_label].toString().split(".")[0]
: _controller.text = provider.fields[_label].toString();
if (cursorPos.start > _controller.text.length) {
cursorPos = new TextSelection.fromPosition(
new TextPosition(offset: _controller.text.length),
);
}
_controller.selection = cursorPos;
} else {
provider.fields[_label] = double.parse(val);
}
provider.calculateResultRA();
},
),
);
}),
) : SizedBox();
}
Use TextFormField instead of TextField. TextFormField has several callbacks like validator, onSaved, onChanged, onEditingComplete, onSubmitted, ...
You can connect all your TextFormFields by wrapping it in a Form. This form should be given a GlobalKey so that you can identify the Form and call methods on FormState.
final _form = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
// ...
return Form(
key: _form,
child: // build Material with TextFormFields
);
}
Now to call onSaved on each TextFormField, you can call _form.currentState().save(). To reset every TextFormField you can call _form.currentState().reset().
You can get more information about how to build a Form and the functions you can call on FomState here:
https://flutter.dev/docs/cookbook/forms/validation
https://api.flutter.dev/flutter/widgets/FormState-class.html

how to save input value of text form field as soon as type completed in flutter

i have a text form field that user enter to it a code that sent to it and i must decide if input code is true or false but can not save value of text form field i have a button like it
GestureDetector(
onTap: () {
_formKey.currentState.save();
setState(() {
inputcode=key.getter("sms");
print(inputcode);
print(verifycode);
});
},
child: inputcode==verifycode?send:
VirtualBotton("please enter correct code","ok",40.0,10.0,width: 120.0,),
)
but i must press two times button for done this work
in this code when i enter correct code first run virtual Button class and second times that i press button run correct code how to run on tap before build child in button this is text form field
TextFormField(
inputFormatters: [
new LengthLimitingTextInputFormatter(30),
],
onSaved: (value) {
key.setter(id, value);
},
textAlign: alignment == null ? TextAlign.right : alignment,
maxLines: maxlines==null?1:maxlines,
style: TextStyle(
fontSize: 14,
),
textDirection: TextDirection.rtl,
controller: ctrl,
validator: (String value) {
if (value.isEmpty) {
return "";
}
return null;
},
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: const EdgeInsets.all(0.0),
hintText: _hint,
errorStyle: TextStyle(height: 0),
hintStyle: TextStyle(
color: hintconlor == null ? Colors.grey : hintconlor,
fontSize: hintsize == null ? 13 : hintsize)),
),
);
you can add TextEditingController and listen to controller changes. Here are docs with examples