Customized Picker of Flutter Datetime Picker - flutter

I'm trying to use customized datepicker with creating CustomPicker class as is on Readme.
The problem is the callback of onChanged and onConfirm does not return the updated date. However when I change DatePicker.showPicker to DatePicker.showDatePicker, the callback returns the correct date.
I'm using the following code. I spent hours to investigate this but no luck.
class CustomPicker extends CommonPickerModel {
CustomPicker({DateTime currentTime, LocaleType locale})
: super(locale: locale) {
this.currentTime = currentTime ?? DateTime.now();
}
String yearDigits(int value) {
return '$value';
}
String monthDigits(int value) {
return '$value';
}
#override
String leftStringAtIndex(int index) {
return index >= DateTime.now().year - 100 &&
index <= DateTime.now().year - 18
? '$index'
: null;
}
#override
String middleStringAtIndex(int index) {
return index <= 12 && index >= 1 ? monthDigits(index) : null;
}
#override
String rightStringAtIndex(int index) {
return null;
}
#override
String leftDivider() {
return '';
}
#override
String rightDivider() {
return '';
}
#override
List<int> layoutProportions() {
return [1, 1, 0];
}
#override
DateTime finalTime() {
return currentTime.isUtc
? DateTime.utc(currentTime.year, currentTime.month, 1, 0, 0, 0)
: DateTime(currentTime.month, currentTime.month, 1, 0, 0, 0);
}
}
DatePicker.showPicker(
// ↑ When I change this to DatePicker.showDatePicker
// and comment out pickerModel, the value of print(dateTime) gets selected value.
context,
showTitleActions: true,
onConfirm: (dateTime) {
print(dateTime); // output same value and not updated one
},
pickerModel: CustomPicker(
currentTime: DateTime(_birthYear, _birthMonth, 1),
),
locale: LocaleType.en
);

