Flutter TextFormField decoration - flutter

How to display the number "123456789" as "123 456 789" in TextFormfield?
TextFormField(
controller: _numberId,
keyboardType: TextInputType.number,
),

I write below extention on String :
extension StringSeprate on String {
String stringSeparate(
{int count = 3, String separator = ",", bool fromRightToLeft = true}) {
if (this.isEmpty) {
return "";
}
if (count < 1) {
return this;
}
if (count >= this.length) {
return this;
}
var str = this.replaceAll(separator, "");
if (fromRightToLeft) {
str = String.fromCharCodes(str.runes.toList().reversed);
}
var chars = str.runes.toList();
var namOfSeparation =
(chars.length.toDouble() / count.toDouble()).ceil() - 1;
var separatedChars = List(chars.length + namOfSeparation.round());
var j = 0;
for (var i = 0; i < chars.length; i++) {
separatedChars[j] = String.fromCharCode(chars[i]);
if (i > 0 && (i + 1) < chars.length && (i + 1) % count == 0) {
j += 1;
separatedChars[j] = separator;
}
j += 1;
}
return fromRightToLeft
? String.fromCharCodes(separatedChars.join().runes.toList().reversed)
: separatedChars.join();
}
}
final code (copy below code in your file and call it from main for test):
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class TestWidget extends StatelessWidget {
TextEditingController _textEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[200],
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _textEditingController,
inputFormatters: [
TextInputFormatter.withFunction((oldValue, newValue) =>
TextEditingValue(
text: newValue.text.stringSeparate(
separator: ' ', fromRightToLeft: false)))
],
onChanged: (String value) {
if (value == null) return;
_textEditingController.selection = TextSelection.fromPosition(
TextPosition(
offset: value.length, affinity: TextAffinity.upstream));
},
),
],
),
),
);
}
}
extension StringSeprate on String {
String stringSeparate(
{int count = 3, String separator = ",", bool fromRightToLeft = true}) {
if (this.isEmpty) {
return "";
}
if (count < 1) {
return this;
}
if (count >= this.length) {
return this;
}
var str = this.replaceAll(separator, "");
var chars = str.runes.toList();
var namOfSeparation =
(chars.length.toDouble() / count.toDouble()).ceil() - 1;
var separatedChars = List(chars.length + namOfSeparation.round());
var j = 0;
for (var i = 0; i < chars.length; i++) {
separatedChars[j] = String.fromCharCode(chars[i]);
if (i > 0 && (i + 1) < chars.length && (i + 1) % count == 0) {
j += 1;
separatedChars[j] = separator;
}
j += 1;
}
return fromRightToLeft
? String.fromCharCodes(separatedChars.join().runes.toList().reversed)
: separatedChars.join();
}
}
result is like :

Related

Why is it cannot refer?

Why is it cannot refer String tit, kit??
after elementAt() instance has MANUAL01..MANUAL02
I want refer last element on next code.
snapshot.data!.COOKRCP02.row.elementAt(index).MANUAL01 <-- this
code
manualList(snapshot, index) {
{
for (int i = 1; i < 20; i++) {
String tik = 'MANUAL'+i.toString(); // it says unused_local_variable
String kik = 'MANUALIMG'+i.toString(); // it says unused_local_variable
if(i<10){
tik = 'MANUAL0'+i.toString();
kik = 'MANUALIMG0'+i.toString();
}
var a = snapshot.data!.COOKRCP02.row.elementAt(index).tik; //<--
var b = snapshot.data!.COOKRCP02.row.elementAt(index).kik; //<--
if (a != null && b != null) {
return Column(
children: [
Text(a),
Image.network(b),
],
);
} else if (a != null && b == null) {
return Column(
children: [
Text(a),
],
);
}
}
}
}
error
Row Class

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 in flutter

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

Flutter inputformatter for date

