Convert value to number format in Flutter input? - flutter

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.

Related

How to format initial value for TextFormField using formatter in Flutter?

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
)
)

How to make a TextInputFormatter Mask for IpAddress in flutter

I am trying to make a TextInputFormatter Mask for IpAddress in flutter
What I am trying to do :
import 'package:flutter/services.dart';
class IpAddressInputFormatter extends TextInputFormatter {
String separator = '-';
#override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
if (newValue.text.length > 0) {
if (newValue.text.length > oldValue.text.length) {
String lastEnteredChar = newValue.text.substring(newValue.text.length - 1);
var value = newValue.text;
if (lastEnteredChar == separator) {
if (RegExp(separator).allMatches(newValue.text).length > 3) {
return oldValue;
}
if (newValue.text.length > 9) {
return oldValue;
}
return TextEditingValue(
text: '$value',
selection: TextSelection.collapsed(
offset: value.length,
),
);
}
if (!_isNumeric(lastEnteredChar)) return oldValue;
if (_isNumeric(newValue.text.split(separator).last) && newValue.text.split(separator).last.length == 3) {
if (newValue.text.length < 16 && newValue.text.split(separator).last.length <= 3) {
var s = '$separator';
s = newValue.text.length == 15 ? '' : s;
s = RegExp(separator).allMatches(newValue.text).length > 2 ? '' : s;
value = '${newValue.text}$s';
} else if (RegExp(separator).allMatches(newValue.text).length > 2) {
return oldValue;
} else {
return oldValue;
}
} else if (newValue.text.length > 15) {
return oldValue;
} else if (RegExp(separator).allMatches(newValue.text).length == 3 &&
newValue.text.split(separator).last.length > 3) {
return oldValue;
}
return TextEditingValue(
text: '$value',
selection: TextSelection.collapsed(
offset: value.length,
),
);
}
}
return newValue;
}
bool _isNumeric(String s) {
if (s == null) return false;
return double.tryParse(s) != null;
}
}
I expect the user to be able to enter 55.55.55.55 | 1.1.1.1 | 1.10.100.4 | etc
Is there a easy way to make an input text fiel for Ip Addresses ?
this code works for some scenarios but not for all ip scenarios, it works well for
10-10-10-10
111-111-111-111
192-168-133-13
1-1-1-1
1-10-10-1
but fails from time to time for
10-100-10-1
Surely it can be improved but it works for me like this.
inputFormatters: [
MyInputFormatters.ipAddressInputFilter(),
LengthLimitingTextInputFormatter(15),
IpAddressInputFormatter()
],
MyInputFormatters.class
class MyInputFormatters {
static TextInputFormatter ipAddressInputFilter() {
return FilteringTextInputFormatter.allow(RegExp("[0-9.]"));
}
}
IpAddressInputFormatter.class
class IpAddressInputFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
var text = newValue.text;
if (newValue.selection.baseOffset == 0) {
return newValue;
}
int dotCounter = 0;
var buffer = StringBuffer();
String ipField = "";
for (int i = 0; i < text.length; i++) {
if (dotCounter < 4) {
if (text[i] != ".") {
ipField += text[i];
if (ipField.length < 3) {
buffer.write(text[i]);
} else if (ipField.length == 3) {
if (int.parse(ipField) <= 255) {
buffer.write(text[i]);
} else {
if (dotCounter < 3) {
buffer.write(".");
dotCounter++;
buffer.write(text[i]);
ipField = text[i];
}
}
} else if (ipField.length == 4) {
if (dotCounter < 3) {
buffer.write(".");
dotCounter++;
buffer.write(text[i]);
ipField = text[i];
}
}
} else {
if (dotCounter < 3) {
buffer.write(".");
dotCounter++;
ipField = "";
}
}
}
}
var string = buffer.toString();
return newValue.copyWith(
text: string,
selection: TextSelection.collapsed(offset: string.length));
}
}

Allow only enter 3 decimal number flutter

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",
),
),
),
],
),
),
);
}
}

Thousand separator without decimal separator on TextFormField flutter

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: ',');

Flutter – formatting phone number text field

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();
}