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;
}
}
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",
),
),
),
],
),
),
);
}
}
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: ',');
I am trying to make a text field that properly formats a phone number. I have tried using
NumberFormat("+# ### ### ####");
But it doesn't retain spaces
I have tried simplifying it by just adding a + to the front but I cannot backspace when I set the offset.
class PhoneInputFormatter extends TextInputFormatter {
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
final text = newValue.text.replaceAll(RegExp(r'\D'), '');
final offset = text.length + 1;
return newValue.copyWith(
text: text.length >= 1 ? '+$text' : '',
selection: TextSelection.collapsed(offset: offset),
);
}
}
Any help would be appreciated
This Should Work :
class NumberTextInputFormatter 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 >= 1) {
newText.write('+');
if (newValue.selection.end >= 1) selectionIndex++;
}
if (newTextLength >= 3) {
newText.write(newValue.text.substring(0, usedSubstringIndex = 2) + ' ');
if (newValue.selection.end >= 2) selectionIndex += 1;
}
// Dump the rest.
if (newTextLength >= usedSubstringIndex)
newText.write(newValue.text.substring(usedSubstringIndex));
return new TextEditingValue(
text: newText.toString(),
selection: new TextSelection.collapsed(offset: selectionIndex),
);
}
}
final _mobileFormatter = NumberTextInputFormatter();
TextFormField(
keyboardType: TextInputType.phone,
maxLength: 15,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly,
_mobileFormatter,
],
decoration: InputDecoration(
icon: Icon(Icons.phone_iphone),
hintText: "Mobile*",
),
)
Here's a light weight approach (does not work correctly on older Android OS KitKit) where you can set the specific format you want with the MaskedInputFormater class, using the plugin: flutter_multi_formatter
import 'package:flutter_multi_formatter/flutter_multi_formatter.dart';
TextFormField(
keyboardType: TextInputType.phone,
autocorrect: false,
inputFormatters: [
MaskedInputFormater('(###) ###-####')
],
// .. etc
);
In my case, app is only domestic to start, so I don't want any international code in the phone number UI. All the plugins out there seem to expect that.
========================
Update 1
Just tested this on the older Android KitKat, and unfortunately doesn't work correctly there.
However, depending on the app and the audience - if you know most users will have a later OS, this is not a bad solution for getting something out there.
Solution for RU numbers.
We have identical numbers, but written in different ways. 8900.. = +7900..
Also if we start typing with 9, it can automatically become 9.. > +79..
So, this code result will be: +7(900)000-00-00
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class NumberTextInputFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
final newTextLength = newValue.text.length;
int selectionIndex = newValue.selection.end;
int usedSubstringIndex = 1;
final newTextBuffer = StringBuffer();
if (newTextLength >= 1) {
if (newValue.text.startsWith(RegExp(r'[789]'))) {
newTextBuffer.write('+7');
if (newValue.text.startsWith('9')) {
newTextBuffer.write('(9');
selectionIndex = 4;
}
if (newValue.selection.end >= 1) selectionIndex++;
}
}
if (newTextLength >= 2) {
newTextBuffer
.write('(' + newValue.text.substring(1, usedSubstringIndex = 2));
if (newValue.selection.end >= 2) selectionIndex++;
}
if (newTextLength >= 5) {
newTextBuffer.write(
newValue.text.substring(usedSubstringIndex, usedSubstringIndex = 4) +
')');
if (newValue.selection.end >= 4) selectionIndex++;
}
if (newTextLength >= 8) {
newTextBuffer.write(
newValue.text.substring(usedSubstringIndex, usedSubstringIndex = 7) +
'-');
if (newValue.selection.end >= 7) selectionIndex++;
}
if (newTextLength >= 10) {
newTextBuffer.write(
newValue.text.substring(usedSubstringIndex, usedSubstringIndex = 9) +
'-');
if (newValue.selection.end >= 9) selectionIndex++;
}
// Dump the rest.
if (newTextLength > usedSubstringIndex) newTextBuffer.write(newValue.text.substring(usedSubstringIndex, newTextLength));
return TextEditingValue(
text: newTextBuffer.toString(),
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
}
Use the intl_phone_number_input package from pub.dev. I think it's easy.
follow this link
Because of the android keyboard issue, I've had better luck with my own custom TextInputFormatter. TextFormField has a parameter for inputFormatters which takes a list of formatters. The code I wrote could probably be written in a way that is easier to read without all the ternary operators, but here goes nothing.
class InternationalPhoneFormatter extends TextInputFormatter {
String internationalPhoneFormat(value) {
String nums = value.replaceAll(RegExp(r'[\D]'), '');
String internationalPhoneFormatted = nums.length >= 1
? '+' + nums.substring(0, nums.length >= 1 ? 1 : null) + (nums.length > 1 ? ' (' : '') + nums.substring(1, nums.length >= 4 ? 4 : null)
+ (nums.length > 4 ? ') ' : '') + (nums.length > 4
? nums.substring(4, nums.length >= 7 ? 7 : null) + (nums.length > 7
? '-' + nums.substring(7, nums.length >= 11 ? 11 : null)
: '')
: '')
: nums;
return internationalPhoneFormatted;
}
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
String text = newValue.text;
if (newValue.selection.baseOffset == 0) {
return newValue;
}
return newValue.copyWith(
text: internationalPhoneFormat(text),
selection: new TextSelection.collapsed(offset: internationalPhoneFormat(text).length)
);
}
}
Use this class as an inputerFormatter argument for your TextForm Field and it will format it as +x (xxx) xxx-xxxx
(US only but easily modifiable) I would recommend only storing digits in your model and formatting the number specifically for the view. For that, I did the following:
/// Human readable version of the phone number
String getFormattedPhoneNumber() {
if (_phoneNumber.isEmpty) {
return "";
}
String phoneNumber = _phoneNumber;
bool addPlus = phoneNumber.startsWith("1");
if (addPlus) phoneNumber = phoneNumber.substring(1);
bool addParents = phoneNumber.length >= 3;
bool addDash = phoneNumber.length >= 8;
// +1
String updatedNumber = "";
if (addPlus) updatedNumber += "+1";
// (222)
if (addParents) {
updatedNumber += "(";
updatedNumber += phoneNumber.substring(0, 3);
updatedNumber += ")";
} else {
updatedNumber += phoneNumber.substring(0);
return updatedNumber;
}
// 111
if (addDash) {
updatedNumber += phoneNumber.substring(3, 6);
updatedNumber += "-";
} else {
updatedNumber += phoneNumber.substring(3);
return updatedNumber;
}
// 3333
updatedNumber += phoneNumber.substring(6);
return updatedNumber;
}
And in your TextInput onChanged method:
void setPhoneNumber(String phoneNumber) {
if (phoneNumber.contains("\(") && !phoneNumber.contains("\)")) {
// Remove the digit the user wanted to remove but couldn't b/c a paren
// was in the way.
phoneNumber = phoneNumber.substring(0, phoneNumber.length - 1);
}
// Only store the actual digits
phoneNumber = phoneNumber.replaceAll(RegExp("[^0-9]"), "");
// Don't let the user enter more digits than is possible
int maxLength = phoneNumber.startsWith("1") ? 11 : 10;
if (phoneNumber.length > maxLength) {
phoneNumber = phoneNumber.substring(0, maxLength);
}
model.setPhoneNumber(phoneNumber);
// Notify the UI to update based on new input
notifyListeners();
}