showTimePicker function

Future<TimeOfDay?> showTimePicker({
  1. required BuildContext context,
  2. required TimeOfDay initialTime,
  3. TransitionBuilder? builder,
  4. bool barrierDismissible = true,
  5. Color? barrierColor,
  6. String? barrierLabel,
  7. bool useRootNavigator = true,
  8. TimePickerEntryMode initialEntryMode = TimePickerEntryMode.dial,
  9. String? cancelText,
  10. String? confirmText,
  11. String? helpText,
  12. String? errorInvalidText,
  13. String? hourLabelText,
  14. String? minuteLabelText,
  15. RouteSettings? routeSettings,
  16. EntryModeChangeCallback? onEntryModeChanged,
  17. Offset? anchorPoint,
  18. Orientation? orientation,
  19. Icon? switchToInputEntryModeIcon,
  20. Icon? switchToTimerEntryModeIcon,
  21. bool emptyInitialInput = false,
})

Shows a dialog containing a Material Design time picker.

The returned Future resolves to the time selected by the user when the user closes the dialog. If the user cancels the dialog, null is returned.

Show a dialog with initialTime equal to the current time.

Future<TimeOfDay?> selectedTime = showTimePicker(
  initialTime: TimeOfDay.now(),
  context: context,
);

The context, barrierDismissible, barrierColor, barrierLabel, useRootNavigator and routeSettings arguments are passed to showDialog, the documentation for which discusses how it is used.

The builder parameter can be used to wrap the dialog widget to add inherited widgets like Localizations.override, Directionality, or MediaQuery.

The initialEntryMode parameter can be used to determine the initial time entry selection of the picker (either a clock dial or text input).

Optional strings for the helpText, cancelText, errorInvalidText, hourLabelText, minuteLabelText and confirmText can be provided to override the default values.

The optional orientation parameter sets the Orientation to use when displaying the dialog. By default, the orientation is derived from the MediaQueryData.size of the ambient MediaQuery: wide sizes use the landscape orientation, and tall sizes use the portrait orientation. Use this parameter to override the default and force the dialog to appear in either portrait or landscape mode.

The optional switchToInputEntryModeIcon argument can be used to customize the input method icon that is shown when the TimePickerEntryMode is TimePickerEntryMode.dial.

Defaults to an Icon widget with Icons.keyboard_outlined as icon.

The optional switchToTimerEntryModeIcon argument can be used to customize the input method icon that is shown when the TimePickerEntryMode is TimePickerEntryMode.input.

Defaults to an Icon widget with Icons.access_time as icon.

A DisplayFeature can split the screen into sub-screens. The closest one to anchorPoint is used to render the content.

If no anchorPoint is provided, then Directionality is used:

  • for TextDirection.ltr, anchorPoint is Offset.zero, which will cause the content to appear in the top-left sub-screen.
  • for TextDirection.rtl, anchorPoint is Offset(double.maxFinite, 0), which will cause the content to appear in the top-right sub-screen.

If no anchorPoint is provided, and there is no Directionality ancestor widget in the tree, then the widget asserts during build in debug mode.

By default, the time picker gets its colors from the overall theme's ColorScheme. The time picker can be further customized by providing a TimePickerThemeData to the overall theme.

Show a dialog with the text direction overridden to be TextDirection.rtl.

Future<TimeOfDay?> selectedTimeRTL = showTimePicker(
  context: context,
  initialTime: TimeOfDay.now(),
  builder: (BuildContext context, Widget? child) {
    return Directionality(
      textDirection: TextDirection.rtl,
      child: child!,
    );
  },
);

Show a dialog with time unconditionally displayed in 24 hour format.

Future<TimeOfDay?> selectedTime24Hour = showTimePicker(
  context: context,
  initialTime: const TimeOfDay(hour: 10, minute: 47),
  builder: (BuildContext context, Widget? child) {
    return MediaQuery(
      data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true),
      child: child!,
    );
  },
);

This example illustrates how to open a time picker, and allows exploring some of the variations in the types of time pickers that may be shown.

To see it in action, copy and run this code snippet on DartPad.

import 'package:material_ui/material_ui.dart';

/// Flutter code sample for [showTimePicker].

void main() {
  runApp(const ShowTimePickerApp());
}

class ShowTimePickerApp extends StatefulWidget {
  const ShowTimePickerApp({super.key});

  @override
  State<ShowTimePickerApp> createState() => _ShowTimePickerAppState();
}

class _ShowTimePickerAppState extends State<ShowTimePickerApp> {
  ThemeMode themeMode = .dark;
  bool useMaterial3 = true;

  void setThemeMode(ThemeMode mode) {
    setState(() {
      themeMode = mode;
    });
  }

