Date & Time Pickers
Date & Time Pickers
Flutter ships two Material dialog functions — showDatePicker and showTimePicker — that surface the platform's native-feeling date and clock wheels inside your form. Because these dialogs are async, the chosen value is returned as a Future. Storing that value inside a FormField gives you automatic validation and integrates cleanly with the rest of the Form / GlobalKey<FormState> pattern you already know.
Launching the Date Picker
showDatePicker is a top-level function from package:flutter/material.dart. It requires three parameters:
- context — the current
BuildContext, used to position the dialog and inherit the theme. - initialDate — the
DateTimehighlighted when the dialog opens (typically today or a previously saved value). - firstDate — the earliest selectable date.
- lastDate — the latest selectable date.
The function returns Future<DateTime?>. If the user dismisses the dialog without confirming, the future resolves to null.
Example — basic date picker
DateTime? _selectedDate;
Future<void> _pickDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: _selectedDate ?? DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (picked != null) {
setState(() {
_selectedDate = picked;
});
}
}
Launching the Time Picker
showTimePicker works the same way but returns Future<TimeOfDay?>. The TimeOfDay class holds an hour (0–23) and minute (0–59). You can combine it with a DateTime to build a full timestamp.
Example — date + time combined
DateTime? _selectedDate;
TimeOfDay? _selectedTime;
Future<void> _pickDateTime(BuildContext context) async {
final DateTime? date = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (date == null) return;
final TimeOfDay? time = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (time == null) return;
setState(() {
_selectedDate = DateTime(
date.year, date.month, date.day,
time.hour, time.minute,
);
});
}
Displaying a Formatted Value in a TextFormField
The most common UI pattern is a read-only TextFormField that shows the formatted date and opens the picker when tapped. Set readOnly: true to prevent the keyboard from appearing, and use the onTap callback to call your picker method. A TextEditingController keeps the displayed text in sync with the selected value.
Example — tappable TextFormField showing formatted date
final TextEditingController _dateController = TextEditingController();
DateTime? _selectedDate;
@override
void dispose() {
_dateController.dispose();
super.dispose();
}
Future<void> _pickDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: _selectedDate ?? DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (picked != null) {
setState(() {
_selectedDate = picked;
_dateController.text =
'${picked.year}-${picked.month.toString().padLeft(2, '0')}'
'-${picked.day.toString().padLeft(2, '0')}';
});
}
}
// Inside build():
TextFormField(
controller: _dateController,
readOnly: true,
decoration: const InputDecoration(
labelText: 'Date of birth',
suffixIcon: Icon(Icons.calendar_today),
),
onTap: () => _pickDate(context),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please select a date';
}
return null;
},
),
Using FormField for True Form Integration
Wrapping the picker in a FormField<DateTime> (or using the FormBuilderDateTimePicker from the community flutter_form_builder package) stores the value directly in the form's save/validate cycle. The key advantage is that _formKey.currentState!.save() will call your onSaved callback automatically.
intl package's DateFormat class for locale-aware formatting — e.g., DateFormat.yMMMMd('en_US').format(date) produces "January 15, 2025". This is far more robust than manual string interpolation.Validation: Ensuring a Date Was Selected
Because the date picker is not a keyboard input, your validator must check for null (nothing chosen yet). The pattern is straightforward:
- Keep a
DateTime?field — nullable signals "not yet chosen". - In the validator, return an error string when the value is
null. - For range checks (e.g. date must be in the future), compare against
DateTime.now()inside the validator.
await inside a validator callback — validators are synchronous. Perform async checks (e.g. checking availability on a server) before calling validate(), not inside it.Summary
The complete pattern for a validated date/time picker in a Flutter form is: (1) declare a nullable DateTime? state variable; (2) display it via a readOnly TextFormField with an onTap that calls showDatePicker; (3) on confirmation, call setState to update both the variable and the controller text; (4) write a synchronous validator that returns an error string when the variable is still null. This integrates fully with Form and GlobalKey<FormState>, giving you the same validate/save cycle as any other form field.