Flutter | How to validate time picker - flutter

Here I want to validate the time picker. when I am going to select the time from the time picker it must only allow me to select the current time or before the current time. I am not supposed to allow select future time.
Here is the time picker snippet I've tried.
DateTimeField(
format: format,
autocorrect: true,
autovalidate: false,
controller: _serviceDate,
readOnly: true,
validator: (date) => (date == null || _serviceDate.text == '')
? 'Please enter valid date'
: null,
onShowPicker: (context, currentValue) async {
final date = await showDatePicker(
context: context,
firstDate: DateTime.now(),
initialDate: currentValue ?? DateTime.now(),
lastDate: DateTime(2100));
if (date != null) {
final time = await showTimePicker(
context: context,
initialTime: TimeOfDay.fromDateTime(
currentValue ?? DateTime.now(),
),
);
return DateTimeField.combine(date, time);
} else {
return currentValue;
}
},
);

You can enable autovalidateMode on DateTimeField then check the DateTime difference of the selected DateTime with the current DateTime. If the time difference is negative, this means the selected DateTime is in the past.
DateTimeField(
format: format,
autocorrect: true,
readOnly: true,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (DateTime? selectedDateTime) {
if (selectedDateTime != null) {
// If the DateTime difference is negative,
// this indicates that the selected DateTime is in the past
if (selectedDateTime.difference(DateTime.now()).isNegative) {
debugPrint('Selected DateTime is in the past');
} else {
debugPrint('Selected DateTime is in the future');
}
}
},
onShowPicker: ...
),
Aside from that, you might also want to consider setting the lastDate on showDatePicker to DateTime.now() so future dates can't be selected.
Here's the complete sample that you can try out.
import 'package:datetime_picker_formfield/datetime_picker_formfield.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final format = DateFormat("yyyy-MM-dd HH:mm");
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DateTimeField(
format: format,
autocorrect: true,
readOnly: true,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (DateTime? selectedDateTime) {
if (selectedDateTime != null) {
// If the DateTime difference is negative,
// this indicates that the selected DateTime is in the past
if (selectedDateTime.difference(DateTime.now()).isNegative) {
debugPrint('Selected DateTime is in the past');
} else {
debugPrint('Selected DateTime is in the future');
}
}
},
onShowPicker: (context, currentValue) async {
final date = await showDatePicker(
context: context,
firstDate: DateTime(1900),
initialDate: DateTime.now(),
lastDate: DateTime.now());
if (date != null) {
final time = await showTimePicker(
context: context,
initialTime: TimeOfDay.fromDateTime(
currentValue ?? DateTime.now(),
),
);
return DateTimeField.combine(date, time);
} else {
return currentValue;
}
},
),
],
),
),
);
}
}

Related

Flutter How to set the maximum date range duration in DateTimeRange