I am looking for an example of an inputformatter for text field that will be a date mm/dd/yyyy, what I am trying to do as the user types update the format. For instance user starts to type mm and the / is put in, then when the dd is typed in the / is put in.
Anyone done this or have an example? I have done it in other languages but could not find a similar way to do in flutter/dart.
This is what I have tried so far, but can not get the logic correct. Any ideas?
class _DateFormatter 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 == 2) {
newText.write(newValue.text.substring(0, 2) + '/ ');
if (newValue.selection.end == 3)
selectionIndex+=3;
}
if (newTextLength == 5) {
newText.write(newValue.text.substring(0, 5) + '/ ');
if (newValue.selection.end == 6)
selectionIndex += 6;
}
// Dump the rest.
if (newTextLength >= usedSubstringIndex)
newText.write(newValue.text.substring(usedSubstringIndex));
return new TextEditingValue(
text: newText.toString(),
selection: new TextSelection.collapsed(offset: selectionIndex),
);
}
}
Thanks
I was struggling with this too. I ended up with the following not so elegant solution:
class DateInputTextField extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _DateInputTextFieldState();
}
}
class _DateInputTextFieldState extends State<DateInputTextField> {
#override
Widget build(BuildContext context) {
return TextField(
keyboardType: TextInputType.number,
inputFormatters: [DateTextFormatter()],
onChanged: (String value) {},
);
}
}
class DateTextFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
//this fixes backspace bug
if (oldValue.text.length >= newValue.text.length) {
return newValue;
}
var dateText = _addSeperators(newValue.text, '/');
return newValue.copyWith(text: dateText, selection: updateCursorPosition(dateText));
}
String _addSeperators(String value, String seperator) {
value = value.replaceAll('/', '');
var newString = '';
for (int i = 0; i < value.length; i++) {
newString += value[i];
if (i == 1) {
newString += seperator;
}
if (i == 3) {
newString += seperator;
}
}
return newString;
}
TextSelection updateCursorPosition(String text) {
return TextSelection.fromPosition(TextPosition(offset: text.length));
}
}
Here is an improved version based on Jochem Toolenaar answer. This version will automatically remove the forward slashes for you and limit the user to 8 digits.
class DateTextFormatter extends TextInputFormatter {
static const _maxChars = 8;
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
var text = _format(newValue.text, '/');
return newValue.copyWith(text: text, selection: updateCursorPosition(text));
}
String _format(String value, String seperator) {
value = value.replaceAll(seperator, '');
var newString = '';
for (int i = 0; i < min(value.length, _maxChars); i++) {
newString += value[i];
if ((i == 1 || i == 3) && i != value.length - 1) {
newString += seperator;
}
}
return newString;
}
TextSelection updateCursorPosition(String text) {
return TextSelection.fromPosition(TextPosition(offset: text.length));
}
}
Adding an updated version based on Arizona1911 and Jochem Toolenaar versions.
This one prevents the caret from jumping when modifying some of the text that was already typed.
class DateTextFormatter extends TextInputFormatter {
static const _maxChars = 8;
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
String separator = '/';
var text = _format(
newValue.text,
oldValue.text,
separator,
);
return newValue.copyWith(
text: text,
selection: updateCursorPosition(
oldValue,
text,
),
);
}
String _format(
String value,
String oldValue,
String separator,
) {
var isErasing = value.length < oldValue.length;
var isComplete = value.length > _maxChars + 2;
if (!isErasing && isComplete) {
return oldValue;
}
value = value.replaceAll(separator, '');
final result = <String>[];
for (int i = 0; i < min(value.length, _maxChars); i++) {
result.add(value[i]);
if ((i == 1 || i == 3) && i != value.length - 1) {
result.add(separator);
}
}
return result.join();
}
TextSelection updateCursorPosition(
TextEditingValue oldValue,
String text,
) {
var endOffset = max(
oldValue.text.length - oldValue.selection.end,
0,
);
var selectionEnd = text.length - endOffset;
return TextSelection.fromPosition(TextPosition(offset: selectionEnd));
}
}
Thanks to caseyryan/flutter_multi_formatter
In the above solution, I found one overlapping problem with editing in between dates ( if try to edit the month field, the year values get overlapping)
So, I found one solution for this, But not an optimized solution, but it is covering almost all the scenarios,
1. forward slash during adding fields
2. remove the forward slash on on clearing fields
3. between editing handling
...etc
class CustomDateTextFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
var text = _format(newValue.text, '/', oldValue);
return newValue.copyWith(
text: text, selection: _updateCursorPosition(text, oldValue));
}
}
String _format(String value, String seperator, TextEditingValue old) {
var finalString = '';
var dd = '';
var mm = '';
var yyy = '';
var oldVal = old.text;
print('<------------------------- start---------------------------->');
print('oldVal -> $oldVal');
print('value -> $value');
var temp_oldVal = oldVal;
var temp_value = value;
if (!oldVal.contains(seperator) ||
oldVal.isEmpty ||
seperator.allMatches(oldVal).length < 2) {
oldVal += '///';
}
if (!value.contains(seperator) || _backSlashCount(value) < 2) {
value += '///';
}
var splitArrOLD = oldVal.split(seperator);
var splitArrNEW = value.split(seperator);
print('----> splitArrOLD: $splitArrOLD');
print('----> splitArrNEW: $splitArrNEW');
for (var i = 0; i < 3; i++) {
splitArrOLD[i] = splitArrOLD[i].toString().trim();
splitArrNEW[i] = splitArrNEW[i].toString().trim();
}
// block erasing
if ((splitArrOLD[0].isNotEmpty &&
splitArrOLD[2].isNotEmpty &&
splitArrOLD[1].isEmpty &&
temp_value.length < temp_oldVal.length &&
splitArrOLD[0] == splitArrNEW[0] &&
splitArrOLD[2].toString().trim() ==
splitArrNEW[1].toString().trim()) ||
(_backSlashCount(temp_oldVal) > _backSlashCount(temp_value) &&
splitArrNEW[1].length > 2) ||
(splitArrNEW[0].length > 2 && _backSlashCount(temp_oldVal) == 1) ||
(_backSlashCount(temp_oldVal) == 2 &&
_backSlashCount(temp_value) == 1 &&
splitArrNEW[0].length > splitArrOLD[0].length)) {
finalString = temp_oldVal; // making the old date as it is
print('blocked finalString : $finalString ');
} else {
if (splitArrNEW[0].length > splitArrOLD[0].length) {
if (splitArrNEW[0].length < 3) {
dd = splitArrNEW[0];
} else {
for (var i = 0; i < 2; i++) {
dd += splitArrNEW[0][i];
}
}
if (dd.length == 2 && !dd.contains(seperator)) {
dd += seperator;
}
} else if (splitArrNEW[0].length == splitArrOLD[0].length) {
print('splitArrNEW[0].length == 2');
if (oldVal.length > value.length && splitArrNEW[1].isEmpty) {
dd = splitArrNEW[0];
} else {
dd = splitArrNEW[0] + seperator;
}
} else if (splitArrNEW[0].length < splitArrOLD[0].length) {
print('splitArrNEW[0].length < splitArrOLD[0].length');
if (oldVal.length > value.length &&
splitArrNEW[1].isEmpty &&
splitArrNEW[0].isNotEmpty) {
dd = splitArrNEW[0];
} else if (temp_oldVal.length > temp_value.length &&
splitArrNEW[0].isEmpty &&
_backSlashCount(temp_value) == 2) {
dd += seperator;
} else {
if (splitArrNEW[0].isNotEmpty) {
dd = splitArrNEW[0] + seperator;
}
}
}
print('dd value --> $dd');
if (dd.isNotEmpty) {
finalString = dd;
if (dd.length == 2 &&
!dd.contains(seperator) &&
oldVal.length < value.length &&
splitArrNEW[1].isNotEmpty) {
if (seperator.allMatches(dd).isEmpty) {
finalString += seperator;
}
} else if (splitArrNEW[2].isNotEmpty &&
splitArrNEW[1].isEmpty &&
temp_oldVal.length > temp_value.length) {
if (seperator.allMatches(dd).isEmpty) {
finalString += seperator;
}
} else if (oldVal.length < value.length &&
(splitArrNEW[1].isNotEmpty || splitArrNEW[2].isNotEmpty)) {
if (seperator.allMatches(dd).isEmpty) {
finalString += seperator;
}
}
} else if (_backSlashCount(temp_oldVal) == 2 && splitArrNEW[1].isNotEmpty) {
dd += seperator;
}
print('finalString after dd=> $finalString');
if (splitArrNEW[0].length == 3 && splitArrOLD[1].isEmpty) {
mm = splitArrNEW[0][2];
}
if (splitArrNEW[1].length > splitArrOLD[1].length) {
print('splitArrNEW[1].length > splitArrOLD[1].length');
if (splitArrNEW[1].length < 3) {
mm = splitArrNEW[1];
} else {
for (var i = 0; i < 2; i++) {
mm += splitArrNEW[1][i];
}
}
if (mm.length == 2 && !mm.contains(seperator)) {
mm += seperator;
}
} else if (splitArrNEW[1].length == splitArrOLD[1].length) {
print('splitArrNEW[1].length = splitArrOLD[1].length');
if (splitArrNEW[1].isNotEmpty) {
mm = splitArrNEW[1];
}
} else if (splitArrNEW[1].length < splitArrOLD[1].length) {
print('splitArrNEW[1].length < splitArrOLD[1].length');
if (splitArrNEW[1].isNotEmpty) {
mm = splitArrNEW[1] + seperator;
}
}
print('mm value --> $mm');
if (mm.isNotEmpty) {
finalString += mm;
if (mm.length == 2 && !mm.contains(seperator)) {
if (temp_oldVal.length < temp_value.length) {
finalString += seperator;
}
}
}
print('finalString after mm=> $finalString');
if (splitArrNEW[1].length == 3 && splitArrOLD[2].isEmpty) {
yyy = splitArrNEW[1][2];
}
if (splitArrNEW[2].length > splitArrOLD[2].length) {
print('splitArrNEW[2].length > splitArrOLD[2].length');
if (splitArrNEW[2].length < 5) {
yyy = splitArrNEW[2];
} else {
for (var i = 0; i < 4; i++) {
yyy += splitArrNEW[2][i];
}
}
} else if (splitArrNEW[2].length == splitArrOLD[2].length) {
print('splitArrNEW[2].length == splitArrOLD[2].length');
if (splitArrNEW[2].isNotEmpty) {
yyy = splitArrNEW[2];
}
} else if (splitArrNEW[2].length < splitArrOLD[2].length) {
print('splitArrNEW[2].length < splitArrOLD[2].length');
yyy = splitArrNEW[2];
}
print('yyy value --> $yyy');
if (yyy.isNotEmpty) {
if (_backSlashCount(finalString) < 2) {
if (splitArrNEW[0].isEmpty && splitArrNEW[1].isEmpty) {
finalString = seperator + seperator + yyy;
} else {
finalString = finalString + seperator + yyy;
}
} else {
finalString += yyy;
}
} else {
if (_backSlashCount(finalString) > 1 && oldVal.length > value.length) {
var valueUpdate = finalString.split(seperator);
finalString = valueUpdate[0] + seperator + valueUpdate[1];
}
}
print('finalString after yyyy=> $finalString');
}
print('<------------------------- finish---------------------------->');
return finalString;
}
TextSelection _updateCursorPosition(String text, TextEditingValue oldValue) {
var endOffset = max(
oldValue.text.length - oldValue.selection.end,
0,
);
var selectionEnd = text.length - endOffset;
print('My log ---> $selectionEnd');
return TextSelection.fromPosition(TextPosition(offset: selectionEnd));
}
int _backSlashCount(String value) {
return '/'.allMatches(value).length;
}
We can Use our custom formator as in inputFormatters like below
TextField(
// maxLength: 10,
keyboardType: TextInputType.datetime,
controller: _controllerDOB,
focusNode: _focusNodeDOB,
decoration: InputDecoration(
hintText: 'DD/MM/YYYY',
counterText: '',
),
inputFormatters: [
WhitelistingTextInputFormatter(RegExp("[0-9/]")),
LengthLimitingTextInputFormatter(10),
CustomDateTextFormatter(),
],
),
Try out this, thank you. !
class DateFormatter 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 = StringBuffer();
if (newTextLength >= 3) {
newText.write(newValue.text.substring(0, usedSubstringIndex = 2) + '/');
if (newValue.selection.end >= 2)
selectionIndex++;
}
if (newTextLength >= 5) {
newText.write(newValue.text.substring(2, usedSubstringIndex = 4) + '/');
if (newValue.selection.end >= 4)
selectionIndex++;
}
// Dump the rest.
if (newTextLength >= usedSubstringIndex)
newText.write(newValue.text.substring(usedSubstringIndex));
return TextEditingValue(`enter code here`
text: newText.toString(),
selection: TextSelection.collapsed(offset: selectionIndex),
);
}