  void setUseMaterial3(bool? value) {
    setState(() {
      useMaterial3 = value!;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.light(useMaterial3: useMaterial3),
      darkTheme: ThemeData.dark(useMaterial3: useMaterial3),
      themeMode: themeMode,
      home: TimePickerOptions(
        themeMode: themeMode,
        useMaterial3: useMaterial3,
        setThemeMode: setThemeMode,
        setUseMaterial3: setUseMaterial3,
      ),
    );
  }
}

class TimePickerOptions extends StatefulWidget {
  const TimePickerOptions({
    super.key,
    required this.themeMode,
    required this.useMaterial3,
    required this.setThemeMode,
    required this.setUseMaterial3,
  });

  final ThemeMode themeMode;
  final bool useMaterial3;
  final ValueChanged<ThemeMode> setThemeMode;
  final ValueChanged<bool?> setUseMaterial3;

  @override
  State<TimePickerOptions> createState() => _TimePickerOptionsState();
}

class _TimePickerOptionsState extends State<TimePickerOptions> {
  TimeOfDay? selectedTime;
  TimePickerEntryMode entryMode = .dial;
  Orientation? orientation;
  TextDirection textDirection = .ltr;
  MaterialTapTargetSize tapTargetSize = .padded;
  bool use24HourTime = false;

  void _entryModeChanged(TimePickerEntryMode? value) {
    if (value != entryMode) {
      setState(() {
        entryMode = value!;
      });
    }
  }

  void _orientationChanged(Orientation? value) {
    if (value != orientation) {
      setState(() {
        orientation = value;
      });
    }
  }

  void _textDirectionChanged(TextDirection? value) {
    if (value != textDirection) {
      setState(() {
        textDirection = value!;
      });
    }
  }

  void _tapTargetSizeChanged(MaterialTapTargetSize? value) {
    if (value != tapTargetSize) {
      setState(() {
        tapTargetSize = value!;
      });
    }
  }

  void _use24HourTimeChanged(bool? value) {
    if (value != use24HourTime) {
      setState(() {
        use24HourTime = value!;
      });
    }
  }

