showDateRangePicker function

Future<DateTimeRange?> showDateRangePicker(
  1. {required BuildContext context,
  2. DateTimeRange? initialDateRange,
  3. required DateTime firstDate,
  4. required DateTime lastDate,
  5. DateTime? currentDate,
  6. DatePickerEntryMode initialEntryMode = DatePickerEntryMode.calendar,
  7. String? helpText,
  8. String? cancelText,
  9. String? confirmText,
  10. String? saveText,
  11. String? errorFormatText,
  12. String? errorInvalidText,
  13. String? errorInvalidRangeText,
  14. String? fieldStartHintText,
  15. String? fieldEndHintText,
  16. String? fieldStartLabelText,
  17. String? fieldEndLabelText,
  18. Locale? locale,
  19. bool barrierDismissible = true,
  20. Color? barrierColor,
  21. String? barrierLabel,
  22. bool useRootNavigator = true,
  23. RouteSettings? routeSettings,
  24. TextDirection? textDirection,
  25. TransitionBuilder? builder,
  26. Offset? anchorPoint,
  27. TextInputType keyboardType = TextInputType.datetime,
  28. Icon? switchToInputEntryModeIcon,
  29. Icon? switchToCalendarEntryModeIcon}
)

Shows a full screen modal dialog containing a Material Design date range picker.

The returned Future resolves to the DateTimeRange selected by the user when the user saves their selection. If the user cancels the dialog, null is returned.

If initialDateRange is non-null, then it will be used as the initially selected date range. If it is provided, initialDateRange.start must be before or on initialDateRange.end.

The firstDate is the earliest allowable date. The lastDate is the latest allowable date.

If an initial date range is provided, initialDateRange.start and initialDateRange.end must both fall between or on firstDate and lastDate. For all of these DateTime values, only their dates are considered. Their time fields are ignored.

The currentDate represents the current day (i.e. today). This date will be highlighted in the day grid. If null, the date of DateTime.now() will be used.

An optional initialEntryMode argument can be used to display the date picker in the DatePickerEntryMode.calendar (a scrollable calendar month grid) or DatePickerEntryMode.input (two text input fields) mode. It defaults to DatePickerEntryMode.calendar.

An optional switchToInputEntryModeIcon argument can be used to display a custom Icon in the corner of the dialog when DatePickerEntryMode is DatePickerEntryMode.calendar. Clicking on icon changes the DatePickerEntryMode to DatePickerEntryMode.input. If null, Icon(useMaterial3 ? Icons.edit_outlined : Icons.edit) is used.

An optional switchToCalendarEntryModeIcon argument can be used to display a custom Icon in the corner of the dialog when DatePickerEntryMode is DatePickerEntryMode.input. Clicking on icon changes the DatePickerEntryMode to DatePickerEntryMode.calendar. If null, Icon(Icons.calendar_today) is used.

The following optional string parameters allow you to override the default text used for various parts of the dialog:

  • helpText, the label displayed at the top of the dialog.
  • cancelText, the label on the cancel button for the text input mode.
  • confirmText,the label on the ok button for the text input mode.
  • saveText, the label on the save button for the fullscreen calendar mode.
  • errorFormatText, the message used when an input text isn't in a proper date format.
  • errorInvalidText, the message used when an input text isn't a selectable date.
  • errorInvalidRangeText, the message used when the date range is invalid (e.g. start date is after end date).
  • fieldStartHintText, the text used to prompt the user when no text has been entered in the start field.
  • fieldEndHintText, the text used to prompt the user when no text has been entered in the end field.
  • fieldStartLabelText, the label for the start date text input field.
  • fieldEndLabelText, the label for the end date text input field.

An optional locale argument can be used to set the locale for the date picker. It defaults to the ambient locale provided by Localizations.

An optional textDirection argument can be used to set the text direction (TextDirection.ltr or TextDirection.rtl) for the date picker. It defaults to the ambient text direction provided by Directionality. If both locale and textDirection are non-null, textDirection overrides the direction chosen for the locale.

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 Theme.

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.

State Restoration

Using this method will not enable state restoration for the date range picker. In order to enable state restoration for a date range picker, use Navigator.restorablePush or Navigator.restorablePushNamed with DateRangePickerDialog.

For more information about state restoration, see RestorationManager.