I have the following code with a DateTimeRange and I would like to allow the user to select a range without exceed 15 days. For example a range before today will be 02/02 to 02/16 or lower or after today 02/14 to 03/01 or lower. How can I do that if it's possible ?
class DateRangeView extends StatefulWidget {
late bool isClearClicked;
DateRangeView({Key? key, required this.isClearClicked}) : super(key: key);
#override
DateRangeView createState() => DateRangeViewState();
}
class DateRangeViewState extends State<DateRangeView> {
DateTimeRange? dateRange;
DateTimeRange? initialDateRange;
DateTimeRange? newDateRange;
String getFrom() {
if (dateRange == null) {
return DateTime.now().toRangeDate();
} else {
return dateRange!.start.toRangeDate();
}
}
String getUntil() {
if (dateRange == null) {
return DateTime.now().toRangeDate();
} else {
return dateRange!.end.toRangeDate();
}
}
#override
Widget build(BuildContext context) {
return HeaderWidget(
title: Translation.current.period.toUpperCase(),
child: Row(
children: [
Expanded(
child: ButtonWidget(
text: widget.isClearClicked
? DateTime.now().toRangeDate()
: getFrom(),
onClicked: () => pickDateRange(context),
),
),
Text(Translation.current.untilDateKeyword.toUpperCase(),
style:
TextStyle(fontSize: ThemeSize.text(xl), color: ThemeColor.gray500),
),
Expanded(
child: ButtonWidget(
text: widget.isClearClicked
? DateTime.now().toRangeDate()
: getUntil(),
onClicked: () => pickDateRange(context),
),
),
Icon( Icons.calendar_month_rounded, color: ThemeColor.gray600,),
],
),
);
}
Future pickDateRange(BuildContext context) async {
initialDateRange = DateTimeRange(
start: DateTime.now(),
end: DateTime.now(),
);
newDateRange = await showDateRangePicker(
initialEntryMode: DatePickerEntryMode.calendarOnly,
context: context,
firstDate: DateTime(2022),
lastDate: DateTime(2030),
initialDateRange: dateRange ?? initialDateRange,
builder: (context, Widget? child) => Theme(
data:ThemeData.light().copyWith(
//Header background color
primaryColor: ThemeColor.primaryVariant,
scaffoldBackgroundColor: Colors.grey[50],
dividerColor: Colors.grey,
textTheme: TextTheme(
bodyText2:
TextStyle(color: ThemeColor.gray700, fontSize: ThemeSize.text(xxl)),
),
colorScheme: ColorScheme.fromSwatch().copyWith(
primary: ThemeColor.primaryVariant,
onSurface: Colors.black,
),
),
child: child!,
),
);
if (newDateRange == null) return;
setDateRange();
}
setDateRange() {
setState(() {
dateRange = newDateRange;
widget.isClearClicked = false;
});
Future.delayed(
const Duration(milliseconds: 100), () {
dateRange = initialDateRange;
},
);
}
}
You can use syncfusion_flutter_datepicker library. It has a selectionChanged callback that you can manipulate to limit selectable dates on the go. This is my answer to a similar question
class Home extends StatefulWidget {
const Home({super.key});
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
final DateTime _minDate = DateTime.now();
DateTime _maxDate = DateTime.now().add(const Duration(days: 365));
final Duration _duration = const Duration(days: 14);
DateTime _start = DateTime.now();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SfDateRangePicker(
minDate: _minDate,
maxDate: _maxDate,
selectionMode: DateRangePickerSelectionMode.range,
onSelectionChanged: (DateRangePickerSelectionChangedArgs args) {
if (args.value is PickerDateRange) {
_start = (args.value as PickerDateRange).startDate!;
setState(() {
// limit the maxDate to 14 days from selected date
_maxDate = _start.add(_duration);
});
}
},
)),
);
}
}
You can change this two params to :
firstDate: DateTime.now(),
lastDate: DateTime.now().add(Duration(days: 15)),

how to show time in date time picker in flutter..?

