Skip to content

Commit 87a433b

Browse files
committed
Fix set-state-error on TimeInputWidget
The widget is read-only, so we don't need to use a controller, it is simply rebuilt when the user selects a new time
1 parent a7def64 commit 87a433b

2 files changed

Lines changed: 54 additions & 22 deletions

File tree

lib/widgets/core/datetime_input.dart

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ class TimeInputWidget extends StatefulWidget {
5858
}
5959

6060
class _TimeInputWidgetState extends State<TimeInputWidget> {
61-
final _controller = TextEditingController();
6261
TimeOfDay? _value;
6362

6463
@override
@@ -75,20 +74,14 @@ class _TimeInputWidgetState extends State<TimeInputWidget> {
7574
}
7675
}
7776

78-
@override
79-
void dispose() {
80-
_controller.dispose();
81-
super.dispose();
82-
}
83-
8477
@override
8578
Widget build(BuildContext context) {
86-
// The field is read-only, so rewriting the display text every build is safe.
87-
_controller.text = _value?.format(context) ?? '';
88-
79+
// Keyed initialValue, not a controller: a controller notifies the enclosing
80+
// Form on assignment, which crashes if that happens during a build.
8981
return TextFormField(
82+
key: ValueKey(_value),
9083
readOnly: true,
91-
controller: _controller,
84+
initialValue: _value?.format(context) ?? '',
9285
validator: widget.validator,
9386
decoration: InputDecoration(
9487
labelText: widget.labelText,
@@ -170,7 +163,6 @@ class DateInputWidget extends StatefulWidget {
170163
}
171164

172165
class _DateInputWidgetState extends State<DateInputWidget> {
173-
final _controller = TextEditingController();
174166
DateTime? _value;
175167

176168
@override
@@ -187,21 +179,15 @@ class _DateInputWidgetState extends State<DateInputWidget> {
187179
}
188180
}
189181

190-
@override
191-
void dispose() {
192-
_controller.dispose();
193-
super.dispose();
194-
}
195-
196182
@override
197183
Widget build(BuildContext context) {
198184
final dateFormat = DateFormat.yMd(Localizations.localeOf(context).languageCode);
199-
// The field is read-only, so rewriting the display text every build is safe.
200-
_controller.text = _value != null ? dateFormat.format(_value!) : '';
201-
185+
// Keyed initialValue, not a controller: a controller notifies the enclosing
186+
// Form on assignment, which crashes if that happens during a build.
202187
return TextFormField(
188+
key: ValueKey(_value),
203189
readOnly: true,
204-
controller: _controller,
190+
initialValue: _value != null ? dateFormat.format(_value!) : '',
205191
validator: widget.validator,
206192
decoration: InputDecoration(
207193
labelText: widget.labelText,

test/core/datetime_input_test.dart

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,29 @@ void main() {
9595
expect(cleared, isTrue);
9696
expect(find.text('3:30 PM'), findsNothing);
9797
});
98+
99+
testWidgets('does not rebuild the enclosing Form during build when the value '
100+
'changes from the parent (regression: issue #2401)', (WidgetTester tester) async {
101+
final notifier = ValueNotifier<TimeOfDay?>(null);
102+
await tester.pumpWidget(
103+
wrap(
104+
Form(
105+
child: ValueListenableBuilder<TimeOfDay?>(
106+
valueListenable: notifier,
107+
builder: (context, value, _) =>
108+
TimeInputWidget(value: value, labelText: 'Time', onChanged: (_) {}),
109+
),
110+
),
111+
),
112+
);
113+
await tester.pumpAndSettle();
114+
115+
notifier.value = const TimeOfDay(hour: 15, minute: 30);
116+
await tester.pumpAndSettle();
117+
118+
expect(tester.takeException(), isNull);
119+
expect(find.text('3:30 PM'), findsOneWidget);
120+
});
98121
});
99122

100123
group('DateInputWidget', () {
@@ -130,5 +153,28 @@ void main() {
130153

131154
expect(find.text('5.1.2021'), findsOneWidget);
132155
});
156+
157+
testWidgets('does not rebuild the enclosing Form during build when the value '
158+
'changes from the parent (regression: issue #2401)', (WidgetTester tester) async {
159+
final notifier = ValueNotifier<DateTime?>(null);
160+
await tester.pumpWidget(
161+
wrap(
162+
Form(
163+
child: ValueListenableBuilder<DateTime?>(
164+
valueListenable: notifier,
165+
builder: (context, value, _) =>
166+
DateInputWidget(value: value, labelText: 'Date', onChanged: (_) {}),
167+
),
168+
),
169+
),
170+
);
171+
await tester.pumpAndSettle();
172+
173+
notifier.value = DateTime(2021, 1, 5);
174+
await tester.pumpAndSettle();
175+
176+
expect(tester.takeException(), isNull);
177+
expect(find.text('1/5/2021'), findsOneWidget);
178+
});
133179
});
134180
}

0 commit comments

Comments
 (0)