To test state restoration on Android:

  1. Turn on "Don't keep activities", which destroys the Android activity as soon as the user leaves it. This option should become available when Developer Options are turned on for the device.
  2. Run the code sample on an Android device.
  3. Create some in-memory state in the app on the phone, e.g. by navigating to a different screen.
  4. Background the Flutter app, then return to it. It will restart and restore its state.

To test state restoration on iOS:

  1. Open ios/Runner.xcworkspace/ in Xcode.
  2. (iOS 14+ only): Switch to build in profile or release mode, as launching an app from the home screen is not supported in debug mode.
  3. Press the Play button in Xcode to build and run the app.
  4. Create some in-memory state in the app on the phone, e.g. by navigating to a different screen.
  5. Background the app on the phone, e.g. by going back to the home screen.
  6. Press the Stop button in Xcode to terminate the app while running in the background.
  7. Open the app again on the phone (not via Xcode). It will restart and restore its state.

This sample demonstrates how to create a restorable Material date range picker. This is accomplished by enabling state restoration by specifying MaterialApp.restorationScopeId and using Navigator.restorablePush to push DateRangePickerDialog when the button is tapped.
link

To create a local project with this code sample, run:
flutter create --sample=material.showDateRangePicker.1 mysample

import 'package:flutter/material.dart';

/// Flutter code sample for [showDateRangePicker].

void main() => runApp(const DatePickerApp());

class DatePickerApp extends StatelessWidget {
  const DatePickerApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(useMaterial3: true),
      restorationScopeId: 'app',
      home: const DatePickerExample(restorationId: 'main'),
    );
  }
}

class DatePickerExample extends StatefulWidget {
  const DatePickerExample({super.key, this.restorationId});

  final String? restorationId;

  @override
  State<DatePickerExample> createState() => _DatePickerExampleState();
}

/// RestorationProperty objects can be used because of RestorationMixin.
class _DatePickerExampleState extends State<DatePickerExample>
    with RestorationMixin {
  // In this example, the restoration ID for the mixin is passed in through
  // the [StatefulWidget]'s constructor.
  @override
  String? get restorationId => widget.restorationId;

  final RestorableDateTimeN _startDate = RestorableDateTimeN(DateTime(2021));
  final RestorableDateTimeN _endDate =
      RestorableDateTimeN(DateTime(2021, 1, 5));
  late final RestorableRouteFuture<DateTimeRange?>
      _restorableDateRangePickerRouteFuture =
      RestorableRouteFuture<DateTimeRange?>(
    onComplete: _selectDateRange,
    onPresent: (NavigatorState navigator, Object? arguments) {
      return navigator
          .restorablePush(_dateRangePickerRoute, arguments: <String, dynamic>{
        'initialStartDate': _startDate.value?.millisecondsSinceEpoch,
        'initialEndDate': _endDate.value?.millisecondsSinceEpoch,
      });
    },
  );

  void _selectDateRange(DateTimeRange? newSelectedDate) {
    if (newSelectedDate != null) {
      setState(() {
        _startDate.value = newSelectedDate.start;
        _endDate.value = newSelectedDate.end;
      });
    }
  }

  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(_startDate, 'start_date');
    registerForRestoration(_endDate, 'end_date');
    registerForRestoration(
        _restorableDateRangePickerRouteFuture, 'date_picker_route_future');
  }

  @pragma('vm:entry-point')
  static Route<DateTimeRange?> _dateRangePickerRoute(
    BuildContext context,
    Object? arguments,
  ) {
    return DialogRoute<DateTimeRange?>(
      context: context,
      builder: (BuildContext context) {
        return DateRangePickerDialog(
          restorationId: 'date_picker_dialog',
          initialDateRange:
              _initialDateTimeRange(arguments! as Map<dynamic, dynamic>),
          firstDate: DateTime(2021),
          currentDate: DateTime(2021, 1, 25),
          lastDate: DateTime(2022),
        );
      },
    );
  }

  static DateTimeRange? _initialDateTimeRange(Map<dynamic, dynamic> arguments) {
    if (arguments['initialStartDate'] != null &&
        arguments['initialEndDate'] != null) {
      return DateTimeRange(
        start: DateTime.fromMillisecondsSinceEpoch(
            arguments['initialStartDate'] as int),
        end: DateTime.fromMillisecondsSinceEpoch(
            arguments['initialEndDate'] as int),
      );
    }

    return null;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: OutlinedButton(
          onPressed: () {
            _restorableDateRangePickerRouteFuture.present();
          },
          child: const Text('Open Date Range Picker'),
        ),
      ),
    );
  }
}

