Revert "Re-land ScaffoldMessenger (#65416)" (#65482)
This reverts commit adc5f26b504f3cb80557455d8f0badad874ebc8b.
diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart
index cb832ae..a1c7851 100644
--- a/packages/flutter/lib/src/material/app.dart
+++ b/packages/flutter/lib/src/material/app.dart
@@ -17,7 +17,6 @@
import 'icons.dart';
import 'material_localizations.dart';
import 'page.dart';
-import 'scaffold.dart';
import 'theme.dart';
/// [MaterialApp] uses this [TextStyle] as its [DefaultTextStyle] to encourage
@@ -169,7 +168,6 @@
const MaterialApp({
Key key,
this.navigatorKey,
- this.scaffoldMessengerKey,
this.home,
this.routes = const <String, WidgetBuilder>{},
this.initialRoute,
@@ -217,7 +215,6 @@
/// Creates a [MaterialApp] that uses the [Router] instead of a [Navigator].
const MaterialApp.router({
Key key,
- this.scaffoldMessengerKey,
this.routeInformationProvider,
@required this.routeInformationParser,
@required this.routerDelegate,
@@ -266,14 +263,6 @@
/// {@macro flutter.widgets.widgetsApp.navigatorKey}
final GlobalKey<NavigatorState> navigatorKey;
- /// A key to use when building the [ScaffoldMessenger].
- ///
- /// If a [scaffoldMessengerKey] is specified, the [ScaffoldMessenger] can be
- /// directly manipulated without first obtaining it from a [BuildContext] via
- /// [ScaffoldMessenger.of]: from the [scaffoldMessengerKey], use the
- /// [GlobalKey.currentState] getter.
- final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey;
-
/// {@macro flutter.widgets.widgetsApp.home}
final Widget home;
@@ -733,30 +722,27 @@
}
theme ??= widget.theme ?? ThemeData.light();
- return ScaffoldMessenger(
- key: widget.scaffoldMessengerKey,
- child: AnimatedTheme(
- data: theme,
- isMaterialAppTheme: true,
- child: widget.builder != null
- ? Builder(
- builder: (BuildContext context) {
- // Why are we surrounding a builder with a builder?
- //
- // The widget.builder may contain code that invokes
- // Theme.of(), which should return the theme we selected
- // above in AnimatedTheme. However, if we invoke
- // widget.builder() directly as the child of AnimatedTheme
- // then there is no Context separating them, and the
- // widget.builder() will not find the theme. Therefore, we
- // surround widget.builder with yet another builder so that
- // a context separates them and Theme.of() correctly
- // resolves to the theme we passed to AnimatedTheme.
- return widget.builder(context, child);
- },
- )
- : child,
- )
+ return AnimatedTheme(
+ data: theme,
+ isMaterialAppTheme: true,
+ child: widget.builder != null
+ ? Builder(
+ builder: (BuildContext context) {
+ // Why are we surrounding a builder with a builder?
+ //
+ // The widget.builder may contain code that invokes
+ // Theme.of(), which should return the theme we selected
+ // above in AnimatedTheme. However, if we invoke
+ // widget.builder() directly as the child of AnimatedTheme
+ // then there is no Context separating them, and the
+ // widget.builder() will not find the theme. Therefore, we
+ // surround widget.builder with yet another builder so that
+ // a context separates them and Theme.of() correctly
+ // resolves to the theme we passed to AnimatedTheme.
+ return widget.builder(context, child);
+ },
+ )
+ : child,
);
}
diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart
index ec694b0..b198a80 100644
--- a/packages/flutter/lib/src/material/app_bar.dart
+++ b/packages/flutter/lib/src/material/app_bar.dart
@@ -137,7 +137,7 @@
/// icon: const Icon(Icons.add_alert),
/// tooltip: 'Show Snackbar',
/// onPressed: () {
-/// ScaffoldMessenger.of(context).showSnackBar(snackBar);
+/// scaffoldKey.currentState.showSnackBar(snackBar);
/// },
/// ),
/// IconButton(
diff --git a/packages/flutter/lib/src/material/debug.dart b/packages/flutter/lib/src/material/debug.dart
index edee89e..e90ee46 100644
--- a/packages/flutter/lib/src/material/debug.dart
+++ b/packages/flutter/lib/src/material/debug.dart
@@ -9,7 +9,7 @@
import 'material.dart';
import 'material_localizations.dart';
-import 'scaffold.dart' show Scaffold, ScaffoldMessenger;
+import 'scaffold.dart' show Scaffold;
/// Asserts that the given context has a [Material] ancestor.
///
@@ -125,34 +125,3 @@
}());
return true;
}
-
-/// Asserts that the given context has a [ScaffoldMessenger] ancestor.
-///
-/// Used by various widgets to make sure that they are only used in an
-/// appropriate context.
-///
-/// To invoke this function, use the following pattern, typically in the
-/// relevant Widget's build method:
-///
-/// ```dart
-/// assert(debugCheckHasScaffoldMessenger(context));
-/// ```
-///
-/// Does nothing if asserts are disabled. Always returns true.
-bool debugCheckHasScaffoldMessenger(BuildContext context) {
- assert(() {
- if (context.widget is! ScaffoldMessenger && context.findAncestorWidgetOfExactType<ScaffoldMessenger>() == null) {
- throw FlutterError.fromParts(<DiagnosticsNode>[
- ErrorSummary('No ScaffoldMessenger widget found.'),
- ErrorDescription('${context.widget.runtimeType} widgets require a ScaffoldMessenger widget ancestor.'),
- ...context.describeMissingAncestor(expectedAncestorType: ScaffoldMessenger),
- ErrorHint(
- 'Typically, the ScaffoldMessenger widget is introduced by the MaterialApp '
- 'at the top of your application widget tree.'
- )
- ]);
- }
- return true;
- }());
- return true;
-}
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index c435c9a..17b87fa 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -19,7 +19,6 @@
import 'button_bar.dart';
import 'colors.dart';
import 'curves.dart';
-import 'debug.dart';
import 'divider.dart';
import 'drawer.dart';
import 'flexible_space_bar.dart';
@@ -62,372 +61,6 @@
statusBar,
}
-/// Manages [SnackBar]s for descendant [Scaffold]s.
-///
-/// This class provides APIs for showing snack bars.
-///
-/// To display a snack bar, obtain the [ScaffoldMessengerState] for the current
-/// [BuildContext] via [ScaffoldMessenger.of] and use the
-/// [ScaffoldMessengerState.showSnackBar] function.
-///
-/// See also:
-///
-/// * [SnackBar], which is a temporary notification typically shown near the
-/// bottom of the app using the [ScaffoldMessengerState.showSnackBar] method.
-/// * Cookbook: [Display a snackbar](https://flutter.dev/docs/cookbook/design/snackbars)
-class ScaffoldMessenger extends StatefulWidget {
- /// Creates a widget that manages [SnackBar]s for [Scaffold] descendants.
- const ScaffoldMessenger({
- Key key,
- @required this.child,
- }) : assert(child != null),
- super(key: key);
-
- /// The widget below this widget in the tree.
- ///
- /// {@macro flutter.widgets.child}
- final Widget child;
-
- /// The state from the closest instance of this class that encloses the given
- /// context.
- ///
- /// {@tool dartpad --template=stateless_widget_scaffold_center}
- /// Typical usage of the [ScaffoldMessenger.of] function is to call it in
- /// response to a user gesture or an application state change.
- ///
- /// ```dart
- /// Widget build(BuildContext context) {
- /// return ElevatedButton(
- /// child: const Text('SHOW A SNACKBAR'),
- /// onPressed: () {
- /// ScaffoldMessenger.of(context).showSnackBar(
- /// const SnackBar(
- /// content: Text('Have a snack!'),
- /// ),
- /// );
- /// },
- /// );
- /// }
- /// ```
- /// {@end-tool}
- ///
- /// A less elegant but more expedient solution is assign a [GlobalKey] to the
- /// [ScaffoldMessenger], then use the `key.currentState` property to obtain the
- /// [ScaffoldMessengerState] rather than using the [ScaffoldMessenger.of]
- /// function. The [MaterialApp.scaffoldMessengerKey] refers to the root
- /// ScaffoldMessenger that is provided by default.
- ///
- /// {@tool dartpad --template=freeform}
- /// Sometimes [SnackBar]s are produced by code that doesn't have ready access
- /// to a valid [BuildContext]. One such example of this is when you may want
- /// to show a SnackBar from a method outside of the `build` function. In these
- /// cases, you can assign a [GlobalKey] to the [ScaffoldMessenger]. This
- /// example shows a key being used to obtain the [ScaffoldMessengerState]
- /// provided by the [MaterialApp].
- ///
- /// ```dart imports
- /// import 'package:flutter/material.dart';
- /// ```
- /// ```dart
- /// void main() => runApp(MyApp());
- ///
- /// class MyApp extends StatefulWidget {
- /// @override
- /// _MyAppState createState() => _MyAppState();
- /// }
- ///
- /// class _MyAppState extends State<MyApp> {
- /// final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
- /// int _counter = 0;
- ///
- /// void _incrementCounter() {
- /// setState(() {
- /// _counter++;
- /// });
- /// if (_counter % 10 == 0) {
- /// _scaffoldMessengerKey.currentState.showSnackBar(const SnackBar(
- /// content: Text('A multiple of ten!'),
- /// ));
- /// }
- /// }
- ///
- /// @override
- /// Widget build(BuildContext context) {
- /// return MaterialApp(
- /// scaffoldMessengerKey: _scaffoldMessengerKey,
- /// home: Scaffold(
- /// appBar: AppBar(title: Text('ScaffoldMessenger Demo')),
- /// body: Center(
- /// child: Column(
- /// mainAxisAlignment: MainAxisAlignment.center,
- /// children: <Widget>[
- /// Text(
- /// 'You have pushed the button this many times:',
- /// ),
- /// Text(
- /// '$_counter',
- /// style: Theme.of(context).textTheme.headline4,
- /// ),
- /// ],
- /// ),
- /// ),
- /// floatingActionButton: FloatingActionButton(
- /// onPressed: _incrementCounter,
- /// tooltip: 'Increment',
- /// child: Icon(Icons.add),
- /// ),
- /// ),
- /// );
- /// }
- /// }
- ///
- /// ```
- /// {@end-tool}
- ///
- /// If there is no [ScaffoldMessenger] in scope, then this will throw an
- /// exception.
- static ScaffoldMessengerState of(BuildContext context) {
- assert(context != null);
- final _ScaffoldMessengerScope scope = context.dependOnInheritedWidgetOfExactType<_ScaffoldMessengerScope>();
- return scope?._scaffoldMessengerState;
- }
-
- @override
- ScaffoldMessengerState createState() => ScaffoldMessengerState();
-}
-
-/// State for a [ScaffoldMessenger].
-///
-/// A [ScaffoldMessengerState] object can be used to [showSnackBar] for every
-/// registered [Scaffold] that is a descendant of the associated
-/// [ScaffoldMessenger]. Scaffolds will register to receive [SnackBar]s from
-/// their closest ScaffoldMessenger ancestor.
-///
-/// Typically obtained via [ScaffoldMessenger.of].
-class ScaffoldMessengerState extends State<ScaffoldMessenger> with TickerProviderStateMixin {
- final LinkedHashSet<ScaffoldState> _scaffolds = LinkedHashSet<ScaffoldState>();
- final Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>> _snackBars = Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>>();
- AnimationController _snackBarController;
- Timer _snackBarTimer;
- bool _accessibleNavigation;
-
- @override
- void didChangeDependencies() {
- final MediaQueryData mediaQuery = MediaQuery.of(context);
- // If we transition from accessible navigation to non-accessible navigation
- // and there is a SnackBar that would have timed out that has already
- // completed its timer, dismiss that SnackBar. If the timer hasn't finished
- // yet, let it timeout as normal.
- if (_accessibleNavigation == true
- && !mediaQuery.accessibleNavigation
- && _snackBarTimer != null
- && !_snackBarTimer.isActive) {
- hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
- }
- _accessibleNavigation = mediaQuery.accessibleNavigation;
- super.didChangeDependencies();
- }
-
- void _register(ScaffoldState scaffold) {
- _scaffolds.add(scaffold);
- if (_snackBars.isNotEmpty) {
- scaffold._updateSnackBar();
- }
- }
-
- void _unregister(ScaffoldState scaffold) {
- final bool removed = _scaffolds.remove(scaffold);
- // ScaffoldStates should only be removed once.
- assert(removed);
- }
-
- /// 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
- /// will be added to a queue and displayed after the earlier snack bars have
- /// closed.
- ///
- /// To control how long a [SnackBar] remains visible, use [SnackBar.duration].
- ///
- /// To remove the [SnackBar] with an exit animation, use [hideCurrentSnackBar]
- /// or call [ScaffoldFeatureController.close] on the returned
- /// [ScaffoldFeatureController]. To remove a [SnackBar] suddenly (without an
- /// animation), use [removeCurrentSnackBar].
- ///
- /// See [ScaffoldMessenger.of] for information about how to obtain the
- /// [ScaffoldMessengerState].
- ///
- /// {@tool dartpad --template=stateless_widget_scaffold_center}
- ///
- /// Here is an example of showing a [SnackBar] when the user presses a button.
- ///
- /// ```dart
- /// Widget build(BuildContext context) {
- /// return OutlinedButton(
- /// onPressed: () {
- /// ScaffoldMessenger.of(context).showSnackBar(
- /// const SnackBar(
- /// content: Text('A SnackBar has been shown.'),
- /// ),
- /// );
- /// },
- /// child: const Text('Show SnackBar'),
- /// );
- /// }
- /// ```
- /// {@end-tool}
- ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackBar) {
- _snackBarController ??= SnackBar.createAnimationController(vsync: this)
- ..addStatusListener(_handleStatusChanged);
- if (_snackBars.isEmpty) {
- assert(_snackBarController.isDismissed);
- _snackBarController.forward();
- }
- ScaffoldFeatureController<SnackBar, SnackBarClosedReason> controller;
- controller = ScaffoldFeatureController<SnackBar, SnackBarClosedReason>._(
- // We provide a fallback key so that if back-to-back snackbars happen to
- // match in structure, material ink splashes and highlights don't survive
- // from one to the next.
- snackBar.withAnimation(_snackBarController, fallbackKey: UniqueKey()),
- Completer<SnackBarClosedReason>(),
- () {
- assert(_snackBars.first == controller);
- hideCurrentSnackBar(reason: SnackBarClosedReason.hide);
- },
- null, // SnackBar doesn't use a builder function so setState() wouldn't rebuild it
- );
- setState(() {
- _snackBars.addLast(controller);
- });
- _updateScaffolds();
- return controller;
- }
-
- void _handleStatusChanged(AnimationStatus status) {
- switch (status) {
- case AnimationStatus.dismissed:
- assert(_snackBars.isNotEmpty);
- setState(() {
- _snackBars.removeFirst();
- });
- _updateScaffolds();
- if (_snackBars.isNotEmpty) {
- _snackBarController.forward();
- }
- break;
- case AnimationStatus.completed:
- setState(() {
- assert(_snackBarTimer == null);
- // build will create a new timer if necessary to dismiss the snackBar.
- });
- _updateScaffolds();
- break;
- case AnimationStatus.forward:
- break;
- case AnimationStatus.reverse:
- break;
- }
- }
-
- void _updateScaffolds() {
- for (final ScaffoldState scaffold in _scaffolds) {
- scaffold._updateSnackBar();
- }
- }
-
- /// Removes the current [SnackBar] (if any) immediately from registered
- /// [Scaffold]s.
- ///
- /// The removed snack bar does not run its normal exit animation. If there are
- /// any queued snack bars, they begin their entrance animation immediately.
- void removeCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.remove }) {
- assert(reason != null);
- if (_snackBars.isEmpty)
- return;
- final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
- if (!completer.isCompleted)
- completer.complete(reason);
- _snackBarTimer?.cancel();
- _snackBarTimer = null;
- // This will trigger the animation's status callback.
- _snackBarController.value = 0.0;
- }
-
- /// Removes the current [SnackBar] by running its normal exit animation.
- ///
- /// The closed completer is called after the animation is complete.
- void hideCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.hide }) {
- assert(reason != null);
- if (_snackBars.isEmpty || _snackBarController.status == AnimationStatus.dismissed)
- return;
- final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
- if (_accessibleNavigation) {
- _snackBarController.value = 0.0;
- completer.complete(reason);
- } else {
- _snackBarController.reverse().then<void>((void value) {
- assert(mounted);
- if (!completer.isCompleted)
- completer.complete(reason);
- });
- }
- _snackBarTimer?.cancel();
- _snackBarTimer = null;
- }
-
- @override
- Widget build(BuildContext context) {
- assert(debugCheckHasMediaQuery(context));
- final MediaQueryData mediaQuery = MediaQuery.of(context);
- _accessibleNavigation = mediaQuery.accessibleNavigation;
-
- if (_snackBars.isNotEmpty) {
- final ModalRoute<dynamic> route = ModalRoute.of(context);
- if (route == null || route.isCurrent) {
- if (_snackBarController.isCompleted && _snackBarTimer == null) {
- final SnackBar snackBar = _snackBars.first._widget;
- _snackBarTimer = Timer(snackBar.duration, () {
- assert(_snackBarController.status == AnimationStatus.forward || _snackBarController.status == AnimationStatus.completed);
- // Look up MediaQuery again in case the setting changed.
- final MediaQueryData mediaQuery = MediaQuery.of(context);
- if (mediaQuery.accessibleNavigation && snackBar.action != null)
- return;
- hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
- });
- }
- }
- }
-
- return _ScaffoldMessengerScope(
- scaffoldMessengerState: this,
- child: widget.child,
- );
- }
-
- @override
- void dispose() {
- _snackBarController?.dispose();
- _snackBarTimer?.cancel();
- _snackBarTimer = null;
- super.dispose();
- }
-}
-
-class _ScaffoldMessengerScope extends InheritedWidget {
- const _ScaffoldMessengerScope({
- Key key,
- Widget child,
- ScaffoldMessengerState scaffoldMessengerState,
- }) : _scaffoldMessengerState = scaffoldMessengerState,
- super(key: key, child: child);
-
- final ScaffoldMessengerState _scaffoldMessengerState;
-
- @override
- bool updateShouldNotify(_ScaffoldMessengerScope old) => _scaffoldMessengerState != old._scaffoldMessengerState;
-}
-
/// The geometry of the [Scaffold] after all its contents have been laid out
/// except the [FloatingActionButton].
///
@@ -1204,11 +837,11 @@
/// Implements the basic material design visual layout structure.
///
-/// This class provides APIs for showing drawers and bottom sheets.
+/// This class provides APIs for showing drawers, snack bars, and bottom sheets.
///
-/// To display a persistent bottom sheet, obtain the
+/// To display a snackbar or a persistent bottom sheet, obtain the
/// [ScaffoldState] for the current [BuildContext] via [Scaffold.of] and use the
-/// [ScaffoldState.showBottomSheet] function.
+/// [ScaffoldState.showSnackBar] and [ScaffoldState.showBottomSheet] functions.
///
/// {@tool dartpad --template=stateful_widget_material}
/// This example shows a [Scaffold] with a [body] and [FloatingActionButton].
@@ -1285,7 +918,7 @@
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
-/// title: const Text('Sample Code'),
+/// title: Text('Sample Code'),
/// ),
/// body: Center(
/// child: Text('You have pressed the button $_count times.'),
@@ -1373,6 +1006,8 @@
/// * [BottomNavigationBar], which is a horizontal array of buttons typically
/// shown along the bottom of the app using the [bottomNavigationBar]
/// property.
+/// * [SnackBar], which is a temporary notification typically shown near the
+/// bottom of the app using the [ScaffoldState.showSnackBar] method.
/// * [BottomSheet], which is an overlay typically shown near the bottom of the
/// app. A bottom sheet can either be persistent, in which case it is shown
/// using the [ScaffoldState.showBottomSheet] method, or modal, in which case
@@ -1380,6 +1015,7 @@
/// * [ScaffoldState], which is the state associated with this widget.
/// * <https://material.io/design/layout/responsive-layout-grid.html>
/// * Cookbook: [Add a Drawer to a screen](https://flutter.dev/docs/cookbook/design/drawer)
+/// * Cookbook: [Display a snackbar](https://flutter.dev/docs/cookbook/design/snackbars)
/// * See our
/// [Scaffold Sample Apps](https://flutter.dev/docs/catalog/samples/Scaffold).
class Scaffold extends StatefulWidget {
@@ -1751,7 +1387,7 @@
/// ),
/// home: Scaffold(
/// body: MyScaffoldBody(),
- /// appBar: AppBar(title: const Text('Scaffold.of Example')),
+ /// appBar: AppBar(title: Text('Scaffold.of Example')),
/// ),
/// color: Colors.white,
/// );
@@ -1765,30 +1401,12 @@
/// Widget build(BuildContext context) {
/// return Center(
/// child: ElevatedButton(
- /// child: const Text('SHOW BOTTOM SHEET'),
+ /// child: Text('SHOW A SNACKBAR'),
/// onPressed: () {
- /// Scaffold.of(context).showBottomSheet<void>(
- /// (BuildContext context) {
- /// return Container(
- /// alignment: Alignment.center,
- /// height: 200,
- /// color: Colors.amber,
- /// child: Center(
- /// child: Column(
- /// mainAxisSize: MainAxisSize.min,
- /// children: <Widget>[
- /// const Text('BottomSheet'),
- /// ElevatedButton(
- /// child: const Text('Close BottomSheet'),
- /// onPressed: () {
- /// Navigator.pop(context);
- /// },
- /// )
- /// ],
- /// ),
- /// ),
- /// );
- /// },
+ /// Scaffold.of(context).showSnackBar(
+ /// SnackBar(
+ /// content: Text('Have a snack!'),
+ /// ),
/// );
/// },
/// ),
@@ -1809,38 +1427,20 @@
/// ```dart
/// Widget build(BuildContext context) {
/// return Scaffold(
- /// appBar: AppBar(title: const Text('Demo')),
+ /// appBar: AppBar(
+ /// title: Text('Demo')
+ /// ),
/// body: Builder(
/// // Create an inner BuildContext so that the onPressed methods
/// // can refer to the Scaffold with Scaffold.of().
/// builder: (BuildContext context) {
/// return Center(
/// child: ElevatedButton(
- /// child: const Text('SHOW BOTTOM SHEET'),
+ /// child: Text('SHOW A SNACKBAR'),
/// onPressed: () {
- /// Scaffold.of(context).showBottomSheet<void>(
- /// (BuildContext context) {
- /// return Container(
- /// alignment: Alignment.center,
- /// height: 200,
- /// color: Colors.amber,
- /// child: Center(
- /// child: Column(
- /// mainAxisSize: MainAxisSize.min,
- /// children: <Widget>[
- /// const Text('BottomSheet'),
- /// ElevatedButton(
- /// child: const Text('Close BottomSheet'),
- /// onPressed: () {
- /// Navigator.pop(context);
- /// },
- /// )
- /// ],
- /// ),
- /// ),
- /// );
- /// },
- /// );
+ /// Scaffold.of(context).showSnackBar(SnackBar(
+ /// content: Text('Have a snack!'),
+ /// ));
/// },
/// ),
/// );
@@ -1957,7 +1557,7 @@
/// See also:
///
/// * [Scaffold.of], which provides access to the [ScaffoldState] object as a
- /// whole, from which you can show bottom sheets, and so forth.
+ /// whole, from which you can show snackbars, bottom sheets, and so forth.
static bool hasDrawer(BuildContext context, { bool registerForUpdates = true }) {
assert(registerForUpdates != null);
assert(context != null);
@@ -1976,8 +1576,8 @@
/// State for a [Scaffold].
///
-/// Can display [BottomSheet]s. Retrieve a [ScaffoldState] from the current
-/// [BuildContext] using [Scaffold.of].
+/// Can display [SnackBar]s and [BottomSheet]s. Retrieve a [ScaffoldState] from
+/// the current [BuildContext] using [Scaffold.of].
class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
// DRAWER API
@@ -2068,10 +1668,12 @@
// SNACKBAR API
- ScaffoldMessengerState _scaffoldMessenger;
+ final Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>> _snackBars = Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>>();
+ AnimationController _snackBarController;
+ Timer _snackBarTimer;
+ bool _accessibleNavigation;
- /// [ScaffoldMessengerState.showSnackBar] shows a [SnackBar] at the bottom of
- /// the scaffold. This method should not be used.
+ /// Shows a [SnackBar] at the bottom of the scaffold.
///
/// 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
@@ -2080,14 +1682,12 @@
///
/// To control how long a [SnackBar] remains visible, use [SnackBar.duration].
///
- /// To remove the [SnackBar] with an exit animation, use
- /// [ScaffoldMessengerState.hideCurrentSnackBar] or call
- /// [ScaffoldFeatureController.close] on the returned [ScaffoldFeatureController].
- /// To remove a [SnackBar] suddenly (without an animation), use
- /// [ScaffoldMessengerState.removeCurrentSnackBar].
+ /// To remove the [SnackBar] with an exit animation, use [hideCurrentSnackBar]
+ /// or call [ScaffoldFeatureController.close] on the returned
+ /// [ScaffoldFeatureController]. To remove a [SnackBar] suddenly (without an
+ /// animation), use [removeCurrentSnackBar].
///
- /// See [ScaffoldMessenger.of] for information about how to obtain the
- /// [ScaffoldMessengerState].
+ /// See [Scaffold.of] for information about how to obtain the [ScaffoldState].
///
/// {@tool dartpad --template=stateless_widget_scaffold_center}
///
@@ -2097,66 +1697,104 @@
/// Widget build(BuildContext context) {
/// return OutlinedButton(
/// onPressed: () {
- /// ScaffoldMessenger.of(context).showSnackBar(
+ /// Scaffold.of(context).showSnackBar(
/// SnackBar(
- /// content: const Text('A SnackBar has been shown.'),
+ /// content: Text('A SnackBar has been shown.'),
/// ),
/// );
/// },
- /// child: const Text('Show SnackBar'),
+ /// child: Text('Show SnackBar'),
/// );
/// }
/// ```
/// {@end-tool}
- ///
- /// See also:
- ///
- /// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
- // TODO(Piinks): Deprecate after customers are migrated
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackbar) {
- assert(debugCheckHasScaffoldMessenger(context));
- return _scaffoldMessenger.showSnackBar(snackbar);
+ _snackBarController ??= SnackBar.createAnimationController(vsync: this)
+ ..addStatusListener(_handleSnackBarStatusChange);
+ if (_snackBars.isEmpty) {
+ assert(_snackBarController.isDismissed);
+ _snackBarController.forward();
+ }
+ ScaffoldFeatureController<SnackBar, SnackBarClosedReason> controller;
+ controller = ScaffoldFeatureController<SnackBar, SnackBarClosedReason>._(
+ // We provide a fallback key so that if back-to-back snackbars happen to
+ // match in structure, material ink splashes and highlights don't survive
+ // from one to the next.
+ snackbar.withAnimation(_snackBarController, fallbackKey: UniqueKey()),
+ Completer<SnackBarClosedReason>(),
+ () {
+ assert(_snackBars.first == controller);
+ hideCurrentSnackBar(reason: SnackBarClosedReason.hide);
+ },
+ null, // SnackBar doesn't use a builder function so setState() wouldn't rebuild it
+ );
+ setState(() {
+ _snackBars.addLast(controller);
+ });
+ return controller;
}
- /// [ScaffoldMessengerState.removeCurrentSnackBar] removes the current
- /// [SnackBar] (if any) immediately. This method should not be used.
+ void _handleSnackBarStatusChange(AnimationStatus status) {
+ switch (status) {
+ case AnimationStatus.dismissed:
+ assert(_snackBars.isNotEmpty);
+ setState(() {
+ _snackBars.removeFirst();
+ });
+ if (_snackBars.isNotEmpty)
+ _snackBarController.forward();
+ break;
+ case AnimationStatus.completed:
+ setState(() {
+ assert(_snackBarTimer == null);
+ // build will create a new timer if necessary to dismiss the snack bar
+ });
+ break;
+ case AnimationStatus.forward:
+ case AnimationStatus.reverse:
+ break;
+ }
+ }
+
+ /// Removes the current [SnackBar] (if any) immediately.
///
/// The removed snack bar does not run its normal exit animation. If there are
/// any queued snack bars, they begin their entrance animation immediately.
- ///
- /// See also:
- ///
- /// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
- // TODO(Piinks): Deprecate after customers are migrated
void removeCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.remove }) {
- assert(debugCheckHasScaffoldMessenger(context));
- _scaffoldMessenger.removeCurrentSnackBar(reason: reason);
+ assert(reason != null);
+ if (_snackBars.isEmpty)
+ return;
+ final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
+ if (!completer.isCompleted)
+ completer.complete(reason);
+ _snackBarTimer?.cancel();
+ _snackBarTimer = null;
+ _snackBarController.value = 0.0;
}
- /// [ScaffoldMessengerState.hideCurrentSnackBar] removes the current
- /// [SnackBar] by running its normal exit animation. This method should not be
- /// used.
+ /// Removes the current [SnackBar] by running its normal exit animation.
///
/// The closed completer is called after the animation is complete.
- ///
- /// See also:
- ///
- /// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
- // TODO(Piinks): Deprecate after customers are migrated.
void hideCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.hide }) {
- assert(debugCheckHasScaffoldMessenger(context));
- _scaffoldMessenger.hideCurrentSnackBar(reason: reason);
+ assert(reason != null);
+ if (_snackBars.isEmpty || _snackBarController.status == AnimationStatus.dismissed)
+ return;
+ final MediaQueryData mediaQuery = MediaQuery.of(context);
+ final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
+ if (mediaQuery.accessibleNavigation) {
+ _snackBarController.value = 0.0;
+ completer.complete(reason);
+ } else {
+ _snackBarController.reverse().then<void>((void value) {
+ assert(mounted);
+ if (!completer.isCompleted)
+ completer.complete(reason);
+ });
+ }
+ _snackBarTimer?.cancel();
+ _snackBarTimer = null;
}
- ScaffoldFeatureController<SnackBar, SnackBarClosedReason> _snackBar;
-
- void _updateSnackBar() {
- setState(() {
- _snackBar = _scaffoldMessenger._snackBars.isNotEmpty
- ? _scaffoldMessenger._snackBars.first
- : null;
- });
- }
// PERSISTENT BOTTOM SHEET API
@@ -2378,9 +2016,7 @@
/// const Text('BottomSheet'),
/// ElevatedButton(
/// child: const Text('Close BottomSheet'),
- /// onPressed: () {
- /// Navigator.pop(context);
- /// }
+ /// onPressed: () => Navigator.pop(context),
/// )
/// ],
/// ),
@@ -2568,14 +2204,27 @@
@override
void didChangeDependencies() {
- _scaffoldMessenger = ScaffoldMessenger.of(context);
- _scaffoldMessenger?._register(this);
+ final MediaQueryData mediaQuery = MediaQuery.of(context);
+ // If we transition from accessible navigation to non-accessible navigation
+ // and there is a SnackBar that would have timed out that has already
+ // completed its timer, dismiss that SnackBar. If the timer hasn't finished
+ // yet, let it timeout as normal.
+ if (_accessibleNavigation == true
+ && !mediaQuery.accessibleNavigation
+ && _snackBarTimer != null
+ && !_snackBarTimer.isActive) {
+ hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
+ }
+ _accessibleNavigation = mediaQuery.accessibleNavigation;
_maybeBuildPersistentBottomSheet();
super.didChangeDependencies();
}
@override
void dispose() {
+ _snackBarController?.dispose();
+ _snackBarTimer?.cancel();
+ _snackBarTimer = null;
_geometryNotifier.dispose();
for (final _StandardBottomSheet bottomSheet in _dismissedBottomSheets) {
bottomSheet.animationController?.dispose();
@@ -2585,7 +2234,6 @@
}
_floatingActionButtonMoveController.dispose();
_floatingActionButtonVisibilityController.dispose();
- _scaffoldMessenger?._unregister(this);
super.dispose();
}
@@ -2699,6 +2347,28 @@
final MediaQueryData mediaQuery = MediaQuery.of(context);
final ThemeData themeData = Theme.of(context);
final TextDirection textDirection = Directionality.of(context);
+ _accessibleNavigation = mediaQuery.accessibleNavigation;
+
+ if (_snackBars.isNotEmpty) {
+ final ModalRoute<dynamic> route = ModalRoute.of(context);
+ if (route == null || route.isCurrent) {
+ if (_snackBarController.isCompleted && _snackBarTimer == null) {
+ final SnackBar snackBar = _snackBars.first._widget;
+ _snackBarTimer = Timer(snackBar.duration, () {
+ assert(_snackBarController.status == AnimationStatus.forward ||
+ _snackBarController.status == AnimationStatus.completed);
+ // Look up MediaQuery again in case the setting changed.
+ final MediaQueryData mediaQuery = MediaQuery.of(context);
+ if (mediaQuery.accessibleNavigation && snackBar.action != null)
+ return;
+ hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
+ });
+ }
+ } else {
+ _snackBarTimer?.cancel();
+ _snackBarTimer = null;
+ }
+ }
final List<LayoutId> children = <LayoutId>[];
_addIfNonNull(
@@ -2753,16 +2423,16 @@
bool isSnackBarFloating = false;
double snackBarWidth;
- if (_snackBar != null) {
- final SnackBarBehavior snackBarBehavior = _snackBar._widget.behavior
+ if (_snackBars.isNotEmpty) {
+ final SnackBarBehavior snackBarBehavior = _snackBars.first._widget.behavior
?? themeData.snackBarTheme.behavior
?? SnackBarBehavior.fixed;
isSnackBarFloating = snackBarBehavior == SnackBarBehavior.floating;
- snackBarWidth = _snackBar._widget.width;
+ snackBarWidth = _snackBars.first._widget.width;
_addIfNonNull(
children,
- _snackBar._widget,
+ _snackBars.first._widget,
_ScaffoldSlot.snackBar,
removeLeftPadding: false,
removeTopPadding: true,
@@ -2926,8 +2596,7 @@
/// An interface for controlling a feature of a [Scaffold].
///
-/// Commonly obtained from [ScaffoldMessengerState.showSnackBar] or
-/// [ScaffoldState.showBottomSheet].
+/// Commonly obtained from [ScaffoldState.showSnackBar] or [ScaffoldState.showBottomSheet].
class ScaffoldFeatureController<T extends Widget, U> {
const ScaffoldFeatureController._(this._widget, this._completer, this.close, this.setState);
final T _widget;
diff --git a/packages/flutter/lib/src/material/snack_bar.dart b/packages/flutter/lib/src/material/snack_bar.dart
index be39856..39c1e7a 100644
--- a/packages/flutter/lib/src/material/snack_bar.dart
+++ b/packages/flutter/lib/src/material/snack_bar.dart
@@ -32,7 +32,7 @@
/// Specify how a [SnackBar] was closed.
///
-/// The [ScaffoldMessengerState.showSnackBar] function returns a
+/// The [ScaffoldState.showSnackBar] function returns a
/// [ScaffoldFeatureController]. The value of the controller's closed property
/// is a Future that resolves to a SnackBarClosedReason. Applications that need
/// to know how a snackbar was closed can use this value.
@@ -40,7 +40,7 @@
/// Example:
///
/// ```dart
-/// ScaffoldMessenger.of(context).showSnackBar(
+/// Scaffold.of(context).showSnackBar(
/// SnackBar( ... )
/// ).closed.then((SnackBarClosedReason reason) {
/// ...
@@ -57,10 +57,10 @@
swipe,
/// The snack bar was closed by the [ScaffoldFeatureController] close callback
- /// or by calling [ScaffoldMessengerState.hideCurrentSnackBar] directly.
+ /// or by calling [ScaffoldState.hideCurrentSnackBar] directly.
hide,
- /// The snack bar was closed by an call to [ScaffoldMessengerState.removeCurrentSnackBar].
+ /// The snack bar was closed by an call to [ScaffoldState.removeCurrentSnackBar].
remove,
/// The snack bar was closed because its timer expired.
@@ -123,7 +123,7 @@
_haveTriggeredAction = true;
});
widget.onPressed();
- ScaffoldMessenger.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.action);
+ Scaffold.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.action);
}
@override
@@ -146,8 +146,8 @@
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=zpO6n_oZWw0}
///
-/// To display a snack bar, call `ScaffoldMessenger.of(context).showSnackBar()`,
-/// passing an instance of [SnackBar] that describes the message.
+/// To display a snack bar, call `Scaffold.of(context).showSnackBar()`, passing
+/// an instance of [SnackBar] that describes the message.
///
/// To control how long the [SnackBar] remains visible, specify a [duration].
///
@@ -156,11 +156,11 @@
///
/// See also:
///
-/// * [ScaffoldMessenger.of], to obtain the current [ScaffoldMessengerState],
-/// which manages the display and animation of snack bars.
-/// * [ScaffoldMessengerState.showSnackBar], which displays a [SnackBar].
-/// * [ScaffoldMessengerState.removeCurrentSnackBar], which abruptly hides the
-/// currently displayed snack bar, if any, and allows the next to be displayed.
+/// * [Scaffold.of], to obtain the current [ScaffoldState], which manages the
+/// display and animation of snack bars.
+/// * [ScaffoldState.showSnackBar], which displays a [SnackBar].
+/// * [ScaffoldState.removeCurrentSnackBar], which abruptly hides the currently
+/// displayed snack bar, if any, and allows the next to be displayed.
/// * [SnackBarAction], which is used to specify an [action] button to show
/// on the snack bar.
/// * [SnackBarThemeData], to configure the default property values for
@@ -289,7 +289,7 @@
///
/// See also:
///
- /// * [ScaffoldMessengerState.removeCurrentSnackBar], which abruptly hides the
+ /// * [ScaffoldState.removeCurrentSnackBar], which abruptly hides the
/// currently displayed snack bar, if any, and allows the next to be
/// displayed.
/// * <https://material.io/design/components/snackbars.html>
@@ -301,7 +301,7 @@
/// Called the first time that the snackbar is visible within a [Scaffold].
final VoidCallback onVisible;
- // API for ScaffoldMessengerState.showSnackBar():
+ // API for Scaffold.showSnackBar():
/// Creates an animation controller useful for driving a snack bar's entrance and exit animation.
static AnimationController createAnimationController({ @required TickerProvider vsync }) {
@@ -516,14 +516,14 @@
container: true,
liveRegion: true,
onDismiss: () {
- ScaffoldMessenger.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.dismiss);
+ Scaffold.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.dismiss);
},
child: Dismissible(
key: const Key('dismissible'),
direction: DismissDirection.down,
resizeDuration: null,
onDismissed: (DismissDirection direction) {
- ScaffoldMessenger.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.swipe);
+ Scaffold.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.swipe);
},
child: snackBar,
),
@@ -550,9 +550,7 @@
child: snackBar,
);
}
- return Hero(
- child: ClipRect(child: snackBarTransition),
- tag: '<SnackBar Hero tag - ${widget.content}>',
- );
+
+ return ClipRect(child: snackBarTransition);
}
}
diff --git a/packages/flutter/lib/src/widgets/animated_list.dart b/packages/flutter/lib/src/widgets/animated_list.dart
index a01be9d..f463dd1 100644
--- a/packages/flutter/lib/src/widgets/animated_list.dart
+++ b/packages/flutter/lib/src/widgets/animated_list.dart
@@ -512,7 +512,6 @@
/// class _SliverAnimatedListSampleState extends State<SliverAnimatedListSample> {
/// final GlobalKey<SliverAnimatedListState> _listKey = GlobalKey<SliverAnimatedListState>();
/// final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
-/// final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
/// ListModel<int> _list;
/// int _selectedItem;
/// int _nextItem; // The next item inserted when the user presses the '+' button.
@@ -570,7 +569,7 @@
/// _selectedItem = null;
/// });
/// } else {
-/// _scaffoldMessengerKey.currentState.showSnackBar(SnackBar(
+/// _scaffoldKey.currentState.showSnackBar(SnackBar(
/// content: Text(
/// 'Select an item to remove from the list.',
/// style: TextStyle(fontSize: 20),
@@ -582,7 +581,6 @@
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
-/// scaffoldMessengerKey: _scaffoldMessengerKey,
/// home: Scaffold(
/// key: _scaffoldKey,
/// body: CustomScrollView(
diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart
index 82f2d10..b73c517 100644
--- a/packages/flutter/lib/src/widgets/framework.dart
+++ b/packages/flutter/lib/src/widgets/framework.dart
@@ -2084,52 +2084,33 @@
/// widget can be used: the build context passed to the [Builder.builder]
/// callback will be that of the [Builder] itself.
///
-/// For example, in the following snippet, the [ScaffoldState.showBottomSheet]
+/// For example, in the following snippet, the [ScaffoldState.showSnackBar]
/// method is called on the [Scaffold] widget that the build method itself
/// creates. If a [Builder] had not been used, and instead the `context`
/// argument of the build method itself had been used, no [Scaffold] would have
/// been found, and the [Scaffold.of] function would have returned null.
///
/// ```dart
-/// @override
-/// Widget build(BuildContext context) {
-/// // here, Scaffold.of(context) returns null
-/// return Scaffold(
-/// appBar: AppBar(title: Text('Demo')),
-/// body: Builder(
-/// builder: (BuildContext context) {
-/// return TextButton(
-/// child: Text('BUTTON'),
-/// onPressed: () {
-/// Scaffold.of(context).showBottomSheet<void>(
-/// (BuildContext context) {
-/// return Container(
-/// alignment: Alignment.center,
-/// height: 200,
-/// color: Colors.amber,
-/// child: Center(
-/// child: Column(
-/// mainAxisSize: MainAxisSize.min,
-/// children: <Widget>[
-/// const Text('BottomSheet'),
-/// ElevatedButton(
-/// child: const Text('Close BottomSheet'),
-/// onPressed: () {
-/// Navigator.pop(context),
-/// },
-/// )
-/// ],
-/// ),
-/// ),
-/// );
-/// },
-/// );
-/// },
-/// );
-/// },
-/// )
-/// );
-/// }
+/// @override
+/// Widget build(BuildContext context) {
+/// // here, Scaffold.of(context) returns null
+/// return Scaffold(
+/// appBar: AppBar(title: Text('Demo')),
+/// body: Builder(
+/// builder: (BuildContext context) {
+/// return TextButton(
+/// child: Text('BUTTON'),
+/// onPressed: () {
+/// // here, Scaffold.of(context) returns the locally created Scaffold
+/// Scaffold.of(context).showSnackBar(SnackBar(
+/// content: Text('Hello.')
+/// ));
+/// }
+/// );
+/// }
+/// )
+/// );
+/// }
/// ```
///
/// The [BuildContext] for a particular widget can change location over time as
diff --git a/packages/flutter/test/material/debug_test.dart b/packages/flutter/test/material/debug_test.dart
index 9ff158c..5461742 100644
--- a/packages/flutter/test/material/debug_test.dart
+++ b/packages/flutter/test/material/debug_test.dart
@@ -164,8 +164,6 @@
' _InheritedTheme\n'
' Theme\n'
' AnimatedTheme\n'
- ' _ScaffoldMessengerScope\n'
- ' ScaffoldMessenger\n'
' Builder\n'
' DefaultTextStyle\n'
' CustomPaint\n'
@@ -198,50 +196,4 @@
' or WidgetsApp widget at the top of your application widget tree.\n',
));
});
-
- testWidgets('debugCheckHasScaffoldMessenger control test', (WidgetTester tester) async {
- final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
- await tester.pumpWidget(Directionality(
- textDirection: TextDirection.ltr,
- child: MediaQuery(
- data: const MediaQueryData(),
- child: Scaffold(
- key: _scaffoldKey,
- body: Container(),
- ),
- ),
- ));
- FlutterError error;
- try {
- _scaffoldKey.currentState.showSnackBar(const SnackBar(content: Text('Something is missing here')));
- } on FlutterError catch (e) {
- error = e;
- } finally {
- expect(error.diagnostics.length, 5);
- expect(error.diagnostics[2], isA<DiagnosticsProperty<Element>>());
- expect(error.diagnostics[3], isA<DiagnosticsBlock>());
- expect(error.diagnostics[4].level, DiagnosticLevel.hint);
- expect(
- error.diagnostics[4].toStringDeep(),
- equalsIgnoringHashCodes(
- 'Typically, the ScaffoldMessenger widget is introduced by the\n'
- 'MaterialApp at the top of your application widget tree.\n',
- ),
- );
- expect(error.toStringDeep(), equalsIgnoringHashCodes(
- 'FlutterError\n'
- ' No ScaffoldMessenger widget found.\n'
- ' Scaffold widgets require a ScaffoldMessenger widget ancestor.\n'
- ' The specific widget that could not find a ScaffoldMessenger\n'
- ' ancestor was:\n'
- ' Scaffold-[LabeledGlobalKey<ScaffoldState>#d60fa]\n'
- ' The ancestors of this widget were:\n'
- ' MediaQuery\n'
- ' Directionality\n'
- ' [root]\n'
- ' Typically, the ScaffoldMessenger widget is introduced by the\n'
- ' MaterialApp at the top of your application widget tree.\n'
- ));
- }
- });
}
diff --git a/packages/flutter/test/material/floating_action_button_location_test.dart b/packages/flutter/test/material/floating_action_button_location_test.dart
index 3086003..c919cf7 100644
--- a/packages/flutter/test/material/floating_action_button_location_test.dart
+++ b/packages/flutter/test/material/floating_action_button_location_test.dart
@@ -649,7 +649,7 @@
builder: (BuildContext context) {
return FloatingActionButton(
onPressed: () {
- ScaffoldMessenger.of(context).showSnackBar(
+ Scaffold.of(context).showSnackBar(
const SnackBar(content: Text('Snacky!')),
);
},
diff --git a/packages/flutter/test/material/snack_bar_test.dart b/packages/flutter/test/material/snack_bar_test.dart
index 481854f..6e28582 100644
--- a/packages/flutter/test/material/snack_bar_test.dart
+++ b/packages/flutter/test/material/snack_bar_test.dart
@@ -18,7 +18,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
+ Scaffold.of(context).showSnackBar(const SnackBar(
content: Text(helloSnackBar),
duration: Duration(seconds: 2),
));
@@ -64,7 +64,7 @@
return GestureDetector(
onTap: () {
snackBarCount += 1;
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: Text('bar$snackBarCount'),
duration: const Duration(seconds: 2),
));
@@ -141,7 +141,7 @@
return GestureDetector(
onTap: () {
snackBarCount += 1;
- lastController = ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ lastController = Scaffold.of(context).showSnackBar(SnackBar(
content: Text('bar$snackBarCount'),
duration: Duration(seconds: time),
));
@@ -225,7 +225,7 @@
return GestureDetector(
onTap: () {
snackBarCount += 1;
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: Text('bar$snackBarCount'),
duration: const Duration(seconds: 2),
));
@@ -268,7 +268,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
@@ -309,7 +309,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(
+ Scaffold.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
@@ -351,7 +351,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(
+ Scaffold.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
@@ -389,7 +389,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(
+ Scaffold.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
margin: const EdgeInsets.all(padding),
@@ -476,7 +476,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(
+ Scaffold.of(context).showSnackBar(
const SnackBar(
content: Text('I am a snack bar.'),
padding: EdgeInsets.all(padding),
@@ -520,7 +520,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(
+ Scaffold.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
width: width,
@@ -558,7 +558,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(
+ Scaffold.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
@@ -611,7 +611,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () { }),
@@ -666,7 +666,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
@@ -717,7 +717,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
@@ -771,7 +771,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
@@ -829,7 +829,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
@@ -861,18 +861,18 @@
});
testWidgets('SnackBarClosedReason', (WidgetTester tester) async {
- final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
+ final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
bool actionPressed = false;
SnackBarClosedReason closedReason;
await tester.pumpWidget(MaterialApp(
- scaffoldMessengerKey: scaffoldMessengerKey,
home: Scaffold(
+ key: scaffoldKey,
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('snack'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
@@ -917,14 +917,14 @@
// Pop up the snack bar and then remove it.
await tester.tap(find.text('X'));
await tester.pump(const Duration(milliseconds: 750));
- scaffoldMessengerKey.currentState.removeCurrentSnackBar();
+ scaffoldKey.currentState.removeCurrentSnackBar();
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(closedReason, equals(SnackBarClosedReason.remove));
// Pop up the snack bar and then hide it.
await tester.tap(find.text('X'));
await tester.pump(const Duration(milliseconds: 750));
- scaffoldMessengerKey.currentState.hideCurrentSnackBar();
+ scaffoldKey.currentState.hideCurrentSnackBar();
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(closedReason, equals(SnackBarClosedReason.hide));
@@ -944,27 +944,25 @@
await tester.pumpWidget(MaterialApp(
home: MediaQuery(
data: const MediaQueryData(accessibleNavigation: true),
- child: ScaffoldMessenger(
- child: Builder(
+ child: Scaffold(
+ key: scaffoldKey,
+ body: Builder(
builder: (BuildContext context) {
- return Scaffold(
- key: scaffoldKey,
- body: GestureDetector(
- onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
- content: const Text('snack'),
- duration: const Duration(seconds: 1),
- action: SnackBarAction(
- label: 'ACTION',
- onPressed: () { },
- ),
- ));
- },
- child: const Text('X'),
- ),
+ return GestureDetector(
+ onTap: () {
+ Scaffold.of(context).showSnackBar(SnackBar(
+ content: const Text('snack'),
+ duration: const Duration(seconds: 1),
+ action: SnackBarAction(
+ label: 'ACTION',
+ onPressed: () { },
+ ),
+ ));
+ },
+ child: const Text('X'),
);
- }
- )
+ },
+ ),
),
),
));
@@ -986,29 +984,29 @@
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
await tester.pumpWidget(MaterialApp(
- home: MediaQuery(
- data: const MediaQueryData(accessibleNavigation: true),
- child: ScaffoldMessenger(
- child: Builder(builder: (BuildContext context) {
- return Scaffold(
- key: scaffoldKey,
- body: GestureDetector(
- onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
- content: const Text('snack'),
- duration: const Duration(seconds: 1),
- action: SnackBarAction(
- label: 'ACTION',
- onPressed: () { },
- ),
- ));
- },
- child: const Text('X'),
- ),
- );
- }),
+ home: MediaQuery(
+ data: const MediaQueryData(accessibleNavigation: true),
+ child: Scaffold(
+ key: scaffoldKey,
+ body: Builder(
+ builder: (BuildContext context) {
+ return GestureDetector(
+ onTap: () {
+ Scaffold.of(context).showSnackBar(SnackBar(
+ content: const Text('snack'),
+ duration: const Duration(seconds: 1),
+ action: SnackBarAction(
+ label: 'ACTION',
+ onPressed: () { },
+ ),
+ ));
+ },
+ child: const Text('X'),
+ );
+ },
+ ),
+ ),
),
- )
));
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
@@ -1033,7 +1031,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
+ Scaffold.of(context).showSnackBar(const SnackBar(
content: Text(helloSnackBar),
));
},
@@ -1082,7 +1080,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('test'),
action: SnackBarAction(label: 'foo', onPressed: () { }),
));
@@ -1128,7 +1126,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: Text(nonconst('hello')),
duration: null,
));
@@ -1158,7 +1156,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('hello'),
duration: const Duration(seconds: 1),
onVisible: () {
@@ -1195,14 +1193,14 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('hello'),
duration: const Duration(seconds: 1),
onVisible: () {
called += 1;
},
));
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('hello 2'),
duration: const Duration(seconds: 1),
onVisible: () {
@@ -1249,8 +1247,8 @@
),
);
- final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
- scaffoldMessengerState.showSnackBar(snackBar);
+ final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));
+ scaffoldState.showSnackBar(snackBar);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
@@ -1280,8 +1278,8 @@
),
);
- final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
- scaffoldMessengerState.showSnackBar(snackBar);
+ final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));
+ scaffoldState.showSnackBar(snackBar);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
@@ -1300,8 +1298,9 @@
testWidgets(
'Padding of $behavior is not consumed by viewInsets',
(WidgetTester tester) async {
- final Widget child = MaterialApp(
- home: Scaffold(
+ final Widget child = Directionality(
+ textDirection: TextDirection.ltr,
+ child: Scaffold(
resizeToAvoidBottomInset: false,
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.send),
@@ -1311,7 +1310,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(
+ Scaffold.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
@@ -1375,8 +1374,8 @@
),
);
- final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
- scaffoldMessengerState.showSnackBar(
+ final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));
+ scaffoldState.showSnackBar(
const SnackBar(
content: Text('Snackbar text'),
behavior: SnackBarBehavior.fixed,
@@ -1411,7 +1410,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
@@ -1453,8 +1452,8 @@
),
);
- final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
- scaffoldMessengerState.showSnackBar(
+ final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));
+ scaffoldState.showSnackBar(
const SnackBar(
content: Text('SnackBar text'),
behavior: SnackBarBehavior.fixed,
@@ -1490,8 +1489,8 @@
),
);
- final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
- scaffoldMessengerState.showSnackBar(
+ final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));
+ scaffoldState.showSnackBar(
const SnackBar(
content: Text('SnackBar text'),
behavior: SnackBarBehavior.floating,
@@ -1507,74 +1506,4 @@
},
);
});
-
- testWidgets('SnackBars hero across transitions', (WidgetTester tester) async {
- const String snackBarText = 'hello snackbar';
- const String firstHeader = 'home';
- const String secondHeader = 'second';
- const Key snackTarget = Key('snack-target');
- const Key transitionTarget = Key('transition-target');
-
- Widget _buildApp() {
- return MaterialApp(
- routes: <String, WidgetBuilder> {
- '/': (BuildContext context) {
- return Scaffold(
- appBar: AppBar(title: const Text(firstHeader)),
- body: Center(
- child: ElevatedButton(
- key: transitionTarget,
- child: const Text('PUSH'),
- onPressed: () {
- Navigator.of(context).pushNamed('/second');
- },
- ),
- ),
- floatingActionButton: FloatingActionButton(
- key: snackTarget,
- onPressed: () async {
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text(snackBarText),
- ),
- );
- },
- child: const Text('X'),
- ),
- );
- },
- '/second': (BuildContext context) => Scaffold(appBar: AppBar(title: const Text(secondHeader)),
- ),
- }
- );
- }
- await tester.pumpWidget(_buildApp());
-
- expect(find.text(snackBarText), findsNothing);
- expect(find.text(firstHeader), findsOneWidget);
- expect(find.text(secondHeader), findsNothing);
-
- // Present SnackBar
- await tester.tap(find.byKey(snackTarget));
- await tester.pump(); // schedule animation
- expect(find.text(snackBarText), findsOneWidget);
- await tester.pump(); // begin animation
- expect(find.text(snackBarText), findsOneWidget);
- await tester.pump(const Duration(milliseconds: 750));
- expect(find.text(snackBarText), findsOneWidget);
- // Push new route
- await tester.tap(find.byKey(transitionTarget));
- await tester.pump();
- expect(find.text(snackBarText), findsOneWidget);
- expect(find.text(firstHeader), findsOneWidget);
- expect(find.text(secondHeader, skipOffstage: false), findsOneWidget);
- await tester.pump();
- expect(find.text(snackBarText), findsOneWidget);
- expect(find.text(firstHeader), findsOneWidget);
- expect(find.text(secondHeader), findsOneWidget);
- await tester.pump(const Duration(milliseconds: 750));
- expect(find.text(snackBarText), findsOneWidget);
- expect(find.text(firstHeader), findsNothing);
- expect(find.text(secondHeader), findsOneWidget);
- });
}
diff --git a/packages/flutter/test/material/snack_bar_theme_test.dart b/packages/flutter/test/material/snack_bar_theme_test.dart
index fbbbec0..a49f5bc 100644
--- a/packages/flutter/test/material/snack_bar_theme_test.dart
+++ b/packages/flutter/test/material/snack_bar_theme_test.dart
@@ -73,7 +73,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text(text),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
@@ -110,7 +110,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text(text),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
@@ -153,7 +153,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
backgroundColor: backgroundColor,
elevation: elevation,
shape: shape,
@@ -200,7 +200,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
@@ -242,7 +242,7 @@
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ Scaffold.of(context).showSnackBar(SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),