diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index b5c62ab7c03..f0980085878 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -242,7 +242,7 @@ class ScaffoldMessengerState extends State with TickerProvide // 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 @@ class ScaffoldMessengerState extends State with TickerProvide }, 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 information = [ + 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 d39ce30f0e0..370649bd0b6 100644 --- a/packages/flutter/test/material/scaffold_test.dart +++ b/packages/flutter/test/material/scaffold_test.dart @@ -2582,6 +2582,23 @@ void main() { 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 {