See also:

Implementation

Future<DateTimeRange?> showDateRangePicker({
  required BuildContext context,
  DateTimeRange? initialDateRange,
  required DateTime firstDate,
  required DateTime lastDate,
  DateTime? currentDate,
  DatePickerEntryMode initialEntryMode = DatePickerEntryMode.calendar,
  String? helpText,
  String? cancelText,
  String? confirmText,
  String? saveText,
  String? errorFormatText,
  String? errorInvalidText,
  String? errorInvalidRangeText,
  String? fieldStartHintText,
  String? fieldEndHintText,
  String? fieldStartLabelText,
  String? fieldEndLabelText,
  Locale? locale,
  bool barrierDismissible = true,
  Color? barrierColor,
  String? barrierLabel,
  bool useRootNavigator = true,
  RouteSettings? routeSettings,
  TextDirection? textDirection,
  TransitionBuilder? builder,
  Offset? anchorPoint,
  TextInputType keyboardType = TextInputType.datetime,
  final Icon? switchToInputEntryModeIcon,
  final Icon? switchToCalendarEntryModeIcon,
}) async {
  assert(
    initialDateRange == null || !initialDateRange.start.isAfter(initialDateRange.end),
    "initialDateRange's start date must not be after it's end date.",
  );
  initialDateRange = initialDateRange == null ? null : DateUtils.datesOnly(initialDateRange);
  firstDate = DateUtils.dateOnly(firstDate);
  lastDate = DateUtils.dateOnly(lastDate);
  assert(
    !lastDate.isBefore(firstDate),
    'lastDate $lastDate must be on or after firstDate $firstDate.',
  );
  assert(
    initialDateRange == null || !initialDateRange.start.isBefore(firstDate),
    "initialDateRange's start date must be on or after firstDate $firstDate.",
  );
  assert(
    initialDateRange == null || !initialDateRange.end.isBefore(firstDate),
    "initialDateRange's end date must be on or after firstDate $firstDate.",
  );
  assert(
    initialDateRange == null || !initialDateRange.start.isAfter(lastDate),
    "initialDateRange's start date must be on or before lastDate $lastDate.",
  );
  assert(
    initialDateRange == null || !initialDateRange.end.isAfter(lastDate),
    "initialDateRange's end date must be on or before lastDate $lastDate.",
  );
  currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now());
  assert(debugCheckHasMaterialLocalizations(context));

  Widget dialog = DateRangePickerDialog(
    initialDateRange: initialDateRange,
    firstDate: firstDate,
    lastDate: lastDate,
    currentDate: currentDate,
    initialEntryMode: initialEntryMode,
    helpText: helpText,
    cancelText: cancelText,
    confirmText: confirmText,
    saveText: saveText,
    errorFormatText: errorFormatText,
    errorInvalidText: errorInvalidText,
    errorInvalidRangeText: errorInvalidRangeText,
    fieldStartHintText: fieldStartHintText,
    fieldEndHintText: fieldEndHintText,
    fieldStartLabelText: fieldStartLabelText,
    fieldEndLabelText: fieldEndLabelText,
    keyboardType: keyboardType,
    switchToInputEntryModeIcon: switchToInputEntryModeIcon,
    switchToCalendarEntryModeIcon: switchToCalendarEntryModeIcon,
  );

  if (textDirection != null) {
    dialog = Directionality(
      textDirection: textDirection,
      child: dialog,
    );
  }

  if (locale != null) {
    dialog = Localizations.override(
      context: context,
      locale: locale,
      child: dialog,
    );
  }

  return showDialog<DateTimeRange>(
    context: context,
    barrierDismissible: barrierDismissible,
    barrierColor: barrierColor,
    barrierLabel: barrierLabel,
    useRootNavigator: useRootNavigator,
    routeSettings: routeSettings,
    useSafeArea: false,
    builder: (BuildContext context) {
      return builder == null ? dialog : builder(context, dialog);
    },
    anchorPoint: anchorPoint,
  );
}