Okay. So I am not an expert with the Flutter DateTime Picker package but I needed it for a project and so needed to figure out a way for the custom picker, and here is how I solved it.
I realized the issue was with the finalTime() method and what it returns.
I checked the implementation on the package's Github - https://github.com/Realank/flutter_datetime_picker/blob/0b0be9623e905299befc10569f83f3bd11b36125/lib/src/date_model.dart#L494 and picked the sample model for both date & time. It still didn't work until I made sure to update the current left index and the current middle index.
I don't know if my approach is the best, but here is how I solved it.
Here is my solution:
I updated the _currentLeftIndex variable in the setLeftIndex() method:
void setLeftIndex(int index) {
print('left index: '+index.toString());
_currentLeftIndex = index;
....
and then updated the _currentMiddleIndex variable in the setMiddleIndex() method:
void setMiddleIndex(int index) {
print('middle index: '+index.toString());
_currentMiddleIndex = index;
....
Find the full code implementation below:
Note: Remember to import 'date_format.dart'.
...
import 'package:flutter_datetime_picker/src/date_format.dart';
class CustomPicker extends CommonPickerModel {
List<String> leftList;
List<String> middleList;
List<String> rightList;
DateTime currentTime;
int _currentLeftIndex;
int _currentMiddleIndex;
int _currentRightIndex;
LocaleType locale;
DateTime maxTime;
DateTime minTime;
CustomPicker({DateTime currentTime, DateTime maxTime, DateTime minTime, LocaleType locale})
: super(locale: locale) {
this.currentTime = currentTime ?? DateTime.now();
_currentLeftIndex = 0;
_currentMiddleIndex = this.currentTime.hour;
_currentRightIndex = this.currentTime.minute;
this.setLeftIndex(0);
this.setMiddleIndex(this.currentTime.hour);
this.setRightIndex(this.currentTime.minute);
if (currentTime != null) {
this.currentTime = currentTime ?? DateTime.now();
if (maxTime != null &&
(currentTime.isBefore(maxTime) || currentTime.isAtSameMomentAs(maxTime))) {
this.maxTime = maxTime;
}
if (minTime != null &&
(currentTime.isAfter(minTime) || currentTime.isAtSameMomentAs(minTime))) {
this.minTime = minTime;
}
} else {
this.maxTime = maxTime;
this.minTime = minTime;
var now = DateTime.now();
if (this.minTime != null && this.minTime.isAfter(now)) {
this.currentTime = this.minTime;
} else if (this.maxTime != null && this.maxTime.isBefore(now)) {
this.currentTime = this.maxTime;
} else {
this.currentTime = now;
}
}
if (this.minTime != null && this.maxTime != null && this.maxTime.isBefore(this.minTime)) {
// invalid
this.minTime = null;
this.maxTime = null;
}
if (this.minTime != null && isAtSameDay(this.minTime, this.currentTime)) {
_currentMiddleIndex = this.currentTime.hour - this.minTime.hour;
if (_currentMiddleIndex == 0) {
_currentRightIndex = this.currentTime.minute - this.minTime.minute;
}
}
}
bool isAtSameDay(DateTime day1, DateTime day2) {
return day1 != null &&
day2 != null &&
day1.difference(day2).inDays == 0 &&
day1.day == day2.day;
}
#override
void setLeftIndex(int index) {
print('left index: '+index.toString());
_currentLeftIndex = index;
// TODO: implement setLeftIndex
super.setLeftIndex(index);
DateTime time = currentTime.add(Duration(days: index));
if (isAtSameDay(minTime, time)) {
var index = min(24 - minTime.hour - 1, _currentMiddleIndex);
this.setMiddleIndex(index);
} else if (isAtSameDay(maxTime, time)) {
var index = min(maxTime.hour, _currentMiddleIndex);
this.setMiddleIndex(index);
}
}
#override
void setMiddleIndex(int index) {
print('middle index: '+index.toString());
_currentMiddleIndex = index;
// TODO: implement setMiddleIndex
super.setMiddleIndex(index);
DateTime time = currentTime.add(Duration(days: _currentLeftIndex));
if (isAtSameDay(minTime, time) && index == 0) {
var maxIndex = 60 - minTime.minute - 1;
if (_currentRightIndex > maxIndex) {
_currentRightIndex = maxIndex;
}
} else if (isAtSameDay(maxTime, time) && _currentMiddleIndex == maxTime.hour) {
var maxIndex = maxTime.minute;
if (_currentRightIndex > maxIndex) {
_currentRightIndex = maxIndex;
}
}
}
#override
String leftStringAtIndex(int index) {
print('left string index: '+index.toString());
DateTime time = currentTime.add(Duration(days: index));
if (minTime != null && time.isBefore(minTime) && !isAtSameDay(minTime, time)) {
return null;
} else if (maxTime != null && time.isAfter(maxTime) && !isAtSameDay(maxTime, time)) {
return null;
}
return formatDate(time, [ymdw], locale);
}
#override
String middleStringAtIndex(int index) {
print('middle string index: '+index.toString());
if (index >= 0 && index < 24) {
DateTime time = currentTime.add(Duration(days: _currentLeftIndex));
if (isAtSameDay(minTime, time)) {
if (index >= 0 && index < 24 - minTime.hour) {
return digits(minTime.hour + index, 2);
} else {
return null;
}
} else if (isAtSameDay(maxTime, time)) {
if (index >= 0 && index <= maxTime.hour) {
return digits(index, 2);
} else {
return null;
}
}
return digits(index, 2);
}
return null;
}
#override
String rightStringAtIndex(int index) {
print('right string index: '+index.toString());
if (index >= 0 && index < 60) {
DateTime time = currentTime.add(Duration(days: _currentLeftIndex));
if (isAtSameDay(minTime, time) && _currentMiddleIndex == 0) {
if (index >= 0 && index < 60 - minTime.minute) {
return digits(minTime.minute + index, 2);
} else {
return null;
}
} else if (isAtSameDay(maxTime, time) && _currentMiddleIndex >= maxTime.hour) {
if (index >= 0 && index <= maxTime.minute) {
return digits(index, 2);
} else {
return null;
}
}
return digits(index, 2);
}
return null;
}
#override
DateTime finalTime() {
print('all index: ');
print('left index: '+_currentLeftIndex.toString());
print('middle index: '+_currentMiddleIndex.toString());
print('right index: '+_currentRightIndex.toString());
DateTime time = currentTime.add(Duration(days: _currentLeftIndex));
var hour = _currentMiddleIndex;
var minute = _currentRightIndex;
if (isAtSameDay(minTime, time)) {
hour += minTime.hour;
if (minTime.hour == hour) {
minute += minTime.minute;
}
}
return currentTime.isUtc
? DateTime.utc(time.year, time.month, time.day, hour, minute)
: DateTime(time.year, time.month, time.day, hour, minute);
}
#override
List<int> layoutProportions() {
return [4, 1, 1];
}
#override
String rightDivider() {
return ':';
}
}

Related

How can you reload data that is generated through Builder BlocConsumer?

I tried to write my own action Future using . clear() and re-generate it in the same place, but it didn't work.
For example, it first generates data for the month of the last image (August), selects May and it clears August data and generates for May.
To explain: I get data for several months or years, for example: August 2022, May 2022, December 2021, etc.
To make the code more optimized I want to generate first for the last incoming item, and then when I click on a certain month in the dropdown, deleting the old data to generate new data for the month and year he chose.
#override
Widget build(BuildContext context) {
final _provider = Provider.of<MapRepository>(context);
currentIndicator = _provider.fieldIndicator;
filterLayer = _provider.filterLayer;
selectedMonth = currentIndicator != null ? currentIndicator!.getMonth : "";
selectedYear = currentIndicator != null ? currentIndicator!.getYear : "";
String dateMonth =
currentIndicator != null ? "$selectedMonth $selectedYear" : "Загрузка";
_controller = FixedExtentScrollController(initialItem: 0);
return BlocConsumer(
bloc: indicatorBloc,
listener: (context, state) {
if (state is FetchedIndicatorsState) {
int lastPresent = 0;
Future.delayed(Duration(seconds: 1)).then((value) {
for (int i = 0; i < indicators.length; i++) {
if (indicators.reversed.toList()[i].isPresent) {
lastPresent = i;
}
dates.add(indicators[i].getDateTime);
}
for (var date in dates) {
String shortMonth = DateFormat("MMM", "ru").format(date);
String year = DateFormat("yy", "ru").format(date);
String month =
DateFormat("MMMM", "ru").format(date).toCapitalized();
indicatorDates['$shortMonth$year'] = '$month ${date.year}';
}
selectIndicator(indicators.reversed.toList()[lastPresent]);
});
}
},
builder: (context, state) {
if (state is FetchedIndicatorsState) {
indicators = state.indicators;
for (int i = 0; i < indicators.length; i++) {
if (indicators[i].getMonth == selectedMonth &&
indicators[i].getYear == selectedYear) {
childrenIndicators.add(buildIndicator(
indicators[i], indicators[(i - 1 < 0) ? 0 : (i - 1)], i));
}
}
return buildBody(context, dateMonth, childrenIndicators);
}
return Container();
},
);
}
OnTap actions:
onTap: () {
_refreshData();
setState(() {
selectedYear = entry.value.substring(entry.value.length - 5);
selectedMonth = entry.value.substring(0, entry.value.length - 5);
});
Navigator.pop(context);
},
My action:
Future _refreshData() async {
await Future.delayed(Duration(seconds: 3));
childrenIndicators.clear();
for (int i = 0; i < indicators.length; i++) {
if (indicators[i].getMonth == selectedMonth &&
indicators[i].getYear == selectedYear) {
childrenIndicators.add(buildIndicator(
indicators[i], indicators[(i - 1 < 0) ? 0 : (i - 1)], i));
}
}
setState(() {});
}

Expected a value of type 'string' but got one of type 'int' - Flutter

I have a function that returns a String, but when I call this function, the app screen goes red and I get this error: Expected a value of type 'string' but got one of type 'int'.
Here is my function that returns a String:
checkProportion(String predominantGamete, String resultado) {
var countBrown = 0;
var countBlack = 0;
var countWhite = 0;
var proportionCamundongo =
'Proporção: ${countBrown}:${countBlack}:${countWhite}';
if (predominantGamete == 'recessiva_aa' &&
resultado.contains('A') &&
resultado.contains('P')) {
return countBrown += 1;
} else if (predominantGamete == 'recessiva_aa' &&
resultado.contains('A') &&
resultado.contains('pp')) {
return countBlack += 1;
} else if (predominantGamete == 'recessiva_aa' &&
resultado.contains('aa')) {
return countWhite += 1;
}
return proportionCamundongo;
}
Here is how I call the function:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Text(
checkProportion(widget.predominant, widget.result),
),
),
How to solve this error?
Here is an image that shows the colors of each result:
The issue here is that you are returning early, not breaking the if statement, when you do something like return countBrown += 1;;
Try incrementing the counters, then using string interpolation to display the value:
String checkProportion(String predominantGamete, String resultado) {
int countBrown = 0;
int countBlack = 0;
int countWhite = 0;
if (predominantGamete == 'recessiva_aa' &&
resultado.contains('A') &&
resultado.contains('P')) {
countBrown += 1;
} else if (predominantGamete == 'recessiva_aa' &&
resultado.contains('A') &&
resultado.contains('pp')) {
countBlack += 1;
} else if (predominantGamete == 'recessiva_aa' &&
resultado.contains('aa')) {
countWhite += 1;
}
return 'Proporção: ${countBrown}:${countBlack}:${countWhite}';
}
I'd also recommend specifing the return type of the function (String), using the correct types for counters (int). That will help your compiler catch the issues as well.
It isn't my best work, and there is probably a better way to check for if a string contains all occurrence of multiple substrings, but here you go:
bool isColorContained(String resultado, Set<String> requirements) {
for(String requirement in requirements) {
if (!resultado.contains(requirement)) {
return false;
}
}
return true;
}
 
String checkProportion(String predominantGamete, String resultado) {
Map<ColorType, Set<String>> colorType = {
ColorType.brown: {'A', 'P'},
ColorType.black: {'A', 'pp'},
ColorType.white: {'aa'},
};
Map<ColorType, int> colorTypeCount = {
ColorType.brown: 0,
ColorType.black: 0,
ColorType.white: 0,
};
for(MapEntry<ColorType, Set<String>> entry in colorType.entries ) {
if(predominantGamete != 'recessiva_aa') continue;
bool contained = isColorContained(resultado, entry.value);
if(contained) {
int count = colorTypeCount[entry.key] ?? 0;
colorTypeCount[entry.key] = count + 1;
}
}
return 'Proporção: ${colorTypeCount[ColorType.brown]}:${colorTypeCount[ColorType.black]}:${colorTypeCount[ColorType.white]}';
}
 
Also, declare the ColorType enum:
enum ColorType {
brown, black, white
}
This will scale with as many colors and requirements you have, by adding to the ColorType enum, the colorType map, and the colorTypeCount map.

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

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