Flutter TextField input validation for a date - date

I am trying to write a date input control which accepts a date like 23/12/1997. What I would like it to do is automatically insert the / characters for the user. So as they type in 23 the listener returns 23/, so that they can then type in 12. At this point the listener again adds a / leaving the user to complete the date by typing 1997.
My TextEditingController code half works and looks like this:
final _controller = TextEditingController();
_controller.addListener(() {
String text = _controller.text;
if (text.length == 2) {
text += '/';
}
if (text.length == 5) {
text += '/';
}
_controller.value = _controller.value.copyWith(
text: text,
selection:
TextSelection(baseOffset: text.length, extentOffset: text.length),
composing: TextRange.empty,
);
print(_controller.text);
}
So it works fine until the user makes a mistake and needs to backtrack. As soon as a / is deleted it is immediately replaced stopping any further editing of the date.
In order to get it to work I need to access is the previously entered text to determine if the user is backspacing. So if text == 23/ && previous_text == 23/1 then I can remove the / from text.
I found this question textfield must only accept numbers and I think it may help me, but I am not sure how to implement an existing widget and override its methods. Of course there may be a simpler way to do this within the TextEditingController?

I have found what I needed to solve my date validation input. It is not perfect, but it is good enough for what I am trying to do. All I needed was to look at the inputFormatters method of a TextField(). This allow manipulation of the input text to put it into any number of user-defined formats. I am including a segment of my code for anyone who would like to try it out:
class _DateFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue prevText, TextEditingValue currText) {
int selectionIndex;
// Get the previous and current input strings
String pText = prevText.text;
String cText = currText.text;
// Abbreviate lengths
int cLen = cText.length;
int pLen = pText.length;
if (cLen == 1) {
// Can only be 0, 1, 2 or 3
if (int.parse(cText) > 3) {
// Remove char
cText = '';
}
} else if (cLen == 2 && pLen == 1) {
// Days cannot be greater than 31
int dd = int.parse(cText.substring(0, 2));
if (dd == 0 || dd > 31) {
// Remove char
cText = cText.substring(0, 1);
} else {
// Add a / char
cText += '/';
}
} else if (cLen == 4) {
// Can only be 0 or 1
if (int.parse(cText.substring(3, 4)) > 1) {
// Remove char
cText = cText.substring(0, 3);
}
} else if (cLen == 5 && pLen == 4) {
// Month cannot be greater than 12
int mm = int.parse(cText.substring(3, 5));
if (mm == 0 || mm > 12) {
// Remove char
cText = cText.substring(0, 4);
} else {
// Add a / char
cText += '/';
}
} else if ((cLen == 3 && pLen == 4) || (cLen == 6 && pLen == 7)) {
// Remove / char
cText = cText.substring(0, cText.length - 1);
} else if (cLen == 3 && pLen == 2) {
if (int.parse(cText.substring(2, 3)) > 1) {
// Replace char
cText = cText.substring(0, 2) + '/';
} else {
// Insert / char
cText =
cText.substring(0, pLen) + '/' + cText.substring(pLen, pLen + 1);
}
} else if (cLen == 6 && pLen == 5) {
// Can only be 1 or 2 - if so insert a / char
int y1 = int.parse(cText.substring(5, 6));
if (y1 < 1 || y1 > 2) {
// Replace char
cText = cText.substring(0, 5) + '/';
} else {
// Insert / char
cText = cText.substring(0, 5) + '/' + cText.substring(5, 6);
}
} else if (cLen == 7) {
// Can only be 1 or 2
int y1 = int.parse(cText.substring(6, 7));
if (y1 < 1 || y1 > 2) {
// Remove char
cText = cText.substring(0, 6);
}
} else if (cLen == 8) {
// Can only be 19 or 20
int y2 = int.parse(cText.substring(6, 8));
if (y2 < 19 || y2 > 20) {
// Remove char
cText = cText.substring(0, 7);
}
}
selectionIndex = cText.length;
return TextEditingValue(
text: cText,
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
}
To use it simply call it from the Textfield() as shown below. I've also incorporated two built in methods as well. WhitelistingTextInputFormatter() to only allow digits and a slash(/) character to be entered and LengthLimitingTextInputFormatter() to restrict the number of characters allowed. The latter could be achieved using the maxLength parameter of TextField() but it is here by way of example. Note that there is also a BlacklistingTextInputFormatter() which does as you would expect.
WhitelistingTextInputFormatter was removed use FilteringTextInputFormatter.allow(RegExp("[0-9-]")), to replace, and If you want to change split symbol (current is "/"), Pls add it to RegExp(....).
TextField(
// maxLength: 10,
keyboardType: TextInputType.datetime,
controller: _controllerDOB,
focusNode: _focusNodeDOB,
decoration: InputDecoration(
hintText: 'DD/MM/YYYY',
counterText: '',
),
inputFormatters: [
WhitelistingTextInputFormatter(RegExp("[0-9/]")),
LengthLimitingTextInputFormatter(10),
_DateFormatter(),
],
),

You can use datepicker dialog made by flutter.
DateTime _date = DateTime.now()
onPressed: () {
showDatePicker(
context: context,
initialDate: _date,
firstDate: DateTime(2020),
lastDate: DateTime(2021),
).then((date) {
setState(() {
_date = date;
});
});
},

I find your code to be a great utility, without any dependencies. I took the liberty to do a few mods and thought of posting it back here, as I find your concept very neat and lightweight on the UI. The requirements were;
Validating the date for non-31-day months and leap years. The mods were quite straightforward.
Preventing the user entering "/" at undesirable places which will throw the algorithm off-track. The simplest solution is to make the
keyboardType: TextInputType.number, in the TextField
This works perfectly for mobile devices.
But Flutter being cross-platform, this solution may not be foolproof when it comes to a device with physical keyboard. I tried various checks and blocks but only partially succeeded; i.e, user can still input "/" in between the digits of the Day and Month. I think there is an internal delay between KB input and programmatic formatter.
Following is the modified code for _DateFormatter. I have used the /// notion to distinguish my comments. They should be read along with the original // comments.
class _DateFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue prevText, TextEditingValue currText) {
int selectionIndex;
String date;
String month;
int year;
// Get the previous and current input strings
String pText = prevText.text;
String cText = currText.text;
cText = cText.replaceAll("//", "/");
// Abbreviate lengths
int cLen = cText.length;
int pLen = pText.length;
/// ENTERING THE DATE
if (cLen == 1) {
/// User enters the first digit of the date. The first digit
// Can only be 0, 1, 2 or 3
if (int.parse(cText) > 3) {
// Remove char
cText = '';
}
} else if (cLen == 2 && pLen == 1) {
/// User has already entered a valid first digit of the date, now he
/// enters the second digit of the date; but
// Days cannot be greater than 31
int dd = int.parse(cText.substring(0, 2));
if (dd == 0 || dd > 31) {
// Remove char
cText = cText.substring(0, 1);
} else {
/// User has entered a valid date (between 1 and 31). So now,
// Add a / char
cText += '/';
}
/// ENTERING THE MONTH
} else if (cLen == 4) {
/// after entering a valid date and programmatic insertion of '/', now User has entered
/// the first digit of the Month. But, it
// Can only be 0 or 1
/// (and, not '/' either)
if (int.parse(cText.substring(3, 4)) > 1 || cText.substring(3, 4) == "/") {
// Remove char
cText = cText.substring(0, 3);
}
} else if (cLen == 5 && pLen == 4) {
int mm = int.parse(cText.substring(3, 5));
int dd = int.parse(cText.substring(0, 2));
/// User has entered the second digit of the Month, but the
// Month cannot be greater than 12
/// Also, that entry cannot be '/'
if ((mm == 0 || mm > 12|| cText.substring(3, 5) == "/") ||
/// If the date is 31, the month cannot be Apr, Jun, Sept or Nov
(dd == 31 && (mm == 02 || mm == 04 || mm == 06 || mm == 09 || mm == 11)) ||
/// If the date is greater than 29, the month cannot be Feb
/// (Leap years will be dealt with, when user enters the Year)
(dd > 29 && (mm == 02))) {
// Remove char
cText = cText.substring(0, 4);
}
else if (cText.length == 5) {
/// the Month entered is valid; so,
// Add a / char
cText += '/';
}
} else if ((cLen == 3 && pLen == 4) || (cLen == 6 && pLen == 7)) {
// Remove / char
cText = cText.substring(0, cText.length - 1);
} else if (cLen == 3 && pLen == 2) {
if (int.parse(cText.substring(2, 3)) > 1) {
// Replace char
cText = cText.substring(0, 2) + '/';
} else {
// Insert / char
cText =
cText.substring(0, pLen) + '/' + cText.substring(pLen, pLen + 1);
}
/// ENTERING THE YEAR
} else if (cLen == 6 && pLen == 5) {
// Can only be 1 or 2 - if so insert a / char
int y1 = int.parse(cText.substring(5, 6));
if (y1 < 1 || y1 > 2) {
// Replace char
/// i.e, add '/' after the 5th position
cText = cText.substring(0, 5) + '/';
} else {
// Insert / char
cText = cText.substring(0, 5) + '/' + cText.substring(5, 6);
}
} else if (cLen == 7) {
/// the first digit of year
// Can only be 1 or 2
int y1 = int.parse(cText.substring(6, 7));
if (y1 < 1 || y1 > 2) {
// Remove char
cText = cText.substring(0, 6);
}
} else if (cLen == 8) {
// Can only be 19 or 20
/// Also, there cannot be / typed by the user
String y2 = cText.substring(6, 8);
if (y2 != "19" && y2 != "20") {
// Remove char
cText = cText.substring(0, 7);
}
} else if (cLen == 9) {
/// There cannot be / typed by the user
if (cText.substring(8, 9) == "/") {
// Remove char
cText = cText.substring(0, 8);
}
} else if (cLen == 10) {
/// There cannot be / typed by the user
if (cText.substring(9, 10) == "/") {
// Remove char
cText = cText.substring(0, 9);
}
/// If the year entered is not a leap year but the date entered is February 29,
/// it will be advanced to the next valid date
date = cText.substring(0, 2);
month = cText.substring(3, 5);
year = int.parse(cText.substring(6, 10));
bool isNotLeapYear = !((year % 4 == 0) && (year % 100 != 0) ||
(year % 400 == 0));
if (isNotLeapYear && month == "02" && date == "29") {
cText = "01/03/$year";
}
}
selectionIndex = cText.length;
return TextEditingValue(
text: cText,
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
} // END OF class _DateFormatter

I found one solution for this, But not an optimized solution, but it is covering almost all the scenarios,
forward slash during adding fields
remove the forward slash on clearing fields
in 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 solution.

Related

A value of type 'Null' can't be assigned to a variable of type 'double'

Hello I have a connect four game who worked before null safety but I try to make the migration, but I have a problem with scores[i] = null; I can't write like that but without it I have a freeze when CPU is certain to loose
int _compute(Board board, int step, int deepness, List<double> scores) {
for (var i = 0; i < 7; ++i) {
final boardCopy = board.clone();
final target = boardCopy.getColumnTarget(i);
if (target == -1) {
scores[i] = null; // <<<---- HERE I CAN'T USE NULL
continue;
}
final coordinate = Coordinate(i, target);
boardCopy.setBox(coordinate, player);
if (boardCopy.checkWinner(coordinate, player)) {
scores[i] += deepness / (step + 1);
continue;
}
for (var j = 0; j < 7; ++j) {
final target = boardCopy.getColumnTarget(j);
if (target == -1) {
continue;
}
final coordinate = Coordinate(j, target);
boardCopy.setBox(coordinate, otherPlayer);
if (boardCopy.checkWinner(coordinate, otherPlayer)) {
scores[i] -= deepness / (step + 1);
continue;
}
if (step + 1 < deepness) {
_compute(board, step + 1, deepness, scores);
}
}
}
return _getBestScoreIndex(scores);
}
int _getBestScoreIndex(List<double> scores) {
int bestScoreIndex = scores.indexWhere((s) => s != null);
scores.asMap().forEach((index, score) {
if (score != null &&
(score > scores[bestScoreIndex] ||
(score == scores[bestScoreIndex] && _random.nextBool()))) {
bestScoreIndex = index;
}
});
return bestScoreIndex;
}
if I use List<double?>
int _compute(Board board, int step, int deepness, List<double?> scores) {
for (var i = 0; i < 7; ++i) {
final boardCopy = board.clone();
final target = boardCopy.getColumnTarget(i);
if (target == -1) {
scores[i] = null;
continue;
}
final coordinate = Coordinate(i, target);
boardCopy.setBox(coordinate, player);
if (boardCopy.checkWinner(coordinate, player)) {
scores[i] += deepness / (step + 1);//<<<---- HERE I CAN'T USE +=
continue;
}
for (var j = 0; j < 7; ++j) {
final target = boardCopy.getColumnTarget(j);
if (target == -1) {
continue;
}
final coordinate = Coordinate(j, target);
boardCopy.setBox(coordinate, otherPlayer);
if (boardCopy.checkWinner(coordinate, otherPlayer)) {
scores[i] -= deepness / (step + 1); //<<<---- HERE I CAN'T USE -=
continue;
}
if (step + 1 < deepness) {
_compute(board, step + 1, deepness, scores);
}
}
}
return _getBestScoreIndex(scores);
}
int _getBestScoreIndex(List<double?> scores) {
int bestScoreIndex = scores.indexWhere((s) => s != null);
scores.asMap().forEach((index, score) {
if (score != null && // <<<---- HERE I CAN'T USE score !=
(score > scores[bestScoreIndex] || // <<<---- HERE I CAN'T USE score >
(score == scores[bestScoreIndex] && _random.nextBool()))) {
bestScoreIndex = index;
}
});
return bestScoreIndex;
}
The issue with your code is that in the function definition you have defined the data type of score variable as List<double>. Due to which you get an error on assigning score[i] = null. To fix this use data type of score as List<double?>
i believe the param scores is a type-defined value and it has a double, which is a non null value. So you can't assign null to double as it has the type of value assigned.
You can make it double as an optional value, like a List<double?>. but with this, the values in the list will be optional and you need to force unwrap or do null check before using them.

How to format user's input text to MM/DD/YYYY using Input Formatter in Flutter

Need to know how to format the user's input text to "MM/DD/YYYY" format. As the user enters the value in input field, the text should be taken in this format, plus let's say if the users type the month as 14, the "4" should not be added. This month, date, year validation needs to be happening as the users types.
This is what I have so far, The month formatting works fine, but needs improvements in date formatting.
class _DateFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue prevText, TextEditingValue currText) {
int selectionIndex;
// Get the previous and current input strings
String pText = prevText.text;
String cText = currText.text;
// Abbreviate lengths
int cLen = cText.length;
int pLen = pText.length;
//Date
if (cLen == 1) {
// Can only be 0, 1, 2 or 3
if (int.parse(cText) > 1) {
// Remove char
cText = '';
}
} else if (cLen == 2 && pLen == 1) {
// Can only be 0, 1, 2 or 3
if (int.parse(cText) == 0 || int.parse(cText) > 12) {
// Remove char
cText = cText.substring(0, 1);
} else {
cText += '/';
}
} else if (cLen == 2 && pLen == 3) {
cText = cText.substring(0, 1);
} else if (cLen == 2 && pLen == 4) {
cText += '/';
} else if (cLen == 3 && pLen == 2) {
cText = cText.substring(0, 2) + '/' + cText.substring(2, 3);
}
// Month
else if (cLen == 4 && pLen == 2) {
print('s');
if (int.parse(cText.substring(3, 4)) > 3) {
cText = cText;
}
} else if (cLen == 4 && pLen == 3) {
if (int.parse(cText.substring(3, 4)) > 3) {
cText = cText.substring(0, 3);
}
// // Can only be 0, 1, 2 or 3
// if (int.parse(cText) > 3) {
// // Remove char
// cText = '
// }
// // Can only be 0 or 1
// if (int.parse(cText.substring(3, 4)) > 1) {
// // Remove char
// cText = cText.substring(0, 3);
// }
} else if (cLen == 5 && pLen == 4) {
// Month cannot be greater than 12
int mm = int.parse(cText.substring(3, 5));
if (mm == 0 || mm > 12) {
// Remove char
cText = cText.substring(0, 4);
} else {
// Add a / char
cText += '/';
}
} else if ((cLen == 3 && pLen == 4) || (cLen == 6 && pLen == 7)) {
// Remove / char
cText = cText.substring(0, cText.length - 1);
} else if (cLen == 3 && pLen == 2) {
if (int.parse(cText.substring(2, 3)) > 1) {
// Replace char
cText = cText.substring(0, 2) + '/';
} else {
// Insert / char
cText =
cText.substring(0, pLen) + '/' + cText.substring(pLen, pLen + 1);
}
}
//Year
else if (cLen == 6 && pLen == 5) {
// Can only be 1 or 2 - if so insert a / char
int y1 = int.parse(cText.substring(5, 6));
if (y1 < 1 || y1 > 2) {
// Replace char
cText = cText.substring(0, 5) + '/';
} else {
// Insert / char
cText = cText.substring(0, 5) + '/' + cText.substring(5, 6);
}
} else if (cLen == 7) {
// Can only be 1 or 2
int y1 = int.parse(cText.substring(6, 7));
if (y1 < 1 || y1 > 2) {
// Remove char
cText = cText.substring(0, 6);
}
} else if (cLen == 8) {
// Can only be 19 or 20
int y2 = int.parse(cText.substring(6, 8));
if (y2 < 19 || y2 > 20) {
// Remove char
cText = cText.substring(0, 7);
}
}
selectionIndex = cText.length;
print(selectionIndex);
return TextEditingValue(
text: cText,
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
}
Can you try to follow this package, I hope it would be helpful
https://pub.dev/packages/mask_text_input_formatter
var textEditingController = TextEditingController(text: "12345678");
var maskFormatter = new MaskTextInputFormatter(mask: '####-####', filter: { "#": RegExp(r'[0-9]') });
TextField(controller: textEditingController, inputFormatters: [maskFormatter]) // -> "1234-5678"
textEditingController.value = maskFormatter.updateMask(mask: "##-##-##-##");

How to add range validation for year in flutter

I've tried to add year range validation it must be between 1850 to the current year. i've tried but it's not working.
Here is the code I've tried
String validateestablishedyear(String value) {
var date = new DateTime.now();
int currentYear = date.year;
int userinputValue = 0;
print('currentYear = $currentYear');
print('input value = $value');
print('value = ${value is int}');
print('value = ${value is String}');
if (value is String && value.length == 0) {
return "Established Year is Required";
} else {
userinputValue = int.parse(value);
}
if (userinputValue < 1850 && userinputValue >= currentYear) {
return "Year must be between 1850 and $currentYear";
}
return null;
}
Just change your code as below,
if (userinputValue < 1850 || userinputValue >= currentYear) {
return "Year must be between 1850 and $currentYear";
}
You're logic is wrong. you should have use || instead of &&

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

Convert Number to Words in apex salesforce

Below apex code can be used to convert number (currency) into words. This code can be used in triggers,visualforce pages to convert any number/currency field value in words and stored in any text field.
How to call this class
Decimal d = 1491511.61;
NumberTOWordConvertion nwcObj = new NumberTOWordConvertion();
String numInWords = nwcObj.getNumberTOWordConvertion(d);
Output : Fourteen Lakh Ninety One Thousand Five Hundred and Eleven Rupess And Sixty One Paisa Only.
public class NumberTOWordConvertion {
// Call this method with Number to convert
public String getNumberTOWordConvertion(Decimal num) {
Decimal junkVal = num;
Decimal junkValPaisa = junkVal - Math.floor(junkVal);
junkVal = Math.floor(junkVal);
String obStr = junkVal.toPlainString();
String[] numReversed = obStr.split('');
String[] actnumber = reverse(numReversed);
String firstHalf = convertInWords(numReversed, actnumber);
Integer tmp = Math.round(junkValPaisa * 100);
junkValPaisa = (Decimal)tmp / 100; System.debug('jj :' + junkValPaisa);
String paisaStr = junkValPaisa.toPlainString();
String secondHalf;
if (paisaStr == '0') {
secondHalf = '';
} else if (paisaStr.length() != 4) {
paisaStr = paisaStr + '0';
paisaStr = paisaStr.substring(2);
String [] numReversedPaisa = paisaStr.split('');
String[] actnumberPaisa = reverse(numReversedPaisa);
secondHalf = convertInWords(numReversedPaisa, actnumberPaisa);
} else {
paisaStr = paisaStr.substring(2);
String [] numReversedPaisa = paisaStr.split('');
String[] actnumberPaisa = reverse(numReversedPaisa);
secondHalf = convertInWords(numReversedPaisa, actnumberPaisa);
}
String SumOFHalves = '';
if (secondHalf.length() > 4) {
firstHalf = firstHalf.replace('Only', 'Rupess And ');
secondHalf = secondHalf.replace('Only', 'Paisa Only');
SumOFHalves = firstHalf + secondHalf;
} else {
firstHalf = firstHalf.replace('Only', 'Rupess Only');
SumOFHalves = firstHalf;
}
// IF amount has any value
if (SumOFHalves.length() > 5) {
return SumOFHalves;
} else {
return '';
}
}
// Method reverse the number
public List<String> reverse(List<String> strToRev) {
List<String> revList = new List<String>();
for (Integer i = strToRev.size() - 1; i >= 0; i--) {
revList.add(strToRev.get(i));
}
revList.add('');
return revList;
}
public String convertInWords(String[] numRev, String[] actnum) {
List<String> iWords = new List<String> {'Zero', ' One', ' Two', ' Three', ' Four', ' Five', ' Six', ' Seven', ' Eight', ' Nine'};
List<String> ePlace = new List<String> {' Ten', ' Eleven', ' Twelve', ' Thirteen', ' Fourteen', ' Fifteen', ' Sixteen', ' Seventeen', ' Eighteen', ' Nineteen'};
List<String> tensPlace = new List<String> {'dummy', ' Ten', ' Twenty', ' Thirty', ' Forty', ' Fifty', ' Sixty', ' Seventy', ' Eighty', ' Ninety' };
Integer iWordsLength = numRev.size();
String totalWords = '';
List<String> inWords = new List<String>();
for (Integer k = 0; k < iWordsLength; k++) {
inWords.add('');
}
String finalWord = '';
Integer j = 0;
// Main For loop
for (Integer i = 0; i < iWordsLength; i++) {
if (i == 0) {
if (actnum[i] == '0' || actnum[i + 1] == '1') {
inWords[j] = '';
} else {
inWords[j] = iWords[Integer.valueof(actnum[i])];
}
inWords[j] = inWords[j] + ' Only';
} else if (i == 1) {
if (actnum[i] == '0') {
inWords[j] = '';
} else if (actnum[i] == '1') {
inWords[j] = ePlace[Integer.valueof(actnum[i - 1])];
} else {
inWords[j] = tensPlace[Integer.valueof(actnum[i])];
}
} else if (i == 2) {
if (actnum[i] == '0') {
inWords[j] = '';
} else if (actnum[i - 1] != '0' && actnum[i - 2] != '0') {
inWords[j] = iWords[Integer.valueof(actnum[i])] + ' Hundred and';
} else {
inWords[j] = iWords[Integer.valueof(actnum[i])] + ' Hundred';
}
} else if (i == 3) {
if (actnum[i] == '0' || actnum[i + 1] == '1') {
inWords[j] = '';
} else {
inWords[j] = iWords[Integer.valueof(actnum[i])];
}
if (actnum[i + 1] != '0' || Integer.valueof(actnum[i]) > 0) {
inWords[j] = inWords[j] + ' Thousand';
}
} else if (i == 4) {
if (actnum[i] == '0') {
inWords[j] = '';
} else if (actnum[i] == '1') {
inWords[j] = ePlace[Integer.valueof(actnum[i - 1])];
} else {
inWords[j] = tensPlace[Integer.valueof(actnum[i])];
}
} else if (i == 5) {
if (actnum[i] == '0' || actnum[i + 1] == '1') {
inWords[j] = '';
} else {
inWords[j] = iWords[Integer.valueof(actnum[i])];
}
if (actnum[i + 1] != '0' || Integer.valueof(actnum[i]) > 0) {
inWords[j] = inWords[j] + ' Lakh';
}
} else if (i == 6) {
if (actnum[i] == '0') {
inWords[j] = '';
} else if (actnum[i] == '1') {
inWords[j] = ePlace[Integer.valueof(actnum[i - 1])];
} else {
inWords[j] = tensPlace[Integer.valueof(actnum[i])];
}
} else if (i == 7) {
if (actnum[i] == '0' || actnum[i + 1] == '1' ) {
inWords[j] = '';
} else {
inWords[j] = iWords[Integer.valueof(actnum[i])];
}
inWords[j] = inWords[j] + ' Crore';
} else if (i == 8) {
if (actnum[i] == '0') {
inWords[j] = '';
} else if (actnum[i] == '1') {
inWords[j] = ePlace[Integer.valueof(actnum[i - 1])];
} else {
inWords[j] = tensPlace[Integer.valueof(actnum[i])];
}
}
j++;
}
// End of For loop
// Reverse the List
inWords = reverse(inWords);
for (Integer i = 0; i < inWords.size(); i++) {
finalWord += inWords[i];
}
return finalWord;
}
}
String.valueOf(num);
returns the string equivalent of the num