suppose you have a API and a key-value for this API is
"time": "22:05"
i want to show this time in a in a field and when i press the field the date time picker is pop up with time value and i change the time like 22:40 and the 22:05 will replace to 22:40
I have try this..
time = TimeOfDay.now();
final String hour = time.hour.toString().padLeft(2, '0');
final String minute = time.minute.toString().padLeft(2, '0');
GestureDetector(
onTap: () async {
TimeOfDay? pickTime = await showTimePicker(
context: context,
initialTime: time,
);
if (pickTime != null) {
setState(() => time = pickTime);
}
It always show my current system time
Here is a working example:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({ super.key });
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
TimeOfDay? selectedTime;
#override
Widget build(BuildContext context) {
return
GestureDetector(
onTap : () async {
TimeOfDay? newTime = await showTimePicker(
initialTime: TimeOfDay.now(),
context: context,
);
setState((){
selectedTime = newTime;
});
},
child: Text(
selectedTime.toString(),
style: Theme.of(context).textTheme.headline4,
),
);
}
}
this is my usual way. with TextFormfied. so you can use validator if you need too.
TextFormField(
readOnly: true,
decoration: kInputDecor,
controller: _ctrl,
onTap: () async {
final selectedTime = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (selectedTime != null) {
final String hour =
selectedTime.hour.toString().padLeft(2, '0');
final String minute =
selectedTime.minute.toString().padLeft(2, '0');
// _ctrl.text = selectedTime.format(context);
_ctrl.text = '${hour} :$minute';
}
},
),
result:
demo : https://dartpad.dev/?id=be4ccb0cad0930c9797bb4c2b63cd70e

Flutter app logic to cancell date and time picker dialog box at once

I am working on a flutter app where I want to show date and time pickers. In the third example, I am trying to show how to pick a date and time together, which is working fine. But what I want is when the user cancels the date picker dialog then the time picker should automatically get cancelled. But right now we have to do it separately. For reference, you can see the problem in the image below.
I need some help with the app logic to cancel the date & time picked at once.
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
void main() {
runApp(const DateTimePickerApp());
}
class DateTimePickerApp extends StatelessWidget {
const DateTimePickerApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: TextScreen(),
);
}
}
class TextScreen extends StatefulWidget {
const TextScreen({
Key? key,
}) : super(key: key);
#override
State<TextScreen> createState() => _TextScreenState();
}
class _TextScreenState extends State<TextScreen> {
DateTime selectedDate = DateTime.now();
TimeOfDay selectedTime = TimeOfDay.now();
DateTime dateTime = DateTime.now();
bool showDate = true;
bool showTime = true;
bool showDateTime = true;
// Select for Date
Future<DateTime> _selectDate(BuildContext context) async {
final selected = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(2000),
lastDate: DateTime(2025),
);
if (selected != null && selected != selectedDate) {
setState(() {
selectedDate = selected;
});
}
return selectedDate;
}
// Select for Time
Future<TimeOfDay> _selectTime(BuildContext context) async {
final selected = await showTimePicker(
context: context,
initialTime: selectedTime,
);
if (selected != null && selected != selectedTime) {
setState(() {
selectedTime = selected;
});
}
return selectedTime;
}
// select date time picker
Future _selectDateTime(BuildContext context) async {
final date = await _selectDate(context);
final time = await _selectTime(context);
if (date == null) return;
if (time == null) return;
setState(() {
dateTime = DateTime(
date.year,
date.month,
date.day,
time.hour,
time.minute,
);
});
}
String getDate() {
// ignore: unnecessary_null_comparison
if (selectedDate == null) {
return 'select date';
} else {
return DateFormat('MMM d, yyyy').format(selectedDate);
}
}
String getDateTime() {
// ignore: unnecessary_null_comparison
if (dateTime == null) {
return 'select date timer';
} else {
return DateFormat('yyyy-MM-dd HH:mm a').format(dateTime);
}
}
String getTime(TimeOfDay tod) {
final now = DateTime.now();
final dt = DateTime(now.year, now.month, now.day, tod.hour, tod.minute);
final format = DateFormat.jm();
return format.format(dt);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('GeeksforGeeks'),
centerTitle: true,
backgroundColor: Colors.green,
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
showDate ? Center(child: Text(getDate())) : const SizedBox(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: ElevatedButton(
onPressed: () {
_selectDate(context);
showDate = true;
},
child: const Text('Date Picker'),
),
),
showTime
? Center(child: Text(getTime(selectedTime)))
: const SizedBox(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: ElevatedButton(
onPressed: () {
_selectTime(context);
showTime = true;
},
child: const Text('Timer Picker'),
),
),
showDateTime
? Center(child: Text(getDateTime()))
: const SizedBox(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: ElevatedButton(
onPressed: () {
_selectDateTime(context);
showDateTime = true;
},
child: const Text(' Date & Time '),
),
),
],
),
),
);
}
}
You need to use nullable selectedDate
class _TextScreenState extends State<TextScreen> {
DateTime? selectedDate ;
then make _selectDate nullable return
// Select for Date
Future<DateTime?> _selectDate(BuildContext context) async {
final selected = await showDatePicker(
context: context,
initialDate: selectedDate??DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2025),
);
if (selected != null && selected != selectedDate) {
setState(() {
selectedDate = selected;
});
}
return selectedDate;
}
And while showing _selectTime check selectedDate if it null or not. Also make it null again if you like to avoid cancel second timer loop
// select date time picker
Future<void> _selectDateTime(BuildContext context) async {
final date = await _selectDate(context);
if (date == null) return;
selectedDate = null; // if you want to avoid second loop time picker on cancel
final time = await _selectTime(context);
You also need to add bang on getDate while selectedDate is now nullable. Using bang! because we've check it's null
String getDate() {
// ignore: unnecessary_null_comparison
if (selectedDate == null) {
return 'select date';
} else {
return DateFormat('MMM d, yyyy').format(selectedDate);
}
}

Flutter: How to enable only certain dates in DatePicker

I have an array with certain dates.
I want to disable these dates in the date picker and also change the color. How to do this?
You can use selectableDayPredicate property. For colors, you can change it by themes.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
DateTime selectedDate = DateTime(2020, 1, 14);
// DatePicker will call this function on every day and expect
// a bool output. If it's true, it will draw that day as "enabled"
// and that day will be selectable and vice versa.
bool _predicate(DateTime day) {
if ((day.isAfter(DateTime(2020, 1, 5)) &&
day.isBefore(DateTime(2020, 1, 9)))) {
return true;
}
if ((day.isAfter(DateTime(2020, 1, 10)) &&
day.isBefore(DateTime(2020, 1, 15)))) {
return true;
}
if ((day.isAfter(DateTime(2020, 2, 5)) &&
day.isBefore(DateTime(2020, 2, 17)))) {
return true;
}
return false;
}
Future<void> _selectDate(BuildContext context) async {
final DateTime picked = await showDatePicker(
context: context,
initialDate: selectedDate,
selectableDayPredicate: _predicate,
firstDate: DateTime(2019),
lastDate: DateTime(2021),
builder: (context, child) {
return Theme(
data: ThemeData(
primaryColor: Colors.orangeAccent,
disabledColor: Colors.brown,
textTheme:
TextTheme(body1: TextStyle(color: Colors.blueAccent)),
accentColor: Colors.yellow),
child: child,
);
});
if (picked != null && picked != selectedDate)
setState(() {
selectedDate = picked;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("${selectedDate.toLocal()}".split(' ')[0]),
SizedBox(
height: 20.0,
),
RaisedButton(
onPressed: () => _selectDate(context),
child: Text('Select date'),
),
],
),
),
);
}
}
I encountered the same problem. After going back and forth, I did the following:
Created a list of specific dates:
static var unavailableDates = ["2020-08-14", "2020-08-20", "2020-08-13","2020-08-21","2020-08-23"];
Created an initial date variable:
static DateTime initialDate = DateTime.now();
formatted the initial date to get rid of the timestamp:
static DateFormat dateFormat = new DateFormat("yyyy-MM-dd");
String formattedDate = dateFormat.format(initialDate);
Put my unavailable dates in order:
unavailableDates.sort(((a, b) => a.compareTo(b)));
Added a way to verify the initial date does not fall on the disabled date. This jumps to the next available date. If you do not have something in place to verify unavailable date and initial date is not the same, you will get an exception:
for(var unavdate in unavailableDates){
if (unavdate.compareTo(formattedDate) == 0) {
formattedDate = unavdate;
fromStringDate = DateTime.parse(formattedDate);
initialDate = fromStringDate.add(new Duration(days: 1));
formattedDate = dateFormat.format(initialDate);
}
}
Created a day predicate function:
bool setDayPredicate(DateTime val) {
//this allows certain dates to be greyed out based on availability
String Dates = dateFormat.format(val); //formatting passed in value
return !unavailableDates.contains(Dates);
}
Put it all together:
date = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: new DateTime.now(),
lastDate: new DateTime.now().add(new Duration(days: 30)),
selectableDayPredicate: setDayPredicate, });