  void _themeModeChanged(ThemeMode? value) {
    widget.setThemeMode(value!);
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Column(
        children: <Widget>[
          Expanded(
            child: GridView(
              gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
                maxCrossAxisExtent: 350,
                mainAxisSpacing: 4,
                mainAxisExtent: 200,
                crossAxisSpacing: 4,
              ),
              children: <Widget>[
                EnumCard<TimePickerEntryMode>(
                  choices: TimePickerEntryMode.values,
                  value: entryMode,
                  onChanged: _entryModeChanged,
                ),
                EnumCard<ThemeMode>(
                  choices: ThemeMode.values,
                  value: widget.themeMode,
                  onChanged: _themeModeChanged,
                ),
                EnumCard<TextDirection>(
                  choices: TextDirection.values,
                  value: textDirection,
                  onChanged: _textDirectionChanged,
                ),
                EnumCard<MaterialTapTargetSize>(
                  choices: MaterialTapTargetSize.values,
                  value: tapTargetSize,
                  onChanged: _tapTargetSizeChanged,
                ),
                ChoiceCard<Orientation?>(
                  choices: const <Orientation?>[...Orientation.values, null],
                  value: orientation,
                  title: '$Orientation',
                  choiceLabels: <Orientation?, String>{
                    for (final Orientation choice in Orientation.values)
                      choice: choice.name,
                    null: 'from MediaQuery',
                  },
                  onChanged: _orientationChanged,
                ),
                ChoiceCard<bool>(
                  choices: const <bool>[false, true],
                  value: use24HourTime,
                  onChanged: _use24HourTimeChanged,
                  title: 'Time Mode',
                  choiceLabels: const <bool, String>{
                    false: '12-hour am/pm time',
                    true: '24-hour time',
                  },
                ),
                ChoiceCard<bool>(
                  choices: const <bool>[false, true],
                  value: widget.useMaterial3,
                  onChanged: widget.setUseMaterial3,
                  title: 'Material Version',
                  choiceLabels: const <bool, String>{
                    false: 'Material 2',
                    true: 'Material 3',
                  },
                ),
              ],
            ),
          ),
          SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Row(
              mainAxisAlignment: .center,
              children: <Widget>[
                Padding(
                  padding: const .all(12.0),
                  child: ElevatedButton(
                    child: const Text('Open time picker'),
                    onPressed: () async {
                      final TimeOfDay? time = await showTimePicker(
                        context: context,
                        initialTime: selectedTime ?? TimeOfDay.now(),
                        initialEntryMode: entryMode,
                        orientation: orientation,
                        builder: (BuildContext context, Widget? child) {
                          // We just wrap these environmental changes around the
                          // child in this builder so that we can apply the
                          // options selected above. In regular usage, this is
                          // rarely necessary, because the default values are
                          // usually used as-is.
                          return Theme(
                            data: Theme.of(
                              context,
                            ).copyWith(materialTapTargetSize: tapTargetSize),
                            child: Directionality(
                              textDirection: textDirection,
                              child: MediaQuery(
                                data: MediaQuery.of(context).copyWith(
                                  alwaysUse24HourFormat: use24HourTime,
                                ),
                                child: child!,
                              ),
                            ),
                          );
                        },
                      );
                      setState(() {
                        selectedTime = time;
                      });
                    },
                  ),
                ),
                if (selectedTime != null)
                  Text('Selected time: ${selectedTime!.format(context)}'),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// This is a simple card that presents a set of radio buttons (inside of a
// RadioSelection, defined below) for the user to select from.
class ChoiceCard<T extends Object?> extends StatelessWidget {
  const ChoiceCard({
    super.key,
    required this.value,
    required this.choices,
    required this.onChanged,
    required this.choiceLabels,
    required this.title,
  });

  final T value;
  final Iterable<T> choices;
  final Map<T, String> choiceLabels;
  final String title;
  final ValueChanged<T?> onChanged;

  @override
  Widget build(BuildContext context) {
    return Card(
      // If the card gets too small, let it scroll both directions.
      child: SingleChildScrollView(
        child: SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: Padding(
            padding: const .all(8.0),
            child: RadioGroup<T>(
              groupValue: value,
              onChanged: onChanged,
              child: Column(
                crossAxisAlignment: .start,
                children: <Widget>[
                  Padding(padding: const .all(8.0), child: Text(title)),
                  for (final T choice in choices)
                    RadioSelection<T>(
                      value: choice,
                      child: Text(choiceLabels[choice]!),
                    ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

// This aggregates a ChoiceCard so that it presents a set of radio buttons for
// the allowed enum values for the user to select from.
class EnumCard<T extends Enum> extends StatelessWidget {
  const EnumCard({
    super.key,
    required this.value,
    required this.choices,
    required this.onChanged,
  });

  final T value;
  final Iterable<T> choices;
  final ValueChanged<T?> onChanged;

  @override
  Widget build(BuildContext context) {
    return ChoiceCard<T>(
      value: value,
      choices: choices,
      onChanged: onChanged,
      choiceLabels: <T, String>{
        for (final T choice in choices) choice: choice.name,
      },
      title: value.runtimeType.toString(),
    );
  }
}

// A button that has a radio button on one side and a label child. Tapping on
// the label or the radio button selects the item.
class RadioSelection<T extends Object?> extends StatelessWidget {
  const RadioSelection({super.key, required this.value, required this.child});

  final T value;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: .min,
      children: <Widget>[
        Padding(
          padding: const .directional(end: 8),
          child: Radio<T>(value: value),
        ),
        GestureDetector(
          onTap: () => RadioGroup.maybeOf<T>(context)?.onChanged(value),
          child: child,
        ),
      ],
    );
  }
}

See also:

Implementation

// TODO(framework): Add unit tests to this code snippet.
// https://github.com/flutter/flutter/issues/188530
/// Show a dialog with [initialTime] equal to the current time.
///
/// ```dart
/// Future<TimeOfDay?> selectedTime = showTimePicker(
///   initialTime: TimeOfDay.now(),
///   context: context,
/// );
/// ```
///
/// </callout-box>
///
/// The [context], [barrierDismissible], [barrierColor], [barrierLabel],
/// [useRootNavigator] and [routeSettings] arguments are passed to [showDialog],
/// the documentation for which discusses how it is used.
///
/// The [builder] parameter can be used to wrap the dialog widget to add
/// inherited widgets like [Localizations.override], [Directionality], or
/// [MediaQuery].
///
/// The `initialEntryMode` parameter can be used to determine the initial time
/// entry selection of the picker (either a clock dial or text input).
///
/// Optional strings for the [helpText], [cancelText], [errorInvalidText],
/// [hourLabelText], [minuteLabelText] and [confirmText] can be provided to
/// override the default values.
///
/// The optional [orientation] parameter sets the [Orientation] to use when
/// displaying the dialog. By default, the orientation is derived from the
/// [MediaQueryData.size] of the ambient [MediaQuery]: wide sizes use the
/// landscape orientation, and tall sizes use the portrait orientation. Use this
/// parameter to override the default and force the dialog to appear in either
/// portrait or landscape mode.
///
/// {@template material_ui.time_picker.switchToInputEntryModeIcon}
/// The optional [switchToInputEntryModeIcon] argument can be used to customize
/// the input method icon that is shown when the [TimePickerEntryMode]
/// is [TimePickerEntryMode.dial].
///
/// Defaults to an [Icon] widget with [Icons.keyboard_outlined] as icon.
/// {@endtemplate}
///
/// {@template material_ui.time_picker.switchToTimerEntryModeIcon}
/// The optional [switchToTimerEntryModeIcon] argument can be used to customize
/// the input method icon that is shown when the [TimePickerEntryMode]
/// is [TimePickerEntryMode.input].
///
/// Defaults to an [Icon] widget with [Icons.access_time] as icon.
/// {@endtemplate}
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// By default, the time picker gets its colors from the overall theme's
/// [ColorScheme]. The time picker can be further customized by providing a
/// [TimePickerThemeData] to the overall theme.
///
/// <callout-box>
///
// TODO(framework): Add unit tests to this code snippet.
// https://github.com/flutter/flutter/issues/188530
/// Show a dialog with the text direction overridden to be
/// [TextDirection.rtl].
///
/// ```dart
/// Future<TimeOfDay?> selectedTimeRTL = showTimePicker(
///   context: context,
///   initialTime: TimeOfDay.now(),
///   builder: (BuildContext context, Widget? child) {
///     return Directionality(
///       textDirection: TextDirection.rtl,
///       child: child!,
///     );
///   },
/// );
/// ```
///
/// </callout-box>
///
/// <callout-box>
///
// TODO(framework): Add unit tests to this code snippet.
// https://github.com/flutter/flutter/issues/188530
/// Show a dialog with time unconditionally displayed in 24 hour
/// format.
///
/// ```dart
/// Future<TimeOfDay?> selectedTime24Hour = showTimePicker(
///   context: context,
///   initialTime: const TimeOfDay(hour: 10, minute: 47),
///   builder: (BuildContext context, Widget? child) {
///     return MediaQuery(
///       data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true),
///       child: child!,
///     );
///   },
/// );
/// ```
///
/// </callout-box>
///
/// <callout-box>
///
/// This example illustrates how to open a time picker, and allows exploring
/// some of the variations in the types of time pickers that may be shown.
///
// TODO(framework): Replace the following block with a @dartpad directive
// when it's supported. https://github.com/dart-lang/dartdoc/issues/4123
/// {@macro material_ui.dartpad_guide}
///
/// {@example /example/lib/time_picker/show_time_picker.0.dart#body}
///
/// </callout-box>
///
/// See also:
///
/// * [showDatePicker], which shows a dialog that contains a Material Design
///   date picker.
/// * [TimePickerThemeData], which allows you to customize the colors,
///   typography, and shape of the time picker.
/// * [DisplayFeatureSubScreen], which documents the specifics of how
///   [DisplayFeature]s can split the screen into sub-screens.
Future<TimeOfDay?> showTimePicker({
  required BuildContext context,
  required TimeOfDay initialTime,
  TransitionBuilder? builder,
  bool barrierDismissible = true,
  Color? barrierColor,
  String? barrierLabel,
  bool useRootNavigator = true,
  TimePickerEntryMode initialEntryMode = TimePickerEntryMode.dial,
  String? cancelText,
  String? confirmText,
  String? helpText,
  String? errorInvalidText,
  String? hourLabelText,
  String? minuteLabelText,
  RouteSettings? routeSettings,
  EntryModeChangeCallback? onEntryModeChanged,
  Offset? anchorPoint,
  Orientation? orientation,
  Icon? switchToInputEntryModeIcon,
  Icon? switchToTimerEntryModeIcon,
  bool emptyInitialInput = false,
}) async {
  assert(debugCheckHasMaterialLocalizations(context));

  final Widget dialog = TimePickerDialog(
    initialTime: initialTime,
    initialEntryMode: initialEntryMode,
    cancelText: cancelText,
    confirmText: confirmText,
    helpText: helpText,
    errorInvalidText: errorInvalidText,
    hourLabelText: hourLabelText,
    minuteLabelText: minuteLabelText,
    orientation: orientation,
    onEntryModeChanged: onEntryModeChanged,
    switchToInputEntryModeIcon: switchToInputEntryModeIcon,
    switchToTimerEntryModeIcon: switchToTimerEntryModeIcon,
    emptyInitialInput: emptyInitialInput,
  );
  return showDialog<TimeOfDay>(
    context: context,
    barrierDismissible: barrierDismissible,
    barrierColor: barrierColor,
    barrierLabel: barrierLabel,
    useRootNavigator: useRootNavigator,
    builder: (BuildContext context) {
      return builder == null ? dialog : builder(context, dialog);
    },
    routeSettings: routeSettings,
    anchorPoint: anchorPoint,
  );
}