Unverified Commit e58ee0fb authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

[State Restoration] Material DateRangePicker, adds some general state restoration tests (#78506)

parent 2977a346
...@@ -901,14 +901,14 @@ class _DatePickerHeader extends StatelessWidget { ...@@ -901,14 +901,14 @@ class _DatePickerHeader extends StatelessWidget {
/// returned. /// returned.
/// ///
/// If [initialDateRange] is non-null, then it will be used as the initially /// If [initialDateRange] is non-null, then it will be used as the initially
/// selected date range. If it is provided, [initialDateRange.start] must be /// selected date range. If it is provided, `initialDateRange.start` must be
/// before or on [initialDateRange.end]. /// before or on `initialDateRange.end`.
/// ///
/// The [firstDate] is the earliest allowable date. The [lastDate] is the latest /// The [firstDate] is the earliest allowable date. The [lastDate] is the latest
/// allowable date. Both must be non-null. /// allowable date. Both must be non-null.
/// ///
/// If an initial date range is provided, [initialDateRange.start] /// If an initial date range is provided, `initialDateRange.start`
/// and [initialDateRange.end] must both fall between or on [firstDate] and /// and `initialDateRange.end` must both fall between or on [firstDate] and
/// [lastDate]. For all of these [DateTime] values, only their dates are /// [lastDate]. For all of these [DateTime] values, only their dates are
/// considered. Their time fields are ignored. /// considered. Their time fields are ignored.
/// ///
...@@ -958,6 +958,133 @@ class _DatePickerHeader extends StatelessWidget { ...@@ -958,6 +958,133 @@ class _DatePickerHeader extends StatelessWidget {
/// The [builder] parameter can be used to wrap the dialog widget /// The [builder] parameter can be used to wrap the dialog widget
/// to add inherited widgets like [Theme]. /// to add inherited widgets like [Theme].
/// ///
/// ### 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].
///
/// {@macro flutter.widgets.RestorationManager}
///
/// {@tool sample --template=freeform}
///
/// 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.
///
/// ```dart imports
/// import 'package:flutter/material.dart';
/// ```
///
/// ```dart
/// void main() {
/// runApp(const MyApp());
/// }
///
/// class MyApp extends StatelessWidget {
/// const MyApp({Key? key}) : super(key: key);
///
/// @override
/// Widget build(BuildContext context) {
/// return const MaterialApp(
/// restorationScopeId: 'app',
/// title: 'Restorable Date Range Picker Demo',
/// home: MyHomePage(),
/// );
/// }
/// }
///
/// class MyHomePage extends StatefulWidget {
/// const MyHomePage({Key? key}) : super(key: key);
///
/// @override
/// _MyHomePageState createState() => _MyHomePageState();
/// }
///
/// class _MyHomePageState extends State<MyHomePage> with RestorationMixin {
/// final RestorableDateTimeN _startDate = RestorableDateTimeN(DateTime(2021, 1, 1));
/// 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
/// String get restorationId => 'scaffold_state';
///
/// @override
/// void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
/// registerForRestoration(_startDate, 'start_date');
/// registerForRestoration(_endDate, 'end_date');
/// registerForRestoration(_restorableDateRangePickerRouteFuture, 'date_picker_route_future');
/// }
///
/// 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, 1, 1),
/// currentDate: DateTime(2021, 1, 25),
/// lastDate: DateTime(2022, 1, 1),
/// );
/// },
/// );
/// }
///
/// 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'),
/// ),
/// ),
/// );
/// }
/// }
/// ```
///
/// {@end-tool}
///
/// See also: /// See also:
/// ///
/// * [showDatePicker], which shows a material design date picker used to /// * [showDatePicker], which shows a material design date picker used to
...@@ -1027,7 +1154,7 @@ Future<DateTimeRange?> showDateRangePicker({ ...@@ -1027,7 +1154,7 @@ Future<DateTimeRange?> showDateRangePicker({
assert(useRootNavigator != null); assert(useRootNavigator != null);
assert(debugCheckHasMaterialLocalizations(context)); assert(debugCheckHasMaterialLocalizations(context));
Widget dialog = _DateRangePickerDialog( Widget dialog = DateRangePickerDialog(
initialDateRange: initialDateRange, initialDateRange: initialDateRange,
firstDate: firstDate, firstDate: firstDate,
lastDate: lastDate, lastDate: lastDate,
...@@ -1100,8 +1227,18 @@ String _formatRangeEndDate(MaterialLocalizations localizations, DateTime? startD ...@@ -1100,8 +1227,18 @@ String _formatRangeEndDate(MaterialLocalizations localizations, DateTime? startD
: localizations.formatShortDate(endDate); : localizations.formatShortDate(endDate);
} }
class _DateRangePickerDialog extends StatefulWidget { /// A Material-style date range picker dialog.
const _DateRangePickerDialog({ ///
/// It is used internally by [showDateRangePicker] or can be directly pushed
/// onto the [Navigator] stack to enable state restoration. See
/// [showDateRangePicker] for a state restoration app example.
///
/// See also:
///
/// * [showDateRangePicker], which is a way to display the date picker.
class DateRangePickerDialog extends StatefulWidget {
/// A Material-style date range picker dialog.
const DateRangePickerDialog({
Key? key, Key? key,
this.initialDateRange, this.initialDateRange,
required this.firstDate, required this.firstDate,
...@@ -1119,58 +1256,161 @@ class _DateRangePickerDialog extends StatefulWidget { ...@@ -1119,58 +1256,161 @@ class _DateRangePickerDialog extends StatefulWidget {
this.fieldEndHintText, this.fieldEndHintText,
this.fieldStartLabelText, this.fieldStartLabelText,
this.fieldEndLabelText, this.fieldEndLabelText,
this.restorationId,
}) : super(key: key); }) : super(key: key);
/// The date range that the date range picker starts with when it opens.
///
/// 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.
///
/// 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`.
final DateTimeRange? initialDateRange; final DateTimeRange? initialDateRange;
/// The earliest allowable date on the date range.
final DateTime firstDate; final DateTime firstDate;
/// The latest allowable date on the date range.
final DateTime lastDate; final DateTime lastDate;
/// 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.
final DateTime? currentDate; final DateTime? currentDate;
/// The initial date range picker entry mode.
///
/// The date range has two main modes: [DatePickerEntryMode.calendar] (a
/// scrollable calendar month grid) or [DatePickerEntryMode.input] (two text
/// input fields) mode.
///
/// It defaults to [DatePickerEntryMode.calendar] and must be non-null.
final DatePickerEntryMode initialEntryMode; final DatePickerEntryMode initialEntryMode;
/// The label on the cancel button for the text input mode.
///
/// If null, the localized value of
/// [MaterialLocalizations.cancelButtonLabel] is used.
final String? cancelText; final String? cancelText;
/// The label on the "OK" button for the text input mode.
///
/// If null, the localized value of
/// [MaterialLocalizations.okButtonLabel] is used.
final String? confirmText; final String? confirmText;
/// The label on the save button for the fullscreen calendar mode.
///
/// If null, the localized value of
/// [MaterialLocalizations.saveButtonLabel] is used.
final String? saveText; final String? saveText;
/// The label displayed at the top of the dialog.
///
/// If null, the localized value of
/// [MaterialLocalizations.dateRangePickerHelpText] is used.
final String? helpText; final String? helpText;
/// The message used when the date range is invalid (e.g. start date is after
/// end date).
///
/// If null, the localized value of
/// [MaterialLocalizations.invalidDateRangeLabel] is used.
final String? errorInvalidRangeText; final String? errorInvalidRangeText;
/// The message used when an input text isn't in a proper date format.
///
/// If null, the localized value of
/// [MaterialLocalizations.invalidDateFormatLabel] is used.
final String? errorFormatText; final String? errorFormatText;
/// The message used when an input text isn't a selectable date.
///
/// If null, the localized value of
/// [MaterialLocalizations.dateOutOfRangeLabel] is used.
final String? errorInvalidText; final String? errorInvalidText;
/// The text used to prompt the user when no text has been entered in the
/// start field.
///
/// If null, the localized value of
/// [MaterialLocalizations.dateHelpText] is used.
final String? fieldStartHintText; final String? fieldStartHintText;
/// The text used to prompt the user when no text has been entered in the
/// end field.
///
/// If null, the localized value of [MaterialLocalizations.dateHelpText] is
/// used.
final String? fieldEndHintText; final String? fieldEndHintText;
/// The label for the start date text input field.
///
/// If null, the localized value of [MaterialLocalizations.dateRangeStartLabel]
/// is used.
final String? fieldStartLabelText; final String? fieldStartLabelText;
/// The label for the end date text input field.
///
/// If null, the localized value of [MaterialLocalizations.dateRangeEndLabel]
/// is used.
final String? fieldEndLabelText; final String? fieldEndLabelText;
/// Restoration ID to save and restore the state of the [DateRangePickerDialog].
///
/// If it is non-null, the date range picker will persist and restore the
/// date range selected on the dialog.
///
/// The state of this widget is persisted in a [RestorationBucket] claimed
/// from the surrounding [RestorationScope] using the provided restoration ID.
///
/// See also:
///
/// * [RestorationManager], which explains how state restoration works in
/// Flutter.
final String? restorationId;
@override @override
_DateRangePickerDialogState createState() => _DateRangePickerDialogState(); _DateRangePickerDialogState createState() => _DateRangePickerDialogState();
} }
class _DateRangePickerDialogState extends State<_DateRangePickerDialog> { class _DateRangePickerDialogState extends State<DateRangePickerDialog> with RestorationMixin {
late DatePickerEntryMode _entryMode; late final _RestorableDatePickerEntryMode _entryMode = _RestorableDatePickerEntryMode(widget.initialEntryMode);
DateTime? _selectedStart; late final RestorableDateTimeN _selectedStart = RestorableDateTimeN(widget.initialDateRange?.start);
DateTime? _selectedEnd; late final RestorableDateTimeN _selectedEnd = RestorableDateTimeN(widget.initialDateRange?.end);
late bool _autoValidate; final RestorableBool _autoValidate = RestorableBool(false);
final GlobalKey _calendarPickerKey = GlobalKey(); final GlobalKey _calendarPickerKey = GlobalKey();
final GlobalKey<_InputDateRangePickerState> _inputPickerKey = GlobalKey<_InputDateRangePickerState>(); final GlobalKey<_InputDateRangePickerState> _inputPickerKey = GlobalKey<_InputDateRangePickerState>();
@override @override
void initState() { String? get restorationId => widget.restorationId;
super.initState();
_selectedStart = widget.initialDateRange?.start; @override
_selectedEnd = widget.initialDateRange?.end; void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
_entryMode = widget.initialEntryMode; registerForRestoration(_entryMode, 'entry_mode');
_autoValidate = false; registerForRestoration(_selectedStart, 'selected_start');
registerForRestoration(_selectedEnd, 'selected_end');
registerForRestoration(_autoValidate, 'autovalidate');
} }
void _handleOk() { void _handleOk() {
if (_entryMode == DatePickerEntryMode.input || _entryMode == DatePickerEntryMode.inputOnly) { if (_entryMode.value == DatePickerEntryMode.input || _entryMode.value == DatePickerEntryMode.inputOnly) {
final _InputDateRangePickerState picker = _inputPickerKey.currentState!; final _InputDateRangePickerState picker = _inputPickerKey.currentState!;
if (!picker.validate()) { if (!picker.validate()) {
setState(() { setState(() {
_autoValidate = true; _autoValidate.value = true;
}); });
return; return;
} }
} }
final DateTimeRange? selectedRange = _hasSelectedDateRange final DateTimeRange? selectedRange = _hasSelectedDateRange
? DateTimeRange(start: _selectedStart!, end: _selectedEnd!) ? DateTimeRange(start: _selectedStart.value!, end: _selectedEnd.value!)
: null; : null;
Navigator.pop(context, selectedRange); Navigator.pop(context, selectedRange);
...@@ -1182,29 +1422,29 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> { ...@@ -1182,29 +1422,29 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> {
void _handleEntryModeToggle() { void _handleEntryModeToggle() {
setState(() { setState(() {
switch (_entryMode) { switch (_entryMode.value) {
case DatePickerEntryMode.calendar: case DatePickerEntryMode.calendar:
_autoValidate = false; _autoValidate.value = false;
_entryMode = DatePickerEntryMode.input; _entryMode.value = DatePickerEntryMode.input;
break; break;
case DatePickerEntryMode.input: case DatePickerEntryMode.input:
// Validate the range dates // Validate the range dates
if (_selectedStart != null && if (_selectedStart.value != null &&
(_selectedStart!.isBefore(widget.firstDate) || _selectedStart!.isAfter(widget.lastDate))) { (_selectedStart.value!.isBefore(widget.firstDate) || _selectedStart.value!.isAfter(widget.lastDate))) {
_selectedStart = null; _selectedStart.value = null;
// With no valid start date, having an end date makes no sense for the UI. // With no valid start date, having an end date makes no sense for the UI.
_selectedEnd = null; _selectedEnd.value = null;
} }
if (_selectedEnd != null && if (_selectedEnd.value != null &&
(_selectedEnd!.isBefore(widget.firstDate) || _selectedEnd!.isAfter(widget.lastDate))) { (_selectedEnd.value!.isBefore(widget.firstDate) || _selectedEnd.value!.isAfter(widget.lastDate))) {
_selectedEnd = null; _selectedEnd.value = null;
} }
// If invalid range (start after end), then just use the start date // If invalid range (start after end), then just use the start date
if (_selectedStart != null && _selectedEnd != null && _selectedStart!.isAfter(_selectedEnd!)) { if (_selectedStart.value != null && _selectedEnd.value != null && _selectedStart.value!.isAfter(_selectedEnd.value!)) {
_selectedEnd = null; _selectedEnd.value = null;
} }
_entryMode = DatePickerEntryMode.calendar; _entryMode.value = DatePickerEntryMode.calendar;
break; break;
case DatePickerEntryMode.calendarOnly: case DatePickerEntryMode.calendarOnly:
...@@ -1216,14 +1456,14 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> { ...@@ -1216,14 +1456,14 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> {
} }
void _handleStartDateChanged(DateTime? date) { void _handleStartDateChanged(DateTime? date) {
setState(() => _selectedStart = date); setState(() => _selectedStart.value = date);
} }
void _handleEndDateChanged(DateTime? date) { void _handleEndDateChanged(DateTime? date) {
setState(() => _selectedEnd = date); setState(() => _selectedEnd.value = date);
} }
bool get _hasSelectedDateRange => _selectedStart != null && _selectedEnd != null; bool get _hasSelectedDateRange => _selectedStart.value != null && _selectedEnd.value != null;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -1242,15 +1482,15 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> { ...@@ -1242,15 +1482,15 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> {
final double elevation; final double elevation;
final EdgeInsets insetPadding; final EdgeInsets insetPadding;
final bool showEntryModeButton = final bool showEntryModeButton =
_entryMode == DatePickerEntryMode.calendar || _entryMode.value == DatePickerEntryMode.calendar ||
_entryMode == DatePickerEntryMode.input; _entryMode.value == DatePickerEntryMode.input;
switch (_entryMode) { switch (_entryMode.value) {
case DatePickerEntryMode.calendar: case DatePickerEntryMode.calendar:
case DatePickerEntryMode.calendarOnly: case DatePickerEntryMode.calendarOnly:
contents = _CalendarRangePickerDialog( contents = _CalendarRangePickerDialog(
key: _calendarPickerKey, key: _calendarPickerKey,
selectedStartDate: _selectedStart, selectedStartDate: _selectedStart.value,
selectedEndDate: _selectedEnd, selectedEndDate: _selectedEnd.value,
firstDate: widget.firstDate, firstDate: widget.firstDate,
lastDate: widget.lastDate, lastDate: widget.lastDate,
currentDate: widget.currentDate, currentDate: widget.currentDate,
...@@ -1279,8 +1519,8 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> { ...@@ -1279,8 +1519,8 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> {
case DatePickerEntryMode.input: case DatePickerEntryMode.input:
case DatePickerEntryMode.inputOnly: case DatePickerEntryMode.inputOnly:
contents = _InputDateRangePickerDialog( contents = _InputDateRangePickerDialog(
selectedStartDate: _selectedStart, selectedStartDate: _selectedStart.value,
selectedEndDate: _selectedEnd, selectedEndDate: _selectedEnd.value,
currentDate: widget.currentDate, currentDate: widget.currentDate,
picker: Container( picker: Container(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 24),
...@@ -1292,14 +1532,14 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> { ...@@ -1292,14 +1532,14 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> {
const Spacer(), const Spacer(),
_InputDateRangePicker( _InputDateRangePicker(
key: _inputPickerKey, key: _inputPickerKey,
initialStartDate: _selectedStart, initialStartDate: _selectedStart.value,
initialEndDate: _selectedEnd, initialEndDate: _selectedEnd.value,
firstDate: widget.firstDate, firstDate: widget.firstDate,
lastDate: widget.lastDate, lastDate: widget.lastDate,
onStartDateChanged: _handleStartDateChanged, onStartDateChanged: _handleStartDateChanged,
onEndDateChanged: _handleEndDateChanged, onEndDateChanged: _handleEndDateChanged,
autofocus: true, autofocus: true,
autovalidate: _autoValidate, autovalidate: _autoValidate.value,
helpText: widget.helpText, helpText: widget.helpText,
errorInvalidRangeText: widget.errorInvalidRangeText, errorInvalidRangeText: widget.errorInvalidRangeText,
errorFormatText: widget.errorFormatText, errorFormatText: widget.errorFormatText,
......
...@@ -385,6 +385,34 @@ class RestorableDateTime extends RestorableValue<DateTime> { ...@@ -385,6 +385,34 @@ class RestorableDateTime extends RestorableValue<DateTime> {
Object? toPrimitives() => value.millisecondsSinceEpoch; Object? toPrimitives() => value.millisecondsSinceEpoch;
} }
/// A [RestorableValue] that knows how to save and restore [DateTime] that is
/// nullable.
///
/// {@macro flutter.widgets.RestorableNum}.
class RestorableDateTimeN extends RestorableValue<DateTime?> {
/// Creates a [RestorableDateTime].
///
/// {@macro flutter.widgets.RestorableNum.constructor}
RestorableDateTimeN(DateTime? defaultValue) : _defaultValue = defaultValue;
final DateTime? _defaultValue;
@override
DateTime? createDefaultValue() => _defaultValue;
@override
void didUpdateValue(DateTime? oldValue) {
assert(debugIsSerializableForRestoration(value?.millisecondsSinceEpoch));
notifyListeners();
}
@override
DateTime? fromPrimitives(Object? data) => data != null ? DateTime.fromMillisecondsSinceEpoch(data as int) : null;
@override
Object? toPrimitives() => value?.millisecondsSinceEpoch;
}
/// A base class for creating a [RestorableProperty] that stores and restores a /// A base class for creating a [RestorableProperty] that stores and restores a
/// [Listenable]. /// [Listenable].
/// ///
......
...@@ -1162,9 +1162,14 @@ void main() { ...@@ -1162,9 +1162,14 @@ void main() {
await tester.restoreFrom(restorationData); await tester.restoreFrom(restorationData);
expect(find.byType(DatePickerDialog), findsOneWidget); expect(find.byType(DatePickerDialog), findsOneWidget);
// Select a different date and close the date picker. // Select a different date.
await tester.tap(find.text('30')); await tester.tap(find.text('30'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Restart after the new selection. It should remain selected.
await tester.restartAndRestore();
// Close the date picker.
await tester.tap(find.text('OK')); await tester.tap(find.text('OK'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -1210,8 +1215,8 @@ void main() { ...@@ -1210,8 +1215,8 @@ void main() {
await tester.tapAt(const Offset(10.0, 10.0)); await tester.tapAt(const Offset(10.0, 10.0));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// The date picker should be closed, the text value updated to the // The date picker should be closed, the text value should be the same
// newly selected date. // as before.
expect(find.byType(DatePickerDialog), findsNothing); expect(find.byType(DatePickerDialog), findsNothing);
expect(find.text('25/7/2021'), findsOneWidget); expect(find.text('25/7/2021'), findsOneWidget);
......
...@@ -853,4 +853,198 @@ void main() { ...@@ -853,4 +853,198 @@ void main() {
_testInputDecorator(tester.widget(borderContainers.last), border, Colors.transparent); _testInputDecorator(tester.widget(borderContainers.last), border, Colors.transparent);
}); });
}); });
testWidgets('DatePickerDialog is state restorable', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
restorationScopeId: 'app',
home: _RestorableDateRangePickerDialogTestWidget(),
),
);
// The date range picker should be closed.
expect(find.byType(DateRangePickerDialog), findsNothing);
expect(find.text('1/1/2021 to 5/1/2021'), findsOneWidget);
// Open the date range picker.
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
expect(find.byType(DateRangePickerDialog), findsOneWidget);
final TestRestorationData restorationData = await tester.getRestorationData();
await tester.restartAndRestore();
// The date range picker should be open after restoring.
expect(find.byType(DateRangePickerDialog), findsOneWidget);
// Close the date range picker.
await tester.tap(find.byIcon(Icons.close));
await tester.pumpAndSettle();
// The date range picker should be closed, the text value updated to the
// newly selected date.
expect(find.byType(DateRangePickerDialog), findsNothing);
expect(find.text('1/1/2021 to 5/1/2021'), findsOneWidget);
// The date range picker should be open after restoring.
await tester.restoreFrom(restorationData);
expect(find.byType(DateRangePickerDialog), findsOneWidget);
// // Select a different date and close the date range picker.
await tester.tap(find.text('12').first);
await tester.pumpAndSettle();
await tester.tap(find.text('14').first);
await tester.pumpAndSettle();
// Restart after the new selection. It should remain selected.
await tester.restartAndRestore();
// Close the date range picker.
await tester.tap(find.text('SAVE'));
await tester.pumpAndSettle();
// The date range picker should be closed, the text value updated to the
// newly selected date.
expect(find.byType(DateRangePickerDialog), findsNothing);
expect(find.text('12/1/2021 to 14/1/2021'), findsOneWidget);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
testWidgets('DateRangePickerDialog state restoration - DatePickerEntryMode', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
restorationScopeId: 'app',
home: _RestorableDateRangePickerDialogTestWidget(
datePickerEntryMode: DatePickerEntryMode.calendarOnly,
),
),
);
// The date range picker should be closed.
expect(find.byType(DateRangePickerDialog), findsNothing);
expect(find.text('1/1/2021 to 5/1/2021'), findsOneWidget);
// Open the date range picker.
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
expect(find.byType(DateRangePickerDialog), findsOneWidget);
// Only in calendar mode and cannot switch out.
expect(find.byType(TextField), findsNothing);
expect(find.byIcon(Icons.edit), findsNothing);
final TestRestorationData restorationData = await tester.getRestorationData();
await tester.restartAndRestore();
// The date range picker should be open after restoring.
expect(find.byType(DateRangePickerDialog), findsOneWidget);
// Only in calendar mode and cannot switch out.
expect(find.byType(TextField), findsNothing);
expect(find.byIcon(Icons.edit), findsNothing);
// Tap on the barrier.
await tester.tap(find.byIcon(Icons.close));
await tester.pumpAndSettle();
// The date range picker should be closed, the text value should be the same
// as before.
expect(find.byType(DateRangePickerDialog), findsNothing);
expect(find.text('1/1/2021 to 5/1/2021'), findsOneWidget);
// The date range picker should be open after restoring.
await tester.restoreFrom(restorationData);
expect(find.byType(DateRangePickerDialog), findsOneWidget);
// Only in calendar mode and cannot switch out.
expect(find.byType(TextField), findsNothing);
expect(find.byIcon(Icons.edit), findsNothing);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
}
class _RestorableDateRangePickerDialogTestWidget extends StatefulWidget {
const _RestorableDateRangePickerDialogTestWidget({
Key? key,
this.datePickerEntryMode = DatePickerEntryMode.calendar,
}) : super(key: key);
final DatePickerEntryMode datePickerEntryMode;
@override
_RestorableDateRangePickerDialogTestWidgetState createState() => _RestorableDateRangePickerDialogTestWidgetState();
}
class _RestorableDateRangePickerDialogTestWidgetState extends State<_RestorableDateRangePickerDialogTestWidget> with RestorationMixin {
@override
String? get restorationId => 'scaffold_state';
final RestorableDateTimeN _startDate = RestorableDateTimeN(DateTime(2021, 1, 1));
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>{
'datePickerEntryMode': widget.datePickerEntryMode.index,
}
);
},
);
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
registerForRestoration(_startDate, 'start_date');
registerForRestoration(_endDate, 'end_date');
registerForRestoration(_restorableDateRangePickerRouteFuture, 'date_picker_route_future');
}
void _selectDateRange(DateTimeRange? newSelectedDate) {
if (newSelectedDate != null) {
setState(() {
_startDate.value = newSelectedDate.start;
_endDate.value = newSelectedDate.end;
});
}
}
static Route<DateTimeRange?> _dateRangePickerRoute(
BuildContext context,
Object? arguments,
) {
return DialogRoute<DateTimeRange?>(
context: context,
builder: (BuildContext context) {
final Map<dynamic, dynamic> args = arguments! as Map<dynamic, dynamic>;
return DateRangePickerDialog(
restorationId: 'date_picker_dialog',
initialEntryMode: DatePickerEntryMode.values[args['datePickerEntryMode'] as int],
firstDate: DateTime(2021, 1, 1),
currentDate: DateTime(2021, 1, 25),
lastDate: DateTime(2022, 1, 1),
);
},
);
}
@override
Widget build(BuildContext context) {
final DateTime? startDateTime = _startDate.value;
final DateTime? endDateTime = _endDate.value;
// Example: "25/7/1994"
final String startDateTimeString = '${startDateTime?.day}/${startDateTime?.month}/${startDateTime?.year}';
final String endDateTimeString = '${endDateTime?.day}/${endDateTime?.month}/${endDateTime?.year}';
return Scaffold(
body: Center(
child: Column(
children: <Widget>[
OutlinedButton(
onPressed: () {
_restorableDateRangePickerRouteFuture.present();
},
child: const Text('X'),
),
Text('$startDateTimeString to $endDateTimeString'),
],
),
),
);
}
} }
...@@ -19,6 +19,7 @@ void main() { ...@@ -19,6 +19,7 @@ void main() {
expect(() => RestorableBoolN(true).value, throwsAssertionError); expect(() => RestorableBoolN(true).value, throwsAssertionError);
expect(() => RestorableTextEditingController().value, throwsAssertionError); expect(() => RestorableTextEditingController().value, throwsAssertionError);
expect(() => RestorableDateTime(DateTime(2020, 4, 3)).value, throwsAssertionError); expect(() => RestorableDateTime(DateTime(2020, 4, 3)).value, throwsAssertionError);
expect(() => RestorableDateTimeN(DateTime(2020, 4, 3)).value, throwsAssertionError);
expect(() => _TestRestorableValue().value, throwsAssertionError); expect(() => _TestRestorableValue().value, throwsAssertionError);
}); });
...@@ -34,8 +35,14 @@ void main() { ...@@ -34,8 +35,14 @@ void main() {
expect(state.intValue.value, 42); expect(state.intValue.value, 42);
expect(state.stringValue.value, 'hello world'); expect(state.stringValue.value, 'hello world');
expect(state.boolValue.value, false); expect(state.boolValue.value, false);
expect(state.controllerValue.value.text, 'FooBar');
expect(state.dateTimeValue.value, DateTime(2021, 3, 16)); expect(state.dateTimeValue.value, DateTime(2021, 3, 16));
expect(state.nullableNumValue.value, null);
expect(state.nullableDoubleValue.value, null);
expect(state.nullableIntValue.value, null);
expect(state.nullableStringValue.value, null);
expect(state.nullableBoolValue.value, null);
expect(state.nullableDateTimeValue.value, null);
expect(state.controllerValue.value.text, 'FooBar');
expect(state.objectValue.value, 55); expect(state.objectValue.value, 55);
// Modify values. // Modify values.
...@@ -45,8 +52,14 @@ void main() { ...@@ -45,8 +52,14 @@ void main() {
state.intValue.value = 10; state.intValue.value = 10;
state.stringValue.value = 'guten tag'; state.stringValue.value = 'guten tag';
state.boolValue.value = true; state.boolValue.value = true;
state.controllerValue.value.text = 'blabla';
state.dateTimeValue.value = DateTime(2020, 7, 4); state.dateTimeValue.value = DateTime(2020, 7, 4);
state.nullableNumValue.value = 5.0;
state.nullableDoubleValue.value = 2.0;
state.nullableIntValue.value = 1;
state.nullableStringValue.value = 'hullo';
state.nullableBoolValue.value = false;
state.nullableDateTimeValue.value = DateTime(2020, 4, 4);
state.controllerValue.value.text = 'blabla';
state.objectValue.value = 53; state.objectValue.value = 53;
}); });
await tester.pump(); await tester.pump();
...@@ -56,8 +69,14 @@ void main() { ...@@ -56,8 +69,14 @@ void main() {
expect(state.intValue.value, 10); expect(state.intValue.value, 10);
expect(state.stringValue.value, 'guten tag'); expect(state.stringValue.value, 'guten tag');
expect(state.boolValue.value, true); expect(state.boolValue.value, true);
expect(state.controllerValue.value.text, 'blabla');
expect(state.dateTimeValue.value, DateTime(2020, 7, 4)); expect(state.dateTimeValue.value, DateTime(2020, 7, 4));
expect(state.nullableNumValue.value, 5.0);
expect(state.nullableDoubleValue.value, 2.0);
expect(state.nullableIntValue.value, 1);
expect(state.nullableStringValue.value, 'hullo');
expect(state.nullableBoolValue.value, false);
expect(state.nullableDateTimeValue.value, DateTime(2020, 4, 4));
expect(state.controllerValue.value.text, 'blabla');
expect(state.objectValue.value, 53); expect(state.objectValue.value, 53);
expect(find.text('guten tag'), findsOneWidget); expect(find.text('guten tag'), findsOneWidget);
}); });
...@@ -77,8 +96,14 @@ void main() { ...@@ -77,8 +96,14 @@ void main() {
expect(state.intValue.value, 42); expect(state.intValue.value, 42);
expect(state.stringValue.value, 'hello world'); expect(state.stringValue.value, 'hello world');
expect(state.boolValue.value, false); expect(state.boolValue.value, false);
expect(state.controllerValue.value.text, 'FooBar');
expect(state.dateTimeValue.value, DateTime(2021, 3, 16)); expect(state.dateTimeValue.value, DateTime(2021, 3, 16));
expect(state.nullableNumValue.value, null);
expect(state.nullableDoubleValue.value, null);
expect(state.nullableIntValue.value, null);
expect(state.nullableStringValue.value, null);
expect(state.nullableBoolValue.value, null);
expect(state.nullableDateTimeValue.value, null);
expect(state.controllerValue.value.text, 'FooBar');
expect(state.objectValue.value, 55); expect(state.objectValue.value, 55);
// Modify values. // Modify values.
...@@ -88,8 +113,14 @@ void main() { ...@@ -88,8 +113,14 @@ void main() {
state.intValue.value = 10; state.intValue.value = 10;
state.stringValue.value = 'guten tag'; state.stringValue.value = 'guten tag';
state.boolValue.value = true; state.boolValue.value = true;
state.controllerValue.value.text = 'blabla';
state.dateTimeValue.value = DateTime(2020, 7, 4); state.dateTimeValue.value = DateTime(2020, 7, 4);
state.nullableNumValue.value = 5.0;
state.nullableDoubleValue.value = 2.0;
state.nullableIntValue.value = 1;
state.nullableStringValue.value = 'hullo';
state.nullableBoolValue.value = false;
state.nullableDateTimeValue.value = DateTime(2020, 4, 4);
state.controllerValue.value.text = 'blabla';
state.objectValue.value = 53; state.objectValue.value = 53;
}); });
await tester.pump(); await tester.pump();
...@@ -99,8 +130,14 @@ void main() { ...@@ -99,8 +130,14 @@ void main() {
expect(state.intValue.value, 10); expect(state.intValue.value, 10);
expect(state.stringValue.value, 'guten tag'); expect(state.stringValue.value, 'guten tag');
expect(state.boolValue.value, true); expect(state.boolValue.value, true);
expect(state.controllerValue.value.text, 'blabla');
expect(state.dateTimeValue.value, DateTime(2020, 7, 4)); expect(state.dateTimeValue.value, DateTime(2020, 7, 4));
expect(state.nullableNumValue.value, 5.0);
expect(state.nullableDoubleValue.value, 2.0);
expect(state.nullableIntValue.value, 1);
expect(state.nullableStringValue.value, 'hullo');
expect(state.nullableBoolValue.value, false);
expect(state.nullableDateTimeValue.value, DateTime(2020, 4, 4));
expect(state.controllerValue.value.text, 'blabla');
expect(state.objectValue.value, 53); expect(state.objectValue.value, 53);
expect(find.text('guten tag'), findsOneWidget); expect(find.text('guten tag'), findsOneWidget);
...@@ -115,8 +152,14 @@ void main() { ...@@ -115,8 +152,14 @@ void main() {
expect(state.intValue.value, 10); expect(state.intValue.value, 10);
expect(state.stringValue.value, 'guten tag'); expect(state.stringValue.value, 'guten tag');
expect(state.boolValue.value, true); expect(state.boolValue.value, true);
expect(state.controllerValue.value.text, 'blabla');
expect(state.dateTimeValue.value, DateTime(2020, 7, 4)); expect(state.dateTimeValue.value, DateTime(2020, 7, 4));
expect(state.nullableNumValue.value, 5.0);
expect(state.nullableDoubleValue.value, 2.0);
expect(state.nullableIntValue.value, 1);
expect(state.nullableStringValue.value, 'hullo');
expect(state.nullableBoolValue.value, false);
expect(state.nullableDateTimeValue.value, DateTime(2020, 4, 4));
expect(state.controllerValue.value.text, 'blabla');
expect(state.objectValue.value, 53); expect(state.objectValue.value, 53);
expect(find.text('guten tag'), findsOneWidget); expect(find.text('guten tag'), findsOneWidget);
}); });
...@@ -137,13 +180,14 @@ void main() { ...@@ -137,13 +180,14 @@ void main() {
state.intValue.value = 10; state.intValue.value = 10;
state.stringValue.value = 'guten tag'; state.stringValue.value = 'guten tag';
state.boolValue.value = true; state.boolValue.value = true;
state.dateTimeValue.value = DateTime(2020, 7, 4);
state.nullableNumValue.value = 5.0; state.nullableNumValue.value = 5.0;
state.nullableDoubleValue.value = 2.0; state.nullableDoubleValue.value = 2.0;
state.nullableIntValue.value = 1; state.nullableIntValue.value = 1;
state.nullableStringValue.value = 'hullo'; state.nullableStringValue.value = 'hullo';
state.nullableBoolValue.value = false; state.nullableBoolValue.value = false;
state.nullableDateTimeValue.value = DateTime(2020, 4, 4);
state.controllerValue.value.text = 'blabla'; state.controllerValue.value.text = 'blabla';
state.dateTimeValue.value = DateTime(2020, 7, 4);
state.objectValue.value = 53; state.objectValue.value = 53;
}); });
await tester.pump(); await tester.pump();
...@@ -158,13 +202,14 @@ void main() { ...@@ -158,13 +202,14 @@ void main() {
state.intValue.value = 20; state.intValue.value = 20;
state.stringValue.value = 'ciao'; state.stringValue.value = 'ciao';
state.boolValue.value = false; state.boolValue.value = false;
state.dateTimeValue.value = DateTime(2020, 3, 2);
state.nullableNumValue.value = 20.0; state.nullableNumValue.value = 20.0;
state.nullableDoubleValue.value = 20.0; state.nullableDoubleValue.value = 20.0;
state.nullableIntValue.value = 20; state.nullableIntValue.value = 20;
state.nullableStringValue.value = 'ni hao'; state.nullableStringValue.value = 'ni hao';
state.nullableBoolValue.value = null; state.nullableBoolValue.value = null;
state.nullableDateTimeValue.value = DateTime(2020, 5, 5);
state.controllerValue.value.text = 'blub'; state.controllerValue.value.text = 'blub';
state.dateTimeValue.value = DateTime(2020, 3, 2);
state.objectValue.value = 20; state.objectValue.value = 20;
}); });
await tester.pump(); await tester.pump();
...@@ -178,13 +223,14 @@ void main() { ...@@ -178,13 +223,14 @@ void main() {
expect(state.intValue.value, 10); expect(state.intValue.value, 10);
expect(state.stringValue.value, 'guten tag'); expect(state.stringValue.value, 'guten tag');
expect(state.boolValue.value, true); expect(state.boolValue.value, true);
expect(state.dateTimeValue.value, DateTime(2020, 7, 4));
expect(state.nullableNumValue.value, 5.0); expect(state.nullableNumValue.value, 5.0);
expect(state.nullableDoubleValue.value, 2.0); expect(state.nullableDoubleValue.value, 2.0);
expect(state.nullableIntValue.value, 1); expect(state.nullableIntValue.value, 1);
expect(state.nullableStringValue.value, 'hullo'); expect(state.nullableStringValue.value, 'hullo');
expect(state.nullableBoolValue.value, false); expect(state.nullableBoolValue.value, false);
expect(state.nullableDateTimeValue.value, DateTime(2020, 4, 4));
expect(state.controllerValue.value.text, 'blabla'); expect(state.controllerValue.value.text, 'blabla');
expect(state.dateTimeValue.value, DateTime(2020, 7, 4));
expect(state.objectValue.value, 53); expect(state.objectValue.value, 53);
expect(find.text('guten tag'), findsOneWidget); expect(find.text('guten tag'), findsOneWidget);
expect(state.controllerValue.value, isNot(same(controller))); expect(state.controllerValue.value, isNot(same(controller)));
...@@ -196,13 +242,14 @@ void main() { ...@@ -196,13 +242,14 @@ void main() {
expect(state.intValue.value, 42); expect(state.intValue.value, 42);
expect(state.stringValue.value, 'hello world'); expect(state.stringValue.value, 'hello world');
expect(state.boolValue.value, false); expect(state.boolValue.value, false);
expect(state.dateTimeValue.value, DateTime(2021, 3, 16));
expect(state.nullableNumValue.value, null); expect(state.nullableNumValue.value, null);
expect(state.nullableDoubleValue.value, null); expect(state.nullableDoubleValue.value, null);
expect(state.nullableIntValue.value, null); expect(state.nullableIntValue.value, null);
expect(state.nullableStringValue.value, null); expect(state.nullableStringValue.value, null);
expect(state.nullableBoolValue.value, null); expect(state.nullableBoolValue.value, null);
expect(state.nullableDateTimeValue.value, null);
expect(state.controllerValue.value.text, 'FooBar'); expect(state.controllerValue.value.text, 'FooBar');
expect(state.dateTimeValue.value, DateTime(2021, 3, 16));
expect(state.objectValue.value, 55); expect(state.objectValue.value, 55);
expect(find.text('hello world'), findsOneWidget); expect(find.text('hello world'), findsOneWidget);
}); });
...@@ -217,6 +264,7 @@ void main() { ...@@ -217,6 +264,7 @@ void main() {
final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget)); final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget));
final List<String> notifyLog = <String>[]; final List<String> notifyLog = <String>[];
state.numValue.addListener(() { state.numValue.addListener(() {
notifyLog.add('num'); notifyLog.add('num');
}); });
...@@ -232,6 +280,27 @@ void main() { ...@@ -232,6 +280,27 @@ void main() {
state.boolValue.addListener(() { state.boolValue.addListener(() {
notifyLog.add('bool'); notifyLog.add('bool');
}); });
state.dateTimeValue.addListener(() {
notifyLog.add('date-time');
});
state.nullableNumValue.addListener(() {
notifyLog.add('nullable-num');
});
state.nullableDoubleValue.addListener(() {
notifyLog.add('nullable-double');
});
state.nullableIntValue.addListener(() {
notifyLog.add('nullable-int');
});
state.nullableStringValue.addListener(() {
notifyLog.add('nullable-string');
});
state.nullableBoolValue.addListener(() {
notifyLog.add('nullable-bool');
});
state.nullableDateTimeValue.addListener(() {
notifyLog.add('nullable-date-time');
});
state.controllerValue.addListener(() { state.controllerValue.addListener(() {
notifyLog.add('controller'); notifyLog.add('controller');
}); });
...@@ -269,6 +338,48 @@ void main() { ...@@ -269,6 +338,48 @@ void main() {
expect(notifyLog.single, 'bool'); expect(notifyLog.single, 'bool');
notifyLog.clear(); notifyLog.clear();
state.setProperties(() {
state.dateTimeValue.value = DateTime(2020, 7, 4);
});
expect(notifyLog.single, 'date-time');
notifyLog.clear();
state.setProperties(() {
state.nullableNumValue.value = 42.2;
});
expect(notifyLog.single, 'nullable-num');
notifyLog.clear();
state.setProperties(() {
state.nullableDoubleValue.value = 42.2;
});
expect(notifyLog.single, 'nullable-double');
notifyLog.clear();
state.setProperties(() {
state.nullableIntValue.value = 45;
});
expect(notifyLog.single, 'nullable-int');
notifyLog.clear();
state.setProperties(() {
state.nullableStringValue.value = 'bar';
});
expect(notifyLog.single, 'nullable-string');
notifyLog.clear();
state.setProperties(() {
state.nullableBoolValue.value = true;
});
expect(notifyLog.single, 'nullable-bool');
notifyLog.clear();
state.setProperties(() {
state.nullableDateTimeValue.value = DateTime(2020, 4, 4);
});
expect(notifyLog.single, 'nullable-date-time');
notifyLog.clear();
state.setProperties(() { state.setProperties(() {
state.controllerValue.value.text = 'foo'; state.controllerValue.value.text = 'foo';
}); });
...@@ -291,8 +402,17 @@ void main() { ...@@ -291,8 +402,17 @@ void main() {
state.intValue.value = 45; state.intValue.value = 45;
state.stringValue.value = 'bar'; state.stringValue.value = 'bar';
state.boolValue.value = true; state.boolValue.value = true;
state.dateTimeValue.value = DateTime(2020, 7, 4);
state.nullableNumValue.value = 42.2;
state.nullableDoubleValue.value = 42.2;
state.nullableIntValue.value = 45;
state.nullableStringValue.value = 'bar';
state.nullableBoolValue.value = true;
state.nullableDateTimeValue.value = DateTime(2020, 4, 4);
state.controllerValue.value.text = 'foo'; state.controllerValue.value.text = 'foo';
state.objectValue.value = 42;
}); });
expect(notifyLog, isEmpty); expect(notifyLog, isEmpty);
}); });
...@@ -396,13 +516,14 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi ...@@ -396,13 +516,14 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi
final RestorableInt intValue = RestorableInt(42); final RestorableInt intValue = RestorableInt(42);
final RestorableString stringValue = RestorableString('hello world'); final RestorableString stringValue = RestorableString('hello world');
final RestorableBool boolValue = RestorableBool(false); final RestorableBool boolValue = RestorableBool(false);
final RestorableDateTime dateTimeValue = RestorableDateTime(DateTime(2021, 3, 16));
final RestorableNumN<num?> nullableNumValue = RestorableNumN<num?>(null); final RestorableNumN<num?> nullableNumValue = RestorableNumN<num?>(null);
final RestorableDoubleN nullableDoubleValue = RestorableDoubleN(null); final RestorableDoubleN nullableDoubleValue = RestorableDoubleN(null);
final RestorableIntN nullableIntValue = RestorableIntN(null); final RestorableIntN nullableIntValue = RestorableIntN(null);
final RestorableStringN nullableStringValue = RestorableStringN(null); final RestorableStringN nullableStringValue = RestorableStringN(null);
final RestorableBoolN nullableBoolValue = RestorableBoolN(null); final RestorableBoolN nullableBoolValue = RestorableBoolN(null);
final RestorableDateTimeN nullableDateTimeValue = RestorableDateTimeN(null);
final RestorableTextEditingController controllerValue = RestorableTextEditingController(text: 'FooBar'); final RestorableTextEditingController controllerValue = RestorableTextEditingController(text: 'FooBar');
final RestorableDateTime dateTimeValue = RestorableDateTime(DateTime(2021, 3, 16));
final _TestRestorableValue objectValue = _TestRestorableValue(); final _TestRestorableValue objectValue = _TestRestorableValue();
@override @override
...@@ -412,13 +533,14 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi ...@@ -412,13 +533,14 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi
registerForRestoration(intValue, 'int'); registerForRestoration(intValue, 'int');
registerForRestoration(stringValue, 'string'); registerForRestoration(stringValue, 'string');
registerForRestoration(boolValue, 'bool'); registerForRestoration(boolValue, 'bool');
registerForRestoration(dateTimeValue, 'dateTime');
registerForRestoration(nullableNumValue, 'nullableNum'); registerForRestoration(nullableNumValue, 'nullableNum');
registerForRestoration(nullableDoubleValue, 'nullableDouble'); registerForRestoration(nullableDoubleValue, 'nullableDouble');
registerForRestoration(nullableIntValue, 'nullableInt'); registerForRestoration(nullableIntValue, 'nullableInt');
registerForRestoration(nullableStringValue, 'nullableString'); registerForRestoration(nullableStringValue, 'nullableString');
registerForRestoration(nullableBoolValue, 'nullableBool'); registerForRestoration(nullableBoolValue, 'nullableBool');
registerForRestoration(nullableDateTimeValue, 'nullableDateTime');
registerForRestoration(controllerValue, 'controller'); registerForRestoration(controllerValue, 'controller');
registerForRestoration(dateTimeValue, 'dateTime');
registerForRestoration(objectValue, 'object'); registerForRestoration(objectValue, 'object');
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment