Flutter – how to solve delete problem TextInputFormatter - flutter

My input is working perfectly, but I can't delete symbols. How to do this?
I just want the Date in the credit card formatted (like this: 01/25) And if 1 symbol > 1 I write 0+symbol, else I write just symbol
Here the code
class DateFormat extends TextInputFormatter {
//Formatting to *#/## (if (*>1) *=0)
#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'[2-9]'))) {
newTextBuffer.write('0${newValue.text.substring(0, 1)}');
if (newValue.selection.end >= 1) selectionIndex++;
} else {
newTextBuffer.write(newValue.text.substring(0, 2));
}
}
if (newTextLength >= 3) {
newTextBuffer.write('/' + newValue.text.substring(2, usedSubstringIndex = 3));
if (newValue.selection.end >= 2) selectionIndex++;
}
// Dump the rest.
if (newTextLength > usedSubstringIndex) newTextBuffer.write(newValue.text.substring(usedSubstringIndex, newTextLength));
return TextEditingValue(
text: newTextBuffer.toString(),
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
}

Finally i got to solve this problem, checkout this code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class PhoneMaskFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
//Phone Mask: +X (XXX) XXX-XXXX
//While deleting character get the older value to check if you're deleting not number characters
final oldValueText = oldValue.text.replaceAll(new RegExp(r'[^0-9]'), '');
String newValueText = newValue.text;
//If its same its because you're deleting a non number value so remove the value that you want to delete
if (oldValueText == newValue.text) {
newValueText = newValueText.substring(0, newValue.selection.end - 1) + newValueText.substring(newValue.selection.end, newValueText.length);
}
final int newTextLength = newValueText.length;
int selectionIndex = newValue.selection.end;
int usedSubstringIndex = 0;
final StringBuffer newText = StringBuffer();
if (newTextLength >= 1) {
newText.write('+' + newValueText.substring(0, usedSubstringIndex = 1) + ' (');
if (newValue.selection.end >= 1) selectionIndex += 3;
}
if (newTextLength > 4) {
newText.write(newValueText.substring(1, usedSubstringIndex = 4) + ') ');
if (newValue.selection.end >= 4) selectionIndex += 2;
}
if (newTextLength > 7) {
newText.write(newValueText.substring(4, usedSubstringIndex = 7) + '-');
if (newValue.selection.end >= 7) selectionIndex++;
}
// Dump the rest.
if (newTextLength >= usedSubstringIndex) newText.write(newValueText.substring(usedSubstringIndex));
return TextEditingValue(
text: newText.toString(),
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
}

Related

Flutter - How to add space after 1 character when typing in TextFormField

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

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

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

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