Forms, Validation & User Input

Date & Time Pickers

15 min Lesson 10 of 12

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 DateTime highlighted 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.

Tip: Use the 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.
Warning: Never use 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.