Skip to content

Commit 8faccb8

Browse files
authored
Reland: "Add example and troubleshooting comment for showSnackBar" (#105195)
1 parent 8f0981c commit 8faccb8

4 files changed

Lines changed: 242 additions & 0 deletions

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flutter code sample for SnackBar
6+
7+
import 'package:flutter/material.dart';
8+
9+
void main() => runApp(const SnackBarApp());
10+
11+
class SnackBarApp extends StatelessWidget {
12+
const SnackBarApp({super.key});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
return const MaterialApp(
17+
home: SnackBarExample(),
18+
);
19+
}
20+
}
21+
22+
class SnackBarExample extends StatefulWidget {
23+
const SnackBarExample({super.key});
24+
25+
@override
26+
State<SnackBarExample> createState() => _SnackBarExampleState();
27+
}
28+
29+
class _SnackBarExampleState extends State<SnackBarExample> {
30+
bool _largeLogo = false;
31+
32+
@override
33+
Widget build(BuildContext context) {
34+
return Scaffold(
35+
appBar: AppBar(title: const Text('SnackBar Sample')),
36+
body: Padding(
37+
padding: const EdgeInsets.all(8.0),
38+
child: Column(
39+
children: <Widget>[
40+
ElevatedButton(
41+
onPressed: () {
42+
const SnackBar snackBar = SnackBar(
43+
content: Text('A SnackBar has been shown.'),
44+
behavior: SnackBarBehavior.floating,
45+
);
46+
ScaffoldMessenger.of(context).showSnackBar(snackBar);
47+
},
48+
child: const Text('Show SnackBar'),
49+
),
50+
const SizedBox(height: 8.0),
51+
ElevatedButton(
52+
onPressed: () {
53+
setState(() => _largeLogo = !_largeLogo);
54+
},
55+
child: Text(_largeLogo ? 'Shrink Logo' : 'Grow Logo'),
56+
),
57+
],
58+
),
59+
),
60+
// A floating [SnackBar] is positioned above [Scaffold.floatingActionButton].
61+
// If the Widget provided to the floatingActionButton slot takes up too much space
62+
// for the SnackBar to be visible, an error will be thrown.
63+
floatingActionButton: Container(
64+
constraints: BoxConstraints.tightFor(
65+
width: 150,
66+
height: _largeLogo ? double.infinity : 150,
67+
),
68+
decoration: const BoxDecoration(
69+
color: Colors.blueGrey,
70+
borderRadius: BorderRadius.all(Radius.circular(20)),
71+
),
72+
child: const FlutterLogo(),
73+
),
74+
);
75+
}
76+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_api_samples/material/scaffold/scaffold_messenger_state.show_snack_bar.1.dart' as example;
7+
import 'package:flutter_test/flutter_test.dart';
8+
9+
void main() {
10+
testWidgets('Floating SnackBar is visible', (WidgetTester tester) async {
11+
await tester.pumpWidget(
12+
const example.SnackBarApp(),
13+
);
14+
15+
final Finder buttonFinder = find.byType(ElevatedButton);
16+
await tester.tap(buttonFinder.first);
17+
// Have the SnackBar fully animate out.
18+
await tester.pumpAndSettle();
19+
20+
final Finder snackBarFinder = find.byType(SnackBar);
21+
expect(snackBarFinder, findsOneWidget);
22+
23+
// Grow logo to send SnackBar off screen.
24+
await tester.tap(buttonFinder.last);
25+
await tester.pumpAndSettle();
26+
27+
final AssertionError exception = tester.takeException() as AssertionError;
28+
const String message = 'Floating SnackBar presented off screen.\n'
29+
'A SnackBar with behavior property set to SnackBarBehavior.floating is fully '
30+
'or partially off screen because some or all the widgets provided to '
31+
'Scaffold.floatingActionButton, Scaffold.persistentFooterButtons and '
32+
'Scaffold.bottomNavigationBar take up too much vertical space.\n'
33+
'Consider constraining the size of these widgets to allow room for the SnackBar to be visible.';
34+
expect(exception.message, message);
35+
});
36+
}

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,23 @@ class ScaffoldMessengerState extends State<ScaffoldMessenger> with TickerProvide
264264
///
265265
/// ** See code in examples/api/lib/material/scaffold/scaffold_messenger_state.show_snack_bar.0.dart **
266266
/// {@end-tool}
267+
///
268+
/// ## Relative positioning of floating SnackBars
269+
///
270+
/// A [SnackBar] with [SnackBar.behavior] set to [SnackBarBehavior.floating] is
271+
/// positioned above the widgets provided to [Scaffold.floatingActionButton],
272+
/// [Scaffold.persistentFooterButtons], and [Scaffold.bottomNavigationBar].
273+
/// If some or all of these widgets take up enough space such that the SnackBar
274+
/// would not be visible when positioned above them, an error will be thrown.
275+
/// In this case, consider constraining the size of these widgets to allow room for
276+
/// the SnackBar to be visible.
277+
///
278+
/// {@tool dartpad}
279+
/// Here is an example showing that a floating [SnackBar] appears above [Scaffold.floatingActionButton].
280+
///
281+
/// ** See code in examples/api/lib/material/scaffold/scaffold_messenger_state.show_snack_bar.1.dart **
282+
/// {@end-tool}
283+
///
267284
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackBar) {
268285
assert(
269286
_scaffolds.isNotEmpty,
@@ -1151,6 +1168,32 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
11511168

11521169
final double xOffset = hasCustomWidth ? (size.width - snackBarWidth!) / 2 : 0.0;
11531170
positionChild(_ScaffoldSlot.snackBar, Offset(xOffset, snackBarYOffsetBase - snackBarSize.height));
1171+
1172+
assert((){
1173+
// Whether a floating SnackBar has been offsetted too high.
1174+
//
1175+
// To improve the developper experience, this assert is done after the call to positionChild.
1176+
// if we assert sooner the SnackBar is visible because its defaults position is (0,0) and
1177+
// it can cause confusion to the user as the error message states that the SnackBar is off screen.
1178+
if (isSnackBarFloating) {
1179+
final bool snackBarVisible = (snackBarYOffsetBase - snackBarSize.height) >= 0;
1180+
if (!snackBarVisible) {
1181+
throw FlutterError.fromParts(<DiagnosticsNode>[
1182+
ErrorSummary('Floating SnackBar presented off screen.'),
1183+
ErrorDescription(
1184+
'A SnackBar with behavior property set to SnackBarBehavior.floating is fully '
1185+
'or partially off screen because some or all the widgets provided to '
1186+
'Scaffold.floatingActionButton, Scaffold.persistentFooterButtons and '
1187+
'Scaffold.bottomNavigationBar take up too much vertical space.\n'
1188+
),
1189+
ErrorHint(
1190+
'Consider constraining the size of these widgets to allow room for the SnackBar to be visible.',
1191+
),
1192+
]);
1193+
}
1194+
}
1195+
return true;
1196+
}());
11541197
}
11551198

11561199
if (hasChild(_ScaffoldSlot.statusBar)) {

packages/flutter/test/material/snack_bar_test.dart

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,6 +1652,93 @@ void main() {
16521652
},
16531653
);
16541654

1655+
Future<void> openFloatingSnackBar(WidgetTester tester) async {
1656+
final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
1657+
scaffoldMessengerState.showSnackBar(
1658+
const SnackBar(
1659+
content: Text('SnackBar text'),
1660+
behavior: SnackBarBehavior.floating,
1661+
),
1662+
);
1663+
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
1664+
}
1665+
1666+
void expectSnackBarNotVisibleError(WidgetTester tester) {
1667+
final AssertionError exception = tester.takeException() as AssertionError;
1668+
const String message = 'Floating SnackBar presented off screen.\n'
1669+
'A SnackBar with behavior property set to SnackBarBehavior.floating is fully '
1670+
'or partially off screen because some or all the widgets provided to '
1671+
'Scaffold.floatingActionButton, Scaffold.persistentFooterButtons and '
1672+
'Scaffold.bottomNavigationBar take up too much vertical space.\n'
1673+
'Consider constraining the size of these widgets to allow room for the SnackBar to be visible.';
1674+
expect(exception.message, message);
1675+
}
1676+
1677+
testWidgets('Snackbar with SnackBarBehavior.floating will assert when offsetted too high by a large Scaffold.floatingActionButton', (WidgetTester tester) async {
1678+
// Regression test for https://github.com/flutter/flutter/issues/84263
1679+
Future<void> boilerplate({required double? fabHeight}) {
1680+
return tester.pumpWidget(
1681+
MaterialApp(
1682+
home: Scaffold(
1683+
floatingActionButton: Container(height: fabHeight),
1684+
),
1685+
),
1686+
);
1687+
}
1688+
1689+
// Run once with a visible SnackBar to compute the empty space above SnackBar.
1690+
const double mediumFabHeight = 100;
1691+
await boilerplate(fabHeight: mediumFabHeight);
1692+
await openFloatingSnackBar(tester);
1693+
expect(tester.takeException(), isNull);
1694+
final double spaceAboveSnackBar = tester.getTopLeft(find.byType(SnackBar)).dy;
1695+
1696+
// Run with the Snackbar fully off screen.
1697+
await boilerplate(fabHeight: spaceAboveSnackBar + mediumFabHeight * 2);
1698+
await openFloatingSnackBar(tester);
1699+
expectSnackBarNotVisibleError(tester);
1700+
1701+
// Run with the Snackbar partially off screen.
1702+
await boilerplate(fabHeight: spaceAboveSnackBar + mediumFabHeight + 10);
1703+
await openFloatingSnackBar(tester);
1704+
expectSnackBarNotVisibleError(tester);
1705+
1706+
// Run with the Snackbar fully visible right on the top of the screen.
1707+
await boilerplate(fabHeight: spaceAboveSnackBar + mediumFabHeight);
1708+
await openFloatingSnackBar(tester);
1709+
expect(tester.takeException(), isNull);
1710+
});
1711+
1712+
testWidgets('Snackbar with SnackBarBehavior.floating will assert when offsetted too high by a large Scaffold.persistentFooterButtons', (WidgetTester tester) async {
1713+
// Regression test for https://github.com/flutter/flutter/issues/84263
1714+
await tester.pumpWidget(
1715+
const MaterialApp(
1716+
home: Scaffold(
1717+
persistentFooterButtons: <Widget>[SizedBox(height: 1000)],
1718+
),
1719+
),
1720+
);
1721+
1722+
await openFloatingSnackBar(tester);
1723+
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
1724+
expectSnackBarNotVisibleError(tester);
1725+
});
1726+
1727+
testWidgets('Snackbar with SnackBarBehavior.floating will assert when offsetted too high by a large Scaffold.bottomNavigationBar', (WidgetTester tester) async {
1728+
// Regression test for https://github.com/flutter/flutter/issues/84263
1729+
await tester.pumpWidget(
1730+
const MaterialApp(
1731+
home: Scaffold(
1732+
bottomNavigationBar: SizedBox(height: 1000),
1733+
),
1734+
),
1735+
);
1736+
1737+
await openFloatingSnackBar(tester);
1738+
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
1739+
expectSnackBarNotVisibleError(tester);
1740+
});
1741+
16551742
testWidgets(
16561743
'SnackBar has correct end padding when it contains an action with fixed behavior',
16571744
(WidgetTester tester) async {

0 commit comments

Comments
 (0)