Am using flutter_masked_text in order to format my controller to automatically add thousand separator to my currency field. Am using this to achieve that.
var controller = new MoneyMaskedTextController(decimalSeparator: '.', thousandSeparator: ',');
I don't like the way it works because it starts from 0.00 and automatically starts adding digits from the decimal section. If I type 1000, it should become 1,000 not 1,000.00. Is there a way I can format my controller field to add thousand separator without decimal separator?
I have the same problem, I found a custom input formatter code way before as a temporary solution that does the same thing, then I modified it for this specific experience. You could try this if it helps and feel free to optimize it.
class DecimalFormatter extends TextInputFormatter {
final int decimalDigits;
DecimalFormatter({this.decimalDigits = 2}) : assert(decimalDigits >= 0);
#override
TextEditingValue formatEditUpdate(TextEditingValue oldValue,
TextEditingValue newValue,) {
String newText;
if (decimalDigits == 0) {
newText = newValue.text.replaceAll(RegExp('[^0-9]'), '');
}
else {
newText = newValue.text.replaceAll(RegExp('[^0-9\.]'), '');
}
if(newText.contains('.')) {
//in case if user's first input is "."
if (newText.trim() == '.') {
return newValue.copyWith(
text: '0.',
selection: TextSelection.collapsed(offset: 2),
);
}
//in case if user tries to input multiple "."s or tries to input
//more than the decimal place
else if (
(newText.split(".").length > 2)
|| (newText.split(".")[1].length > this.decimalDigits)
) {
return oldValue;
}
else return newValue;
}
//in case if input is empty or zero
if (newText.trim() == '' || newText.trim() == '0') {
return newValue.copyWith(text: '');
}
else if (int.parse(newText) < 1) {
return newValue.copyWith(text: '');
}
double newDouble = double.parse(newText);
var selectionIndexFromTheRight =
newValue.text.length - newValue.selection.end;
String newString = NumberFormat("#,##0.##").format(newDouble);
return TextEditingValue(
text: newString,
selection: TextSelection.collapsed(
offset: newString.length - selectionIndexFromTheRight,
),
);
}
}
I used a custom text input formatter to do something like that :
class CustomTextInputFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
if (newValue.text.length == 0) {
return newValue.copyWith(text: '');
} else if (newValue.text.compareTo(oldValue.text) != 0) {
int selectionIndexFromTheRight =
newValue.text.length - newValue.selection.extentOffset;
List<String> chars = newValue.text.replaceAll(' ', '').split('');
String newString = '';
for (int i = 0; i < chars.length; i++) {
if (i % 3 == 0 && i != 0) newString += ' ';
newString += chars[i];
}
return TextEditingValue(
text: newString,
selection: TextSelection.collapsed(
offset: newString.length - selectionIndexFromTheRight,
),
);
} else {
return newValue;
}
}
}
Then on your TextField:
TextField(
controller: _textController,
keyboardType: TextInputType.number,
inputFormatters: [CustomTextInputFormatter()],
)
I never tried this package, however i can see that MoneyMaskedTextController() has a precision parameter.
try something like that:
var controller = new MoneyMaskedTextController(precision: 0, decimalSeparator: '.', thousandSeparator: ',');
Related
I tried this solution from the other question: How to: add a blank space after every 4 characters when typing in TextFormField
and changed only the 4 to 1. But this solution has some issues like here mention: How to: add a blank space after every 4 characters when typing in TextFormField
class CustomInputFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
var text = newValue.text;
if (newValue.selection.baseOffset == 0) {
return newValue;
}
var buffer = StringBuffer();
for (int i = 0; i < text.length; i++) {
buffer.write(text[i]);
var nonZeroIndex = i + 1;
if (nonZeroIndex % 1 == 0 && nonZeroIndex != text.length) {
buffer.write(' ');
}
}
var string = buffer.toString();
return newValue.copyWith(
text: string,
selection: TextSelection.collapsed(offset: string.length));
}
}
inputFormatters: [CustomInputFormatter()],
You can replace all spaces and add spaces in a loop. Also the last space isn't added so you can delete the values
class CustomInputFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
var text = newValue.text;
print(text);
if (newValue.selection.baseOffset == 0) {
return newValue;
}
var buffer = StringBuffer();
text = text.replaceAll(" ", "");
for (int i = 0; i < text.length; i++) {
buffer.write(text[i]);
if(i < (text.length-1))buffer.write(" ");
}
var string = buffer.toString();
return newValue.copyWith(
text: string,
selection: TextSelection.collapsed(offset: string.length)
);
}
}
I have a text field in which users enter sums. And I want these sums to be formatted by thousands (for example 500 000). I found a formatter that can do that. The problem is that this formatter is not called, when initial value is set in controller. As a result text is not formatted until user modifies it.
For example, if we set initial value as 500000 then text field shows 500000 instead of
500 000 until user modifies this value.
This is my code:
class ThousandTextInputFormatter extends TextInputFormatter {
static const separator = ','; // Change this to '.' for other locales
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
// Short-circuit if the new value is empty
if (newValue.text.length == 0) {
return newValue.copyWith(text: '');
}
// Handle "deletion" of separator character
String oldValueText = oldValue.text.replaceAll(separator, '');
String newValueText = newValue.text.replaceAll(separator, '');
if (oldValue.text.endsWith(separator) &&
oldValue.text.length == newValue.text.length + 1) {
newValueText = newValueText.substring(0, newValueText.length - 1);
}
// Only process if the old value and new value are different
if (oldValueText != newValueText) {
int selectionIndex =
newValue.text.length - newValue.selection.extentOffset;
final chars = newValueText.split('');
String newString = '';
for (int i = chars.length - 1; i >= 0; i--) {
if ((chars.length - 1 - i) % 3 == 0 && i != chars.length - 1)
newString = separator + newString;
newString = chars[i] + newString;
}
return TextEditingValue(
text: newString.toString(),
selection: TextSelection.collapsed(
offset: newString.length - selectionIndex,
),
);
}
// If the new value and old value are the same, just return as-is
return newValue;
}
}
...
var sumController = TextEditingController(text: 500000.toString());//setting initial value
...
TextFormField(
inputFormatters: [ThousandTextInputFormatter()],
controller: sumController,
)
Could anyone say how to format initial value using formatter, if it is possible?
I made it work this way:
sumController.value = ThousandTextInputFormatter()
.formatEditUpdate(TextEditingValue.empty, TextEditingValue(text: 500000.toString()));
Try adding a value to the textController instead of setting _textController.text
_textEditingController.value = TextEditingValue(text: "500000");
EDIT
myTextEditingController.value.copyWith(
text: "5000000",
selection: TextSelection(
baseOffset: "5000000".length,
extentOffset: "5000000.length
)
)
I want to force user to enters only one dot and 3 decimal points.
I found code below:
class NumberRemoveExtraDotFormatter extends TextInputFormatter {
NumberRemoveExtraDotFormatter({this.decimalRange = 3}) : assert(decimalRange == null || decimalRange > 0);
final int decimalRange;
#override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
var nValue = newValue.text;
var nSelection = newValue.selection;
Pattern p = RegExp(r'(\d+\.?)|(\.?\d+)|(\.?)');
nValue = p.allMatches(nValue).map<String>((Match match) => match.group(0)).join();
if (nValue.startsWith('.')) {
nValue = '0.';
} else if (nValue.contains('.')) {
if (nValue.substring(nValue.indexOf('.') + 1).length > decimalRange) {
nValue = oldValue.text;
} else {
if (nValue.split('.').length > 2) {
var split = nValue.split('.');
nValue = split[0] + '.' + split[1];
}
}
}
nSelection = newValue.selection.copyWith(
baseOffset: math.min(nValue.length, nValue.length + 1),
extentOffset: math.min(nValue.length, nValue.length + 1),
);
return TextEditingValue(text: Utils.addCommad(nValue), selection: nSelection, composing: TextRange.empty);
}
}
but the problem is when user enters more than 3 decimal points and then want to remove, it doesn't. because numbers save in textformfield and they to remove until they reach to 3 decimal points and also when typing from middle of input cursor jump to end.
Also I want to shift number out from right if user enter more than 3 decimal points.
How can I achieve this?
If you just want to force user to enters only one dot and 3 decimal points, this could work.
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,3}'))
According to your comment:
How to add thousands separator?
Shifting number out not work. I want to shift number out if user start to typing in decimal part when decimal point reached at maximum. e.g. current value is 0.333 and user set cursor at second 3 (0.3|33) and type 2. then value must be 0.323.
We can use intl NumberFormat to format the number.
This is my code, I did not have a thorough and detailed test. If you find any bugs, please point them out.
UPDATE
when enter long number whit 0 maximumFractionDigits, wrong number will added. => this is not depends on maximumFractionDigits. it's happening always.
I think there has some unexpected behavior in the NumberFormat, and I changed it to custom method and it support negative number now.
class NumberInputFormatter extends TextInputFormatter {
final int maximumFractionDigits;
NumberInputFormatter({
this.maximumFractionDigits = 3,
}) : assert(maximumFractionDigits != null && maximumFractionDigits >= 0);
#override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
var newText = newValue.text;
var selectionOffset = newValue.selection.extent.offset;
bool isNegative = false;
if (newText.startsWith('-')) {
newText = newText.substring(1);
isNegative = true;
}
if (newText.isEmpty) {
return newValue;
}
if (newText.indexOf('.') != newText.lastIndexOf('.')) {
// inputted more than one dot.
return oldValue;
}
if (newText.startsWith('.') && maximumFractionDigits > 0) {
newText = '0$newText';
selectionOffset += 1;
}
while (newText.length > 1 && !newText.startsWith('0.') && newText.startsWith('0')) {
newText = newText.substring(1);
selectionOffset -= 1;
}
if (_decimalDigitsOf(newText) > maximumFractionDigits) {
// delete the extra digits.
newText = newText.substring(0, newText.indexOf('.') + 1 + maximumFractionDigits);
}
if (newValue.text.length == oldValue.text.length - 1 &&
oldValue.text.substring(newValue.selection.extentOffset, newValue.selection.extentOffset + 1) == ',') {
// in this case, user deleted the thousands separator, we should delete the digit number before the cursor.
newText = newText.replaceRange(newValue.selection.extentOffset - 1, newValue.selection.extentOffset, '');
selectionOffset -= 1;
}
if (newText.endsWith('.')) {
// in order to calculate the selection offset correctly, we delete the last decimal point first.
newText = newText.replaceRange(newText.length - 1, newText.length, '');
}
int lengthBeforeFormat = newText.length;
newText = _removeComma(newText);
if (double.tryParse(newText) == null) {
// invalid decimal number
return oldValue;
}
newText = _addComma(newText);
selectionOffset += newText.length - lengthBeforeFormat; // thousands separator newly added
if (maximumFractionDigits > 0 && newValue.text.endsWith('.')) {
// decimal point is at the last digit, we need to append it back.
newText = '$newText.';
}
if (isNegative) {
newText = '-$newText';
}
return TextEditingValue(
text: newText,
selection: TextSelection.collapsed(offset: min(selectionOffset, newText.length)),
);
}
static int _decimalDigitsOf(String text) {
var index = text?.indexOf('.') ?? -1;
return index == -1 ? 0 : text.length - index - 1;
}
static String _addComma(String text) {
StringBuffer sb = StringBuffer();
var pointIndex = text.indexOf('.');
String integerPart;
String decimalPart;
if (pointIndex >= 0) {
integerPart = text.substring(0, pointIndex);
decimalPart = text.substring(pointIndex);
} else {
integerPart = text;
decimalPart = '';
}
List<String> parts = [];
while (integerPart.length > 3) {
parts.add(integerPart.substring(integerPart.length - 3));
integerPart = integerPart.substring(0, integerPart.length - 3);
}
parts.add(integerPart);
sb.writeAll(parts.reversed, ',');
sb.write(decimalPart);
return sb.toString();
}
static String _removeComma(String text) {
return text.replaceAll(',', '');
}
}
Try using this:
FilteringTextInputFormatter(RegExp(r'(^[0-9]*(?:\.[0-9]{0,3})?$)'), allow: true),
Basically the regex will try to match 0 or more occurences of digits followed by optional decimal followed by upto 3 digits after decimal. You can modify it to use negative value also ^(?:\-)?[0-9]*(?:\.[0-9]{0,3})?$.
full code is here,
(update you can also change for data by cursor)
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:math' as math;
class DecimalChecker extends TextInputFormatter {
DecimalChecker({this.decimalRange = 3})
: assert(decimalRange == null || decimalRange > 0);
final int decimalRange;
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
String valueTxt = newValue.text;
TextSelection valueSet = newValue.selection;
var newlength = newValue.text.length;
var oldlength = oldValue.text.length;
if (oldlength < newlength) {
Pattern p = RegExp(r'(\d+\.?)|(\.?\d+)|(\.?)');
valueTxt = p
.allMatches(valueTxt)
.map<String>((Match match) => match.group(0))
.join();
print("------>");
if (valueTxt.startsWith('.')) {
valueTxt = '0.';
} else if (valueTxt.contains('.')) {
if (valueTxt.substring(valueTxt.indexOf('.') + 1).length >
decimalRange) {
valueTxt = oldValue.text;
} else {
if (valueTxt.split('.').length > 2) {
List<String> split = valueTxt.split('.');
valueTxt = split[0] + '.' + split[1];
}
}
}
valueSet = newValue.selection.copyWith(
baseOffset: math.min(valueTxt.length, valueTxt.length + 1),
extentOffset: math.min(valueTxt.length, valueTxt.length + 1),
);
return TextEditingValue(
text: valueTxt, selection: valueSet, composing: TextRange.empty);
} else {
return TextEditingValue(
text: valueTxt, selection: valueSet, composing: TextRange.empty);
}
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'My Decimal Check App'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController numberController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 0.0),
child: TextField(
controller: numberController,
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [DecimalChecker()],
decoration: InputDecoration(
hintText: "Please enter Number",
),
),
),
],
),
),
);
}
}
I have a problem with Flutter InputTextField. I'm developing an app like CashApp were you have a feature which you can send money to other people.
The problem is: I need to achieve a number format and allow only two decimals.
For example, if I enter:
"1000,25" -> It needs to transform to 1.000,25
"10000,25" -> It needs to transform to 10.000,25
And so on..
I've been using this code to reach the only two decimals part
import 'package:flutter/services.dart';
import 'dart:math' as math;
class DecimalTextInputFormatter extends TextInputFormatter {
DecimalTextInputFormatter({this.decimalRange, this.activatedNegativeValues})
: assert(decimalRange == null || decimalRange >= 0,
'DecimalTextInputFormatter declaretion error');
final int decimalRange;
final bool activatedNegativeValues;
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, // unused.
TextEditingValue newValue,
) {
TextSelection newSelection = newValue.selection;
String truncated = newValue.text;
if (newValue.text.contains(' ')) {
return oldValue;
}
if (newValue.text.isEmpty) {
return newValue;
} else if (double.tryParse(newValue.text) == null &&
!(newValue.text.length == 1 &&
(activatedNegativeValues == true ||
activatedNegativeValues == null) &&
newValue.text == '-')) {
return oldValue;
}
if (activatedNegativeValues == false &&
double.tryParse(newValue.text) < 0) {
return oldValue;
}
if (decimalRange != null) {
String value = newValue.text;
if (decimalRange == 0 && value.contains(".")) {
truncated = oldValue.text;
newSelection = oldValue.selection;
}
if (value.contains(".") &&
value.substring(value.indexOf(".") + 1).length > decimalRange) {
truncated = oldValue.text;
newSelection = oldValue.selection;
} else if (value == ".") {
truncated = "0.";
newSelection = newValue.selection.copyWith(
baseOffset: math.min(truncated.length, truncated.length + 1),
extentOffset: math.min(truncated.length, truncated.length + 1),
);
}
return TextEditingValue(
text: truncated,
selection: newSelection,
composing: TextRange.empty,
);
}
return newValue;
}
}
This is a response I found from this post
This code almost do the thing. It restrict the two decimals part, but doesn't have the NumberFormat part, and instead of 'commas' for the decimals, it uses 'dots'.
I want to swap the '.' for thousands and ',' for decimals. And add the NumberFormat too.
Is there any way I can achieve this?
Is this for locale Brazil? Then the number currency format of Brazil could be set in you DecimalTextInputFormatter class to achieve the same result.
Is there a way to make a thousand separator when typing numbers in a TextFormField in flutter?
This is my TextFormField
child: TextFormField(
decoration: InputDecoration(
border: const OutlineInputBorder()),
keyboardType: TextInputType.number,
),
First add intl flutter package
dependencies:
intl: ^0.16.1
Now use NumberFormat
var formatter = new NumberFormat("#,###");
print(formatter.format(1234)), // this will be: 1,234
There isn't anything built into Flutter to handle this. You will have to roll with your own customized text formatter. (Derived from this answer.)
import 'package:flutter/services.dart';
class ThousandsSeparatorInputFormatter extends TextInputFormatter {
static const separator = ','; // Change this to '.' for other locales
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
// Short-circuit if the new value is empty
if (newValue.text.length == 0) {
return newValue.copyWith(text: '');
}
// Handle "deletion" of separator character
String oldValueText = oldValue.text.replaceAll(separator, '');
String newValueText = newValue.text.replaceAll(separator, '');
if (oldValue.text.endsWith(separator) &&
oldValue.text.length == newValue.text.length + 1) {
newValueText = newValueText.substring(0, newValueText.length - 1);
}
// Only process if the old value and new value are different
if (oldValueText != newValueText) {
int selectionIndex =
newValue.text.length - newValue.selection.extentOffset;
final chars = newValueText.split('');
String newString = '';
for (int i = chars.length - 1; i >= 0; i--) {
if ((chars.length - 1 - i) % 3 == 0 && i != chars.length - 1)
newString = separator + newString;
newString = chars[i] + newString;
}
return TextEditingValue(
text: newString.toString(),
selection: TextSelection.collapsed(
offset: newString.length - selectionIndex,
),
);
}
// If the new value and old value are the same, just return as-is
return newValue;
}
}
Usage:
TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.number,
inputFormatters: [ThousandsSeparatorInputFormatter()],
),
Example: https://codepen.io/Abion47/pen/mdVLgGP
The answer from #Abion47 works well except if your value contains decimal places. This adaptation handles decimals too.
class ThousandsSeparatorInputFormatter extends TextInputFormatter {
static const separator = ','; // Change this to '.' for other locales
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
// Short-circuit if the new value is empty
if (newValue.text.length == 0) {
return newValue.copyWith(text: '');
}
// Handle "deletion" of separator character
String oldValueText = oldValue.text.replaceAll(separator, '');
String newValueText = newValue.text.replaceAll(separator, '');
if (oldValue.text.endsWith(separator) &&
oldValue.text.length == newValue.text.length + 1) {
newValueText = newValueText.substring(0, newValueText.length - 1);
}
// Only process if the old value and new value are different
if (oldValueText != newValueText) {
// Split the string into its integer and decimal parts
List<String> parts = newValueText.split('.');
int selectionIndex =
newValue.text.length - newValue.selection.extentOffset;// + (parts.length > 1 ? parts[1].length : 0);
final chars = parts[0].split('');
String newString = '';
for (int i = chars.length - 1; i >= 0; i--) {
if ((chars.length - 1 - i) % 3 == 0 && i != chars.length - 1)
newString = separator + newString;
newString = chars[i] + newString;
}
return TextEditingValue(
text: newString.toString() + (parts.length > 1 ? '.' + parts[1] : ''),
selection: TextSelection.collapsed(
offset: newString.length - selectionIndex + (parts.length > 1 ? parts[1].length + 1 : 0),
),
);
}
// If the new value and old value are the same, just return as-is
return newValue;
}
}