Skip to content

Commit b31ca1a

Browse files
author
Albertus Angga Raharja
authored
Add more structure to errors (continuation of flutter#34684) (flutter#42640)
* Add structured errors in Animations, TabView, ChangeNotifier * Add structured error on MaterialPageRoute, BoxBorder, DecorationImagePainter, TextSpan * Add structured errors in Debug * Fix test errors * Add structured errors in Scaffold and Stepper * Add structured errors in part of Rendering Layer * Fix failing test due to FloatingPoint precision * Fix failing tests due to precision error and not using final * Fix failing test due to floating precision error with RegEx instead * Add structured error in CustomLayout and increase test coverage * Add structured error & its test in ListBody * Add structured error in ProxyBox and increase test coverage * Add structured error message in Viewport * Fix styles and add more assertions on ErrorHint and DiagnosticProperty * Add structured error in scheduler/binding and scheduler/ticker Signed-off-by: Albertus Angga Raharja <albertusangga@google.com> * Add structured error in AssetBundle and TextInput Signed-off-by: Albertus Angga Raharja <albertusangga@google.com> * Add structured errors in several widgets #1 Signed-off-by: Albertus Angga Raharja <albertusangga@google.com> * Remove unused import Signed-off-by: Albertus Angga Raharja <albertusangga@google.com> * Add assertions on hint messages Signed-off-by: Albertus Angga Raharja <albertusangga@google.com> * Fix catch spacing Signed-off-by: Albertus Angga Raharja <albertusangga@google.com> * Add structured error in several widgets part 2 and increase code coverage Signed-off-by: Albertus Angga Raharja <albertusangga@google.com> * Add structured error in flutter_test/widget_tester * Fix floating precision accuracy by using RegExp Signed-off-by: Albertus Angga Raharja <albertusangga@google.com> * Remove todo to add tests in Scaffold showBottomSheet Signed-off-by: Albertus Angga Raharja <albertusangga@google.com> * Fix reviews by indenting lines and fixing the assertion orders Signed-off-by: Albertus Angga Raharja <albertusangga@google.com> * Fix failing tests due to renaming class Signed-off-by: Albertus Angga Raharja <albertusangga@google.com> * Try skipping the NetworkBundleTest Signed-off-by: Albertus Angga Raharja <albertusangga@google.com> * Remove leading space in material/debug error hint Signed-off-by: Albertus Angga Raharja <albertusangga@google.com>
1 parent 734ddd3 commit b31ca1a

File tree

72 files changed

+3167
-608
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+3167
-608
lines changed

packages/flutter/lib/src/animation/animations.dart

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -439,12 +439,14 @@ class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<do
439439
final double transformedValue = activeCurve.transform(t);
440440
final double roundedTransformedValue = transformedValue.round().toDouble();
441441
if (roundedTransformedValue != t) {
442-
throw FlutterError(
443-
'Invalid curve endpoint at $t.\n'
444-
'Curves must map 0.0 to near zero and 1.0 to near one but '
445-
'${activeCurve.runtimeType} mapped $t to $transformedValue, which '
446-
'is near $roundedTransformedValue.'
447-
);
442+
throw FlutterError.fromParts(<DiagnosticsNode>[
443+
ErrorSummary('Invalid curve endpoint at $t.'),
444+
ErrorDescription(
445+
'Curves must map 0.0 to near zero and 1.0 to near one but '
446+
'${activeCurve.runtimeType} mapped $t to $transformedValue, which '
447+
'is near $roundedTransformedValue.'
448+
)
449+
]);
448450
}
449451
return true;
450452
}());

