mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Reland: "Add example and troubleshooting comment for showSnackBar
" (#105195)
This commit is contained in:
parent
8f0981c048
commit
8faccb8a82
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// Flutter code sample for SnackBar
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
void main() => runApp(const SnackBarApp());
|
||||||
|
|
||||||
|
class SnackBarApp extends StatelessWidget {
|
||||||
|
const SnackBarApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const MaterialApp(
|
||||||
|
home: SnackBarExample(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnackBarExample extends StatefulWidget {
|
||||||
|
const SnackBarExample({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SnackBarExample> createState() => _SnackBarExampleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SnackBarExampleState extends State<SnackBarExample> {
|
||||||
|
bool _largeLogo = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('SnackBar Sample')),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
const SnackBar snackBar = SnackBar(
|
||||||
|
content: Text('A SnackBar has been shown.'),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
},
|
||||||
|
child: const Text('Show SnackBar'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8.0),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() => _largeLogo = !_largeLogo);
|
||||||
|
},
|
||||||
|
child: Text(_largeLogo ? 'Shrink Logo' : 'Grow Logo'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// A floating [SnackBar] is positioned above [Scaffold.floatingActionButton].
|
||||||
|
// If the Widget provided to the floatingActionButton slot takes up too much space
|
||||||
|
// for the SnackBar to be visible, an error will be thrown.
|
||||||
|
floatingActionButton: Container(
|
||||||
|
constraints: BoxConstraints.tightFor(
|
||||||
|
width: 150,
|
||||||
|
height: _largeLogo ? double.infinity : 150,
|
||||||
|
),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.blueGrey,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: const FlutterLogo(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_api_samples/material/scaffold/scaffold_messenger_state.show_snack_bar.1.dart' as example;
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Floating SnackBar is visible', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const example.SnackBarApp(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Finder buttonFinder = find.byType(ElevatedButton);
|
||||||
|
await tester.tap(buttonFinder.first);
|
||||||
|
// Have the SnackBar fully animate out.
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final Finder snackBarFinder = find.byType(SnackBar);
|
||||||
|
expect(snackBarFinder, findsOneWidget);
|
||||||
|
|
||||||
|
// Grow logo to send SnackBar off screen.
|
||||||
|
await tester.tap(buttonFinder.last);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final AssertionError exception = tester.takeException() as AssertionError;
|
||||||
|
const String message = 'Floating SnackBar presented off screen.\n'
|
||||||
|
'A SnackBar with behavior property set to SnackBarBehavior.floating is fully '
|
||||||
|
'or partially off screen because some or all the widgets provided to '
|
||||||
|
'Scaffold.floatingActionButton, Scaffold.persistentFooterButtons and '
|
||||||
|
'Scaffold.bottomNavigationBar take up too much vertical space.\n'
|
||||||
|
'Consider constraining the size of these widgets to allow room for the SnackBar to be visible.';
|
||||||
|
expect(exception.message, message);
|
||||||
|
});
|
||||||
|
}
|
@ -264,6 +264,23 @@ class ScaffoldMessengerState extends State<ScaffoldMessenger> with TickerProvide
|
|||||||
///
|
///
|
||||||
/// ** See code in examples/api/lib/material/scaffold/scaffold_messenger_state.show_snack_bar.0.dart **
|
/// ** See code in examples/api/lib/material/scaffold/scaffold_messenger_state.show_snack_bar.0.dart **
|
||||||
/// {@end-tool}
|
/// {@end-tool}
|
||||||
|
///
|
||||||
|
/// ## Relative positioning of floating SnackBars
|
||||||
|
///
|
||||||
|
/// A [SnackBar] with [SnackBar.behavior] set to [SnackBarBehavior.floating] is
|
||||||
|
/// positioned above the widgets provided to [Scaffold.floatingActionButton],
|
||||||
|
/// [Scaffold.persistentFooterButtons], and [Scaffold.bottomNavigationBar].
|
||||||
|
/// If some or all of these widgets take up enough space such that the SnackBar
|
||||||
|
/// would not be visible when positioned above them, an error will be thrown.
|
||||||
|
/// In this case, consider constraining the size of these widgets to allow room for
|
||||||
|
/// the SnackBar to be visible.
|
||||||
|
///
|
||||||
|
/// {@tool dartpad}
|
||||||
|
/// Here is an example showing that a floating [SnackBar] appears above [Scaffold.floatingActionButton].
|
||||||
|
///
|
||||||
|
/// ** See code in examples/api/lib/material/scaffold/scaffold_messenger_state.show_snack_bar.1.dart **
|
||||||
|
/// {@end-tool}
|
||||||
|
///
|
||||||
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackBar) {
|
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackBar) {
|
||||||
assert(
|
assert(
|
||||||
_scaffolds.isNotEmpty,
|
_scaffolds.isNotEmpty,
|
||||||
@ -1151,6 +1168,32 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
|
|||||||
|
|
||||||
final double xOffset = hasCustomWidth ? (size.width - snackBarWidth!) / 2 : 0.0;
|
final double xOffset = hasCustomWidth ? (size.width - snackBarWidth!) / 2 : 0.0;
|
||||||
positionChild(_ScaffoldSlot.snackBar, Offset(xOffset, snackBarYOffsetBase - snackBarSize.height));
|
positionChild(_ScaffoldSlot.snackBar, Offset(xOffset, snackBarYOffsetBase - snackBarSize.height));
|
||||||
|
|
||||||
|
assert((){
|
||||||
|
// Whether a floating SnackBar has been offsetted too high.
|
||||||
|
//
|
||||||
|
// To improve the developper experience, this assert is done after the call to positionChild.
|
||||||
|
// if we assert sooner the SnackBar is visible because its defaults position is (0,0) and
|
||||||
|
// it can cause confusion to the user as the error message states that the SnackBar is off screen.
|
||||||
|
if (isSnackBarFloating) {
|
||||||
|
final bool snackBarVisible = (snackBarYOffsetBase - snackBarSize.height) >= 0;
|
||||||
|
if (!snackBarVisible) {
|
||||||
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
|
ErrorSummary('Floating SnackBar presented off screen.'),
|
||||||
|
ErrorDescription(
|
||||||
|
'A SnackBar with behavior property set to SnackBarBehavior.floating is fully '
|
||||||
|
'or partially off screen because some or all the widgets provided to '
|
||||||
|
'Scaffold.floatingActionButton, Scaffold.persistentFooterButtons and '
|
||||||
|
'Scaffold.bottomNavigationBar take up too much vertical space.\n'
|
||||||
|
),
|
||||||
|
ErrorHint(
|
||||||
|
'Consider constraining the size of these widgets to allow room for the SnackBar to be visible.',
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasChild(_ScaffoldSlot.statusBar)) {
|
if (hasChild(_ScaffoldSlot.statusBar)) {
|
||||||
|
@ -1652,6 +1652,93 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Future<void> openFloatingSnackBar(WidgetTester tester) async {
|
||||||
|
final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
|
||||||
|
scaffoldMessengerState.showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('SnackBar text'),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectSnackBarNotVisibleError(WidgetTester tester) {
|
||||||
|
final AssertionError exception = tester.takeException() as AssertionError;
|
||||||
|
const String message = 'Floating SnackBar presented off screen.\n'
|
||||||
|
'A SnackBar with behavior property set to SnackBarBehavior.floating is fully '
|
||||||
|
'or partially off screen because some or all the widgets provided to '
|
||||||
|
'Scaffold.floatingActionButton, Scaffold.persistentFooterButtons and '
|
||||||
|
'Scaffold.bottomNavigationBar take up too much vertical space.\n'
|
||||||
|
'Consider constraining the size of these widgets to allow room for the SnackBar to be visible.';
|
||||||
|
expect(exception.message, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('Snackbar with SnackBarBehavior.floating will assert when offsetted too high by a large Scaffold.floatingActionButton', (WidgetTester tester) async {
|
||||||
|
// Regression test for https://github.com/flutter/flutter/issues/84263
|
||||||
|
Future<void> boilerplate({required double? fabHeight}) {
|
||||||
|
return tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
floatingActionButton: Container(height: fabHeight),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run once with a visible SnackBar to compute the empty space above SnackBar.
|
||||||
|
const double mediumFabHeight = 100;
|
||||||
|
await boilerplate(fabHeight: mediumFabHeight);
|
||||||
|
await openFloatingSnackBar(tester);
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
final double spaceAboveSnackBar = tester.getTopLeft(find.byType(SnackBar)).dy;
|
||||||
|
|
||||||
|
// Run with the Snackbar fully off screen.
|
||||||
|
await boilerplate(fabHeight: spaceAboveSnackBar + mediumFabHeight * 2);
|
||||||
|
await openFloatingSnackBar(tester);
|
||||||
|
expectSnackBarNotVisibleError(tester);
|
||||||
|
|
||||||
|
// Run with the Snackbar partially off screen.
|
||||||
|
await boilerplate(fabHeight: spaceAboveSnackBar + mediumFabHeight + 10);
|
||||||
|
await openFloatingSnackBar(tester);
|
||||||
|
expectSnackBarNotVisibleError(tester);
|
||||||
|
|
||||||
|
// Run with the Snackbar fully visible right on the top of the screen.
|
||||||
|
await boilerplate(fabHeight: spaceAboveSnackBar + mediumFabHeight);
|
||||||
|
await openFloatingSnackBar(tester);
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Snackbar with SnackBarBehavior.floating will assert when offsetted too high by a large Scaffold.persistentFooterButtons', (WidgetTester tester) async {
|
||||||
|
// Regression test for https://github.com/flutter/flutter/issues/84263
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
persistentFooterButtons: <Widget>[SizedBox(height: 1000)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await openFloatingSnackBar(tester);
|
||||||
|
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
|
||||||
|
expectSnackBarNotVisibleError(tester);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Snackbar with SnackBarBehavior.floating will assert when offsetted too high by a large Scaffold.bottomNavigationBar', (WidgetTester tester) async {
|
||||||
|
// Regression test for https://github.com/flutter/flutter/issues/84263
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
bottomNavigationBar: SizedBox(height: 1000),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await openFloatingSnackBar(tester);
|
||||||
|
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
|
||||||
|
expectSnackBarNotVisibleError(tester);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'SnackBar has correct end padding when it contains an action with fixed behavior',
|
'SnackBar has correct end padding when it contains an action with fixed behavior',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
|
Loading…
Reference in New Issue
Block a user