Improve SnackBar error message when shown during build (#106658)
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index b5c62ab..f098008 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -242,7 +242,7 @@
// SNACKBAR API
- /// Shows a [SnackBar] across all registered [Scaffold]s.
+ /// Shows a [SnackBar] across all registered [Scaffold]s.
///
/// A scaffold can show at most one snack bar at a time. If this function is
/// called while another snack bar is already visible, the given snack bar
@@ -289,10 +289,43 @@
},
null, // SnackBar doesn't use a builder function so setState() wouldn't rebuild it
);
- setState(() {
- _snackBars.addLast(controller);
- });
- _updateScaffolds();
+ try {
+ setState(() {
+ _snackBars.addLast(controller);
+ });
+ _updateScaffolds();
+ } catch (exception) {
+ assert (() {
+ if (exception is FlutterError) {
+ final String summary = exception.diagnostics.first.toDescription();
+ if (summary == 'setState() or markNeedsBuild() called during build.') {
+ final List<DiagnosticsNode> information = <DiagnosticsNode>[
+ ErrorSummary('The showSnackBar() method cannot be called during build.'),
+ ErrorDescription(
+ 'The showSnackBar() method was called during build, which is '
+ 'prohibited as showing snack bars requires updating state. Updating '
+ 'state is not possible during build.',
+ ),
+ ErrorHint(
+ 'Instead of calling showSnackBar() during build, call it directly '
+ 'in your on tap (and related) callbacks. If you need to immediately '
+ 'show a snack bar, make the call in initState() or '
+ 'didChangeDependencies() instead. Otherwise, you can also schedule a '
+ 'post-frame callback using SchedulerBinding.addPostFrameCallback to '
+ 'show the snack bar after the current frame.',
+ ),
+ context.describeOwnershipChain(
+ 'The ownership chain for the particular ScaffoldMessenger is',
+ ),
+ ];
+ throw FlutterError.fromParts(information);
+ }
+ }
+ return true;
+ }());
+ rethrow;
+ }
+
return controller;
}
diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart
index d39ce30..370649b 100644
--- a/packages/flutter/test/material/scaffold_test.dart
+++ b/packages/flutter/test/material/scaffold_test.dart
@@ -2582,6 +2582,23 @@
expect(isDrawerOpen, false);
expect(isEndDrawerOpen, false);
});
+
+ testWidgets('ScaffoldMessenger showSnackBar throws an intuitive error message if called during build', (WidgetTester tester) async {
+ await tester.pumpWidget(MaterialApp(
+ home: Scaffold(
+ body: Builder(
+ builder: (BuildContext context) {
+ ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('SnackBar')));
+ return const SizedBox.shrink();
+ },
+ ),
+ ),
+ ));
+
+ final FlutterError error = tester.takeException() as FlutterError;
+ final ErrorSummary summary = error.diagnostics.first as ErrorSummary;
+ expect(summary.toString(), 'The showSnackBar() method cannot be called during build.');
+ });
}
class _GeometryListener extends StatefulWidget {