packages/flutter/lib/src/cupertino/tab_view.dart

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -192,29 +192,33 @@ class _CupertinoTabViewState extends State<CupertinoTabView> {
192192
Route<dynamic> _onUnknownRoute(RouteSettings settings) {
193193
assert(() {
194194
if (widget.onUnknownRoute == null) {
195-
throw FlutterError(
196-
'Could not find a generator for route $settings in the $runtimeType.\n'
197-
'Generators for routes are searched for in the following order:\n'
198-
' 1. For the "/" route, the "builder" property, if non-null, is used.\n'
199-
' 2. Otherwise, the "routes" table is used, if it has an entry for '
200-
'the route.\n'
201-
' 3. Otherwise, onGenerateRoute is called. It should return a '
202-
'non-null value for any valid route not handled by "builder" and "routes".\n'
203-
' 4. Finally if all else fails onUnknownRoute is called.\n'
204-
'Unfortunately, onUnknownRoute was not set.'
205-
);
195+
throw FlutterError.fromParts(<DiagnosticsNode>[
196+
ErrorSummary('Could not find a generator for route $settings in the $runtimeType.'),
197+
ErrorDescription(
198+
'Generators for routes are searched for in the following order:\n'
199+
' 1. For the "/" route, the "builder" property, if non-null, is used.\n'
200+
' 2. Otherwise, the "routes" table is used, if it has an entry for '
201+
'the route.\n'
202+
' 3. Otherwise, onGenerateRoute is called. It should return a '
203+
'non-null value for any valid route not handled by "builder" and "routes".\n'
204+
' 4. Finally if all else fails onUnknownRoute is called.\n'
205+
'Unfortunately, onUnknownRoute was not set.'
206+
)
207+
]);
206208
}
207209
return true;
208210
}());
209211
final Route<dynamic> result = widget.onUnknownRoute(settings);
210212
assert(() {
211213
if (result == null) {
212-
throw FlutterError(
213-
'The onUnknownRoute callback returned null.\n'
214-
'When the $runtimeType requested the route $settings from its '
215-
'onUnknownRoute callback, the callback returned null. Such callbacks '
216-
'must never return null.'
217-
);
214+
throw FlutterError.fromParts(<DiagnosticsNode>[
215+
ErrorSummary('The onUnknownRoute callback returned null.'),
216+
ErrorDescription(
217+
'When the $runtimeType requested the route $settings from its '
218+
'onUnknownRoute callback, the callback returned null. Such callbacks '
219+
'must never return null.'
220+
)
221+
]);
218222
}
219223
return true;
220224
}());

packages/flutter/lib/src/foundation/change_notifier.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ class ChangeNotifier implements Listenable {
102102
bool _debugAssertNotDisposed() {
103103
assert(() {
104104
if (_listeners == null) {
105-
throw FlutterError(
106-
'A $runtimeType was used after being disposed.\n'
107-
'Once you have called dispose() on a $runtimeType, it can no longer be used.'
108-
);
105+
throw FlutterError.fromParts(<DiagnosticsNode>[
106+
ErrorSummary('A $runtimeType was used after being disposed.'),
107+
ErrorDescription('Once you have called dispose() on a $runtimeType, it can no longer be used.')
108+
]);
109109
}
110110
return true;
111111
}());

packages/flutter/lib/src/material/debug.dart

Lines changed: 45 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:flutter/foundation.dart';
56
import 'package:flutter/widgets.dart';
67

78
import 'material.dart';
@@ -24,45 +25,26 @@ import 'scaffold.dart' show Scaffold;
2425
bool debugCheckHasMaterial(BuildContext context) {
2526
assert(() {
2627
if (context.widget is! Material && context.ancestorWidgetOfExactType(Material) == null) {
27-
final StringBuffer message = StringBuffer();
28-
message.writeln('No Material widget found.');
29-
message.writeln(
30-
'${context.widget.runtimeType} widgets require a Material '
31-
'widget ancestor.'
28+
throw FlutterError.fromParts(<DiagnosticsNode>[
29+
ErrorSummary('No Material widget found.'),
30+
ErrorDescription(
31+
'${context.widget.runtimeType} widgets require a Material '
32+
'widget ancestor.\n'
33+
'In material design, most widgets are conceptually "printed" on '
34+
'a sheet of material. In Flutter\'s material library, that '
35+
'material is represented by the Material widget. It is the '
36+
'Material widget that renders ink splashes, for instance. '
37+
'Because of this, many material library widgets require that '
38+
'there be a Material widget in the tree above them.'
39+
),
40+
ErrorHint(
41+
'To introduce a Material widget, you can either directly '
42+
'include one, or use a widget that contains Material itself, '
43+
'such as a Card, Dialog, Drawer, or Scaffold.',
44+
),
45+
...context.describeMissingAncestor(expectedAncestorType: Material)
46+
]
3247
);
33-
message.writeln(
34-
'In material design, most widgets are conceptually "printed" on '
35-
'a sheet of material. In Flutter\'s material library, that '
36-
'material is represented by the Material widget. It is the '
37-
'Material widget that renders ink splashes, for instance. '
38-
'Because of this, many material library widgets require that '
39-
'there be a Material widget in the tree above them.'
40-
);
41-
message.writeln(
42-
'To introduce a Material widget, you can either directly '
43-
'include one, or use a widget that contains Material itself, '
44-
'such as a Card, Dialog, Drawer, or Scaffold.'
45-
);
46-
message.writeln(
47-
'The specific widget that could not find a Material ancestor was:'
48-
);
49-
message.writeln(' ${context.widget}');
50-
final List<Widget> ancestors = <Widget>[];
51-
context.visitAncestorElements((Element element) {
52-
ancestors.add(element.widget);
53-
return true;
54-
});
55-
if (ancestors.isNotEmpty) {
56-
message.write('The ancestors of this widget were:');
57-
for (Widget ancestor in ancestors)
58-
message.write('\n $ancestor');
59-
} else {
60-
message.writeln(
61-
'This widget is the root of the tree, so it has no '
62-
'ancestors, let alone a "Material" ancestor.'
63-
);
64-
}
65-
throw FlutterError(message.toString());
6648
}
6749
return true;
6850
}());
@@ -87,42 +69,24 @@ bool debugCheckHasMaterial(BuildContext context) {
8769
bool debugCheckHasMaterialLocalizations(BuildContext context) {
8870
assert(() {
8971
if (Localizations.of<MaterialLocalizations>(context, MaterialLocalizations) == null) {
90-
final StringBuffer message = StringBuffer();
91-
message.writeln('No MaterialLocalizations found.');
92-
message.writeln(
93-
'${context.widget.runtimeType} widgets require MaterialLocalizations '
94-
'to be provided by a Localizations widget ancestor.'
95-
);
96-
message.writeln(
97-
'Localizations are used to generate many different messages, labels,'
98-
'and abbreviations which are used by the material library. '
99-
);
100-
message.writeln(
101-
'To introduce a MaterialLocalizations, either use a '
102-
' MaterialApp at the root of your application to include them '
103-
'automatically, or add a Localization widget with a '
104-
'MaterialLocalizations delegate.'
105-
);
106-
message.writeln(
107-
'The specific widget that could not find a MaterialLocalizations ancestor was:'
108-
);
109-
message.writeln(' ${context.widget}');
110-
final List<Widget> ancestors = <Widget>[];
111-
context.visitAncestorElements((Element element) {
112-
ancestors.add(element.widget);
113-
return true;
114-
});
115-
if (ancestors.isNotEmpty) {
116-
message.write('The ancestors of this widget were:');
117-
for (Widget ancestor in ancestors)
118-
message.write('\n $ancestor');
119-
} else {
120-
message.writeln(
121-
'This widget is the root of the tree, so it has no '
122-
'ancestors, let alone a "Localizations" ancestor.'
123-
);
124-
}
125-
throw FlutterError(message.toString());
72+
throw FlutterError.fromParts(<DiagnosticsNode>[
73+
ErrorSummary('No MaterialLocalizations found.'),
74+
ErrorDescription(
75+
'${context.widget.runtimeType} widgets require MaterialLocalizations '
76+
'to be provided by a Localizations widget ancestor.'
77+
),
78+
ErrorDescription(
79+
'Localizations are used to generate many different messages, labels,'
80+
'and abbreviations which are used by the material library.'
81+
),
82+
ErrorHint(
83+
'To introduce a MaterialLocalizations, either use a '
84+
'MaterialApp at the root of your application to include them '
85+
'automatically, or add a Localization widget with a '
86+
'MaterialLocalizations delegate.'
87+
),
88+
...context.describeMissingAncestor(expectedAncestorType: MaterialLocalizations)
89+
]);
12690
}
12791
return true;
12892
}());
@@ -145,17 +109,15 @@ bool debugCheckHasMaterialLocalizations(BuildContext context) {
145109
bool debugCheckHasScaffold(BuildContext context) {
146110
assert(() {
147111
if (context.widget is! Scaffold && context.ancestorWidgetOfExactType(Scaffold) == null) {
148-
final Element element = context;
149-
throw FlutterError(
150-
'No Scaffold widget found.\n'
151-
'${context.widget.runtimeType} widgets require a Scaffold widget ancestor.\n'
152-
'The Specific widget that could not find a Scaffold ancestor was:\n'
153-
' ${context.widget}\n'
154-
'The ownership chain for the affected widget is:\n'
155-
' ${element.debugGetCreatorChain(10)}\n'
112+
throw FlutterError.fromParts(<DiagnosticsNode>[
113+
ErrorSummary('No Scaffold widget found.'),
114+
ErrorDescription('${context.widget.runtimeType} widgets require a Scaffold widget ancestor.'),
115+
...context.describeMissingAncestor(expectedAncestorType: Scaffold),
116+
ErrorHint(
156117
'Typically, the Scaffold widget is introduced by the MaterialApp or '
157118
'WidgetsApp widget at the top of your application widget tree.'
158-
);
119+
)
120+
]);
159121
}
160122
return true;
161123
}());

packages/flutter/lib/src/material/page.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@ class MaterialPageRoute<T> extends PageRoute<T> {
8787
final Widget result = builder(context);
8888
assert(() {
8989
if (result == null) {
90-
throw FlutterError(
91-
'The builder for route "${settings.name}" returned null.\n'
92-
'Route builders must never return null.'
93-
);
90+
throw FlutterError.fromParts(<DiagnosticsNode>[
91+
ErrorSummary('The builder for route "${settings.name}" returned null.'),
92+
ErrorDescription('Route builders must never return null.')
93+
]);
9494
}
9595
return true;
9696
}());

0 commit comments

Comments
 (0)