Flutter: DateTimeField manual editing problem

I have DateTimeField and there is strange issue while editing the date manually.
Here is the display with default value:
I selected month by double tapping and try to type 08 manually.
When I bring pointer at the end of month 12, and pressed backspace to remove 2 from 12. The month was changed to 01.
When I press backspace in the end of year, to remove 8 from 2018. It was changed to 0201.
Here is the code of that field:
DateTimeField(
format: DateFormat("yyyy-MM-dd hh:mm:ss"),
onSaved: (val) => setState(() => _fromDate = val),
keyboardType: TextInputType.datetime,
onChanged: (DateTime newValue) {
setState(() {
_fromDate = newValue;
});
},
onShowPicker: (context, currentValue) {
return showDatePicker(
context: context,
firstDate: DateTime.now(),
initialDate: currentValue ?? DateTime.now(),
lastDate: DateTime.now().add(new Duration(days: 30))
);
},
);
I have no clue, what's going on with this. Please tell me, what could be wrong?
NOTE:
Using picker for date selection works fine
I've date field in another page, it's in yyyy-MM-dd format, and it works as expected there.
So I've tried the available sample that you've provided. I got a different behavior, I'm not able to edit the value manually. The date picker always open whenever I've tried to edit the form.
Here is the complete minimal code that I've tested base from your code:
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:datetime_picker_formfield/datetime_picker_formfield.dart';
void main() => runApp(BasicDateTimeField());
class BasicDateTimeField extends StatefulWidget {
#override
_BasicDateTimeFieldState createState() => _BasicDateTimeFieldState();
}
class _BasicDateTimeFieldState extends State<BasicDateTimeField> {
#override
Widget build(BuildContext context) {
DateTime _fromDate;
return MaterialApp(
home: Scaffold(
body: Center(
child: DateTimeField(
format: DateFormat("yyyy-MM-dd hh:mm:ss"),
onSaved: (val) => setState(() => _fromDate = val),
keyboardType: TextInputType.datetime,
onChanged: (DateTime newValue) {
setState(() {
_fromDate = newValue;
});
},
onShowPicker: (context, currentValue) {
return showDatePicker(
context: context,
firstDate: DateTime.now(),
initialDate: currentValue ?? DateTime.now(),
lastDate: DateTime.now().add(new Duration(days: 30)));
},
),
),
),
);
}
}
Output:
In the sample, I've used the current version of datetime_picker_formfield: ^2.0.0 plugin.
Perhaps you can use the built-in DateTime picker instead.
Here is a sample demo that I've created for your reference:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
theme: ThemeData(primaryColor: Colors.blue),
home: MyWidget(),
),
);
}
class MyWidget extends StatefulWidget {
createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
DateTime selectedDate = DateTime.now();
Future<void> _selectDate(BuildContext context) async {
final DateTime picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(2015, 8),
lastDate: DateTime(2101));
if (picked != null && picked != selectedDate)
setState(() {
selectedDate = picked;
});
}
#override
initState() {
super.initState();
selectedDate = DateTime.now();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo App"),
),
body: ListView(padding: const EdgeInsets.all(16.0), children: [
Container(
height: 100,
child: FlutterLogo(),
),
SizedBox(height: 10.0),
InputDatePickerFormField(
firstDate: DateTime(2015, 8),
lastDate: DateTime(2101),
initialDate: selectedDate,
onDateSubmitted: (date) {
setState(() {
selectedDate = date;
});
},
),
// Text("Selected Date: $selectedDate"),
ElevatedButton(
onPressed: () => _selectDate(context),
child: Text('Select date'),
)
]),
);
}
}
Output:
This behavior means that you can not manually change the date select